You will need a user to add feeds now

This commit is contained in:
Joakim Hellsén 2024-02-23 07:50:02 +01:00
commit 2058054c99
5 changed files with 94 additions and 29 deletions

View file

@ -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),

View file

@ -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),
),
]

View file

@ -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)

View file

@ -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))