Rewriting models
This commit is contained in:
parent
224c6d620b
commit
dfeb368be5
@ -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"
|
||||
)
|
||||
|
@ -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")
|
||||
]
|
@ -1,4 +1 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user