diff --git a/feeds/add_feeds.py b/feeds/add_feeds.py index 94431a7..a55f386 100644 --- a/feeds/add_feeds.py +++ b/feeds/add_feeds.py @@ -3,6 +3,7 @@ from __future__ import annotations import datetime import logging from time import mktime, struct_time +from typing import TYPE_CHECKING from urllib.parse import ParseResult, urlparse import feedparser @@ -11,6 +12,9 @@ from feedparser import FeedParserDict from feeds.models import Author, Domain, Entry, Feed, Generator, Publisher +if TYPE_CHECKING: + from django.contrib.auth.models import AbstractBaseUser, AnonymousUser + logger: logging.Logger = logging.getLogger(__name__) @@ -205,8 +209,16 @@ def add_entry(feed: Feed, entry: FeedParserDict) -> Entry | None: return _entry -def add_feed(url: str | None) -> None | Feed: - """Add a feed to the database.""" +def add_feed(url: str | None, user: AbstractBaseUser | AnonymousUser) -> Feed | None: + """Add a feed to the database. + + Args: + url: The URL of the feed. + user: The user adding the feed. + + Returns: + The feed that was added. + """ # Parse the feed. parsed_feed: dict | None = parse_feed(url=url) if not parsed_feed: @@ -233,6 +245,7 @@ def add_feed(url: str | None) -> None | Feed: # Create the feed feed = Feed( feed_url=url, + user=user, domain=domain, last_checked=timezone.now(), bozo=parsed_feed.get("bozo", 0), diff --git a/feeds/migrations/0003_feed_user.py b/feeds/migrations/0003_feed_user.py new file mode 100644 index 0000000..bda67ee --- /dev/null +++ b/feeds/migrations/0003_feed_user.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.2 on 2024-02-23 05:38 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('feeds', '0002_alter_author_options_alter_domain_options_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='feed', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/feeds/models.py b/feeds/models.py index 8e5c929..65e324d 100644 --- a/feeds/models.py +++ b/feeds/models.py @@ -7,7 +7,7 @@ from typing import Literal from django.db import models from django.db.models import JSONField -logger = logging.getLogger(__name__) +logger: logging.Logger = logging.getLogger(__name__) class Domain(models.Model): @@ -129,6 +129,8 @@ class Feed(models.Model): feed_url = models.URLField(unique=True) + # The user that added the feed + user = models.ForeignKey("auth.User", on_delete=models.SET_NULL, null=True, blank=True) domain = models.ForeignKey(Domain, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) diff --git a/feeds/views.py b/feeds/views.py index 11a3120..dd720d8 100644 --- a/feeds/views.py +++ b/feeds/views.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from django.contrib import messages from django.contrib.auth import login @@ -33,6 +33,7 @@ class IndexView(View): "keywords": "feed, rss, atom, archive, rss list", "author": "TheLovinator", "canonical": "https://feedvault.se/", + "title": "FeedVault", } return HttpResponse(content=template.render(context=context, request=request)) @@ -56,6 +57,7 @@ class FeedView(View): "keywords": "feed, rss, atom, archive, rss list", "author": f"{feed.author_detail.name if feed.author_detail else "FeedVault"}", "canonical": f"https://feedvault.se/feed/{feed_id}/", + "title": f"{feed.title} - FeedVault", } return render(request, "feed.html", context) @@ -72,10 +74,12 @@ class FeedsView(ListView): def get_context_data(self, **kwargs) -> dict: # noqa: ANN003 """Get the context data.""" context = super().get_context_data(**kwargs) - context["description"] = "Archive of all feeds" + feed_amount: int = Feed.objects.count() or 0 + context["description"] = f"Archiving {feed_amount} feeds" context["keywords"] = "feed, rss, atom, archive, rss list" context["author"] = "TheLovinator" context["canonical"] = "https://feedvault.se/feeds/" + context["title"] = "Feeds" return context @@ -95,13 +99,19 @@ class AddView(View): def post(self, request: HttpRequest) -> HttpResponse: """Add a feed.""" + if not request.user.is_authenticated: + return HttpResponse(content="Not logged in", status=401) + + if not request.user.is_active: + return HttpResponse(content="User is not active", status=403) + urls: str | None = request.POST.get("urls", None) if not urls: return HttpResponse(content="No urls", status=400) # Split the urls by newline. for url in urls.split("\n"): - feed: None | Feed = add_feed(url) + feed: None | Feed = add_feed(url, request.user) if not feed: messages.error(request, f"{url} - Failed to add") continue @@ -132,13 +142,19 @@ class UploadView(View): def post(self, request: HttpRequest) -> HttpResponse: """Upload a file.""" + if not request.user.is_authenticated: + return HttpResponse(content="Not logged in", status=401) + + if not request.user.is_active: + return HttpResponse(content="User is not active", status=403) + file = request.FILES.get("file", None) if not file: return HttpResponse(content="No file", status=400) # Split the urls by newline. for url in file.read().decode("utf-8").split("\n"): - feed: None | Feed = add_feed(url) + feed: None | Feed = add_feed(url, request.user) if not feed: messages.error(request, f"{url} - Failed to add") continue @@ -172,6 +188,17 @@ class RegisterView(CreateView): form_class = UserCreationForm success_url = reverse_lazy("feeds:login") + # Add context data to the view + def get_context_data(self, **kwargs) -> dict: # noqa: ANN003 + """Get the context data.""" + context = super().get_context_data(**kwargs) + context["description"] = "Register a new account" + context["keywords"] = "register, account, feed, rss, atom, archive, rss list" + context["author"] = "TheLovinator" + context["canonical"] = "https://feedvault.se/accounts/register/" + context["title"] = "Register" + return context + class CustomLogoutView(LogoutView): """Logout view.""" @@ -186,6 +213,17 @@ class CustomPasswordChangeView(SuccessMessageMixin, PasswordChangeView): success_url = reverse_lazy("feeds:index") success_message = "Your password was successfully updated!" + # Add context data to the view + def get_context_data(self, **kwargs) -> dict: # noqa: ANN003 + """Get the context data.""" + context = super().get_context_data(**kwargs) + context["description"] = "Change your password" + context["keywords"] = "change, password, account, feed, rss, atom, archive, rss list" + context["author"] = "TheLovinator" + context["canonical"] = "https://feedvault.se/accounts/change-password/" + context["title"] = "Change password" + return context + class ProfileView(View): """Profile page.""" @@ -193,11 +231,16 @@ class ProfileView(View): def get(self, request: HttpRequest) -> HttpResponse: """Load the profile page.""" template = loader.get_template(template_name="accounts/profile.html") - context = { - "description": "FeedVault allows users to archive and search their favorite web feeds.", - "keywords": "feed, rss, atom, archive, rss list", - "author": "TheLovinator", - "canonical": "https://feedvault.se/", + + user_feeds = Feed.objects.filter(user=request.user).order_by("-created_at")[:100] + + context: dict[str, str | Any] = { + "description": f"Profile page for {request.user.get_username()}", + "keywords": f"profile, account, {request.user.get_username()}", + "author": f"{request.user.get_username()}", + "canonical": "https://feedvault.se/accounts/profile/", + "title": f"{request.user.get_username()}", + "user_feeds": user_feeds, } return HttpResponse(content=template.render(context=context, request=request)) @@ -213,5 +256,6 @@ class APIView(View): "keywords": "feed, rss, atom, archive, rss list", "author": "TheLovinator", "canonical": "https://feedvault.se/api/", + "title": "API Documentation", } return HttpResponse(content=template.render(context=context, request=request)) diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html index 47fa4b1..c7d3965 100644 --- a/templates/accounts/profile.html +++ b/templates/accounts/profile.html @@ -3,25 +3,10 @@

{{ user.username }}

Feeds

-

Subscriptions

- -

Subscribers

-

-

- {% csrf_token %} - -
-

{% endblock %}