Rewriting models

This commit is contained in:
Corban-Lee Jones 2024-02-07 21:17:14 +00:00
parent 224c6d620b
commit dfeb368be5
5 changed files with 116 additions and 108 deletions

View File

@ -2,8 +2,10 @@
import logging
from rest_framework import serializers
from apps.home.models import RSSFeed, FeedChannel
from apps.home.models import Subscription, TrackedContent
# from apps.home.models import RSSFeed, FeedChannel
log = logging.getLogger(__name__)
@ -103,21 +105,25 @@ class DynamicModelSerializer(serializers.ModelSerializer):
abstract = True
class RssFeedSerializer(DynamicModelSerializer):
class SubscriptionSerializer(DynamicModelSerializer):
"""Serializer for the Subscription Model."""
image = serializers.ImageField()
class Meta:
model = RSSFeed
model = Subscription
fields = (
"uuid", "name", "url", "image", "discord_server_id", "created_at"
"uuid", "name", "url", "image", "server",
"channels", "creation_datetime"
)
class FeedChannelSerializer(DynamicModelSerializer):
feeds = RssFeedSerializer(many=True)
class TrackedContent(DynamicModelSerializer):
"""Serializer for the TrackedContent Model."""
class Meta:
model = FeedChannel
model = TrackedContent
fields = (
"uuid", "discord_server_id", "discord_channel_id", "feeds", "created_at"
"uuid", "content_url", "subscription",
"creation_datetime"
)

View File

@ -10,9 +10,20 @@ urlpatterns = [
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api-token-auth/", obtain_auth_token),
path("rssfeed/", include([
path("", views.RSSFeedList.as_view(), name="rssfeed"),
path("<str:pk>/", views.RSSFeedDetail.as_view(), name="rssfeed-detail")
path("subscription/", include([
path("", views.SubscriptionList.as_view(), name="subscription"),
path("<str:pk>/", views.SubscriptionDetail.as_view(), name="subscription-detail")
])),
path("feedchannel/", views.FeedChannelListApiView.as_view(), name="feedchannel")
path("tracked/", include([
path("", views.TrackedContentList.as_view(), name="tracked"),
path("<str:pk>/", views.TrackedContentDetail.as_view(), name="tracked-detail"),
# path("")
])),
# path("rssfeed/", include([
# path("", views.RSSFeedList.as_view(), name="rssfeed"),
# path("<str:pk>/", views.RSSFeedDetail.as_view(), name="rssfeed-detail")
# ])),
# path("feedchannel/", views.FeedChannelListApiView.as_view(), name="feedchannel")
]

View File

@ -1,4 +1 @@
# -*- encoding: utf-8 -*-
"""
Copyright (c) 2019 - present AppSeed.us
"""

View File

@ -3,17 +3,23 @@
from django.contrib import admin
from .models import RSSFeed, FeedChannel
from .models import Subscription, TrackedContent
class RSSFeedAdmin(admin.ModelAdmin):
list_display = ["uuid", "name", "url", "discord_server_id", "created_at"]
class SubscriptionAdmin(admin.ModelAdmin):
list_display = [
"uuid", "name", "url", "server",
"channels", "creation_datetime"
]
admin.site.register(RSSFeed, RSSFeedAdmin)
class TrackedContentAdmin(admin.ModelAdmin):
list_display = [
"uuid", "content_url", "subscription",
"creation_datetime"
]
class FeedChannelAdmin(admin.ModelAdmin):
list_display = ["uuid", "discord_server_id", "discord_channel_id", "created_at"]
admin.site.register(Subscription, SubscriptionAdmin)
admin.site.register(TrackedContent, TrackedContentAdmin)
admin.site.register(FeedChannel, FeedChannelAdmin)

View File

@ -8,6 +8,8 @@ import httpx
import feedparser
from django.db import models
from django.utils import timezone
from django.dispatch import receiver
from django.db.models.signals import pre_save
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
@ -29,126 +31,112 @@ class OverwriteStorage(FileSystemStorage):
@deconstructible
class RSSFeedIconPathGenerator:
"""Icon path generator for RSS Feed."""
class Subscription_IconPathGenerator:
"""Icon path generator for Subscriptions."""
def __call__(self, instance, filename: str) -> str:
return os.path.join("rssfeed", str(instance.uuid), "icon.webp")
return os.path.join("subscriptions", str(instance.uuid), "icon.webp")
@async_to_sync
async def validate_rss_url(url: str):
log.debug("validating RSS url: %s" % url)
try:
validator = URLValidator(schemes=["https"])
validator(url)
except ValidationError as exc:
raise ValidationError("URL scheme must be 'https'") from exc
async with httpx.AsyncClient() as client:
response = await client.get(url)
try:
response.raise_for_status()
except httpx.HTTPStatusError as error:
log.error(error)
raise ValidationError(error)
content = response.text
feed = feedparser.parse(content)
if not feed.version:
log.error("not a feed")
raise ValidationError(f"{url} does not point to a valid RSS feed")
log.debug("success")
return True
class RSSFeed(models.Model):
"""Represents an RSS Feed."""
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
class Subscription(models.Model):
"""Stores relevant data for a user submitted RSS Feed."""
uuid = models.UUIDField(
primary_key=True,
default=uuid4,
editable=False
)
name = models.CharField(
verbose_name=_("name"),
help_text=_("a human readable nickname for this item"),
null=False, blank=False,
max_length=120
help_text=_("Reference name for this subscription (max %(max_length)s chars)."),
max_length=32,
null=False,
blank=False
)
url = models.URLField(
verbose_name=_("url"),
help_text=_("url to the RSS feed"),
rss_url = models.URLField(
verbose_name=_("rss url"),
help_text=_("URL of the subscribed to RSS feed.")
)
image = models.ImageField(
verbose_name=_("image"),
help_text=_("image of the RSS feed"),
upload_to=RSSFeedIconPathGenerator(),
help_text=_("image of the RSS feed."),
upload_to=Subscription_IconPathGenerator(),
storage=OverwriteStorage(),
default="../static/images/defaultuser.webp",
null=True,
blank=True
)
created_at = models.DateTimeField(
verbose_name=_("creation date & time"),
help_text=_("when this item was created"),
creation_datetime = models.DateTimeField(
verbose_name=_("creation datetime"),
help_text=_("when this instance was created."),
default=timezone.now,
editable=False
)
discord_server_id = models.PositiveBigIntegerField(
verbose_name=_("discord server id"),
help_text=_("the discord server id of this item")
server = models.PositiveBigIntegerField(
verbose_name=_("server id"),
help_text=_("Identifier for the discord server that owns this subscription.")
)
channels = models.ManyToManyField(int)
class Meta:
verbose_name = _("RSS Feed")
verbose_name_plural = _("RSS Feeds")
verbose_name = "subscription"
verbose_name_plural = "subscriptions"
get_latest_by = "-creation_datetime"
constraints = [
models.UniqueConstraint(fields=["name", "discord_server_id"], name="unique name & server pair")
# Prevent servers from having subscriptions with duplicate names
models.UniqueConstraint(fields=["name", "server_id"], name="unique name & server pair")
]
def __str__(self):
return self.name
class FeedChannel(models.Model):
"""Represents a Discord Text Channel to act as a feed."""
class TrackedContent(models.Model):
"""Tracks content shared from an RSS Feed, to prevent duplicates."""
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
discord_server_id = models.PositiveBigIntegerField(
verbose_name=_("discord server id"),
help_text=_("the discord server id of this item")
uuid = models.UUIDField(
primary_key=True,
default=uuid4,
editable=False
)
discord_channel_id = models.PositiveBigIntegerField(
verbose_name=_("discord channel id"),
help_text=_("the discord channel id of this item"),
null=False, blank=False
subscription = models.ForeignKey(
verbose_name=_("subscription"),
help_text=_("The subscription that this content originated from."),
to=Subscription,
on_delete=models.CASCADE,
related_name="tracked_content"
)
feeds = models.ManyToManyField(
to=RSSFeed,
verbose_name=_("feeds"),
help_text=_("the feeds to include in this item"),
related_name=_("queues")
content_url = models.URLField(
verbose_name=_("content url"),
help_text=_("URL of the tracked content.")
)
created_at = models.DateTimeField(
verbose_name=_("creation date & time"),
help_text=_("when this item was created"),
creation_datetime = models.DatetimeField(
verbose_name=_("creation datetime"),
help_text=_("when this instance was created."),
default=timezone.now,
editable=False
)
class Meta:
verbose_name = _("Feed Channel")
verbose_name_plural = _("Feed Channels")
def __str__(self):
return str(self.discord_channel_id)
return str(self.content_url)
@receiver(pre_save, sender=TrackedContent)
def maintain_item_cap(sender, instance, **kwargs):
""""Delete the latest tracked content, if the total under
the same subscription reaches 100."""
log.debug("checking if tracked content can be deleted.")
queryset = sender.objects.filter(subscription=instance.subscription)
total_tracked = queryset.count()
if total_tracked < 100:
log.debug("tracked content cannot be deleted, less than 100 items: %s", total_tracked)
return
oldest = queryset.earliest()
log.info("tracked content limit exceeded, deleting oldest item with uuid: %s", oldest.uuid)
oldest.delete()