Model and API rewrite changes

This commit is contained in:
Corban-Lee Jones 2024-02-08 00:33:49 +00:00
parent dfeb368be5
commit 7a78cf1215
6 changed files with 188 additions and 98 deletions

View File

@ -4,8 +4,7 @@ import logging
from rest_framework import serializers
from apps.home.models import Subscription, TrackedContent
# from apps.home.models import RSSFeed, FeedChannel
from apps.home.models import Subscription, TrackedContent, DiscordChannel
log = logging.getLogger(__name__)
@ -105,6 +104,14 @@ class DynamicModelSerializer(serializers.ModelSerializer):
abstract = True
class DiscordChannelSerializer(DynamicModelSerializer):
"""Serializer for the Discord Channel Model."""
class Meta:
model = DiscordChannel
fields = ("id", "creation_datetime")
class SubscriptionSerializer(DynamicModelSerializer):
"""Serializer for the Subscription Model."""
@ -113,12 +120,12 @@ class SubscriptionSerializer(DynamicModelSerializer):
class Meta:
model = Subscription
fields = (
"uuid", "name", "url", "image", "server",
"uuid", "name", "rss_url", "image", "server",
"channels", "creation_datetime"
)
class TrackedContent(DynamicModelSerializer):
class TrackedContentSerializer(DynamicModelSerializer):
"""Serializer for the TrackedContent Model."""
class Meta:

View File

@ -3,27 +3,32 @@
from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
from . import views
from .views import (
DiscordChannel_ListView,
DiscordChannel_DetailView,
Subscription_ListView,
Subscription_DetailView,
TrackedContent_ListView,
TrackedContent_DetailView
)
urlpatterns = [
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api-token-auth/", obtain_auth_token),
path("channel/", include([
path("", DiscordChannel_ListView.as_view(), name="discordchannel"),
path("<int:pk>/", DiscordChannel_DetailView.as_view(), name="discordchannel-detail")
])),
path("subscription/", include([
path("", views.SubscriptionList.as_view(), name="subscription"),
path("<str:pk>/", views.SubscriptionDetail.as_view(), name="subscription-detail")
path("", Subscription_ListView.as_view(), name="subscription"),
path("<str:pk>/", Subscription_DetailView.as_view(), name="subscription-detail")
])),
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")
path("", TrackedContent_ListView.as_view(), name="tracked"),
path("<str:pk>/", TrackedContent_DetailView.as_view(), name="tracked-detail")
]))
]

View File

@ -1,90 +1,123 @@
# -*- encoding: utf-8 -*-
import logging
import base64
import httpx
from django.http import HttpResponse
from django_filters import rest_framework as rest_filters
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.temp import NamedTemporaryFile
from django.db.utils import IntegrityError
from rest_framework.views import APIView
from rest_framework.response import Response
from django_filters import rest_framework as rest_filters
from rest_framework import status, permissions, filters, generics
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.parsers import MultiPartParser, FormParser
from asgiref.sync import async_to_sync
from apps.home.models import RSSFeed, FeedChannel
from .serializers import RssFeedSerializer, FeedChannelSerializer
from apps.home.models import DiscordChannel, Subscription, TrackedContent
from .serializers import DiscordChannelSerializer, SubscriptionSerializer, TrackedContentSerializer
log = logging.getLogger(__name__)
class RSSFeedPagination(PageNumberPagination):
class DefaultPagination(PageNumberPagination):
"""Default class for pagination in API views."""
page_size = 10
page_size_query_param = "page_size"
max_page_size = 25
class RSSFeedList(generics.ListAPIView, generics.CreateAPIView):
# Discord Channel Views
class DiscordChannel_ListView(generics.ListAPIView, generics.CreateAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = RSSFeedPagination
serializer_class = RssFeedSerializer
queryset = RSSFeed.objects.all().order_by("created_at")
pagination_class = DefaultPagination
serializer_class = DiscordChannelSerializer
queryset = DiscordChannel.objects.all().order_by("-creation_datetime")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["uuid", "name", "url", "discord_server_id", "created_at"]
search_fields = ["name"]
ordering_fields = ["created_at"]
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
self.perform_create(serializer)
except IntegrityError:
return Response({"detail": "RSS Feed name must be unique"}, status=status.HTTP_409_CONFLICT, exception=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
filter_backends = [rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id"]
ordering_fields = ["creation_datetime"]
class RSSFeedDetail(generics.RetrieveUpdateDestroyAPIView):
class DiscordChannel_DetailView(generics.RetrieveDestroyAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = RssFeedSerializer
queryset = RSSFeed.objects.all().order_by("-created_at")
serializer_class = DiscordChannelSerializer
queryset = DiscordChannel.objects.all().order_by("-creation_datetime")
class FeedChannelListApiView(generics.ListAPIView):
# Subscription Views
class Subscription_ListView(generics.ListAPIView, generics.CreateAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
serializer_class = FeedChannelSerializer
queryset = FeedChannel.objects.all().order_by("-created_at")
pagination_class = DefaultPagination
serializer_class = SubscriptionSerializer
queryset = Subscription.objects.all().order_by("-creation_datetime")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["uuid", "discord_channel_id", "discord_server_id", "feeds", "created_at"]
search_fields = ["feeds__name"]
ordering_fields = ["created_at"]
filterset_fields = ["uuid", "name", "rss_url", "server", "channels", "creation_datetime"]
search_fields = ["name"]
ordering_fields = ["creation_datetime"]
def post(self, request):
# def post(self, request):
# serializer = self.get_serializer(data=request.data)
# serializer.is_valid(raise_exception=True)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
# try:
# self.perform_create(serializer)
# except IntegrityError:
# return Response({"detail": "RSS Feed name must be unique"}, status=status.HTTP_409_CONFLICT, exception=True)
return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
# headers = self.get_success_headers(serializer.data)
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class Subscription_DetailView(generics.RetrieveUpdateDestroyAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = SubscriptionSerializer
queryset = Subscription.objects.all().order_by("-creation_datetime")
# Tracked Content Views
class TrackedContent_ListView(generics.ListAPIView, generics.CreateAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
serializer_class = TrackedContentSerializer
queryset = TrackedContent.objects.all().order_by("-creation_datetime")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["uuid", "subscription", "content_url", "creation_datetime"]
search_fields = ["name"]
ordering_fields = ["creation_datetime"]
class TrackedContent_DetailView(generics.RetrieveDestroyAPIView):
""""""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = TrackedContentSerializer
queryset = TrackedContent.objects.all().order_by("-creation_datetime")

View File

@ -2,14 +2,19 @@
from django.contrib import admin
from .models import RSSFeed, FeedChannel
from .models import Subscription, TrackedContent
from .models import DiscordChannel, Subscription, TrackedContent
class DiscordChannelAdmin(admin.ModelAdmin):
list_display = [
"id", "creation_datetime"
]
class SubscriptionAdmin(admin.ModelAdmin):
list_display = [
"uuid", "name", "url", "server",
"channels", "creation_datetime"
"uuid", "name", "rss_url", "server",
"creation_datetime"
]
@ -20,6 +25,7 @@ class TrackedContentAdmin(admin.ModelAdmin):
]
admin.site.register(DiscordChannel, DiscordChannelAdmin)
admin.site.register(Subscription, SubscriptionAdmin)
admin.site.register(TrackedContent, TrackedContentAdmin)

View File

@ -1,6 +1,7 @@
# Generated by Django 5.0.1 on 2024-01-30 20:45
# Generated by Django 5.0.1 on 2024-02-07 23:19
import apps.home.models
import django.db.models.deletion
import django.utils.timezone
import uuid
from django.db import migrations, models
@ -15,40 +16,45 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='FeedChannel',
name='DiscordChannel',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('discord_server_id', models.PositiveBigIntegerField(help_text='the discord server id of this item', verbose_name='discord server id')),
('discord_channel_id', models.PositiveBigIntegerField(help_text='the discord channel id of this item', verbose_name='discord channel id')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False, help_text='when this item was created', verbose_name='creation date & time')),
('id', models.PositiveBigIntegerField(editable=False, help_text='Unique identifier of the channel, provided by Discord.', primary_key=True, serialize=False, verbose_name='id')),
('creation_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, help_text='when this instance was created.', verbose_name='creation datetime')),
],
options={
'verbose_name': 'Feed Channel',
'verbose_name_plural': 'Feed Channels',
'verbose_name': 'discord channel',
'verbose_name_plural': 'discord channels',
'get_latest_by': '-creation_datetime',
},
),
migrations.CreateModel(
name='RSSFeed',
name='Subscription',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(help_text='a human readable nickname for this item', max_length=120, verbose_name='name')),
('url', models.URLField(help_text='url to the RSS feed', verbose_name='url')),
('image', models.ImageField(blank=True, help_text='image of the RSS feed', null=True, storage=apps.home.models.OverwriteStorage(), upload_to=apps.home.models.RSSFeedIconPathGenerator(), verbose_name='image')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False, help_text='when this item was created', verbose_name='creation date & time')),
('discord_server_id', models.PositiveBigIntegerField(help_text='the discord server id of this item', verbose_name='discord server id')),
('name', models.CharField(help_text='Reference name for this subscription (max %(max_length)s chars).', max_length=32, verbose_name='name')),
('rss_url', models.URLField(help_text='URL of the subscribed to RSS feed.', verbose_name='rss url')),
('image', models.ImageField(blank=True, default='../static/images/defaultuser.webp', help_text='image of the RSS feed.', null=True, storage=apps.home.models.OverwriteStorage(), upload_to=apps.home.models.Subscription_IconPathGenerator(), verbose_name='image')),
('creation_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, help_text='when this instance was created.', verbose_name='creation datetime')),
('server', models.PositiveBigIntegerField(help_text='Identifier for the discord server that owns this subscription.', verbose_name='server id')),
('channels', models.ManyToManyField(help_text='List of Discord Channels acting as targets for subscription content.', to='home.discordchannel', verbose_name='channels')),
],
options={
'verbose_name': 'RSS Feed',
'verbose_name_plural': 'RSS Feeds',
'verbose_name': 'subscription',
'verbose_name_plural': 'subscriptions',
'get_latest_by': '-creation_datetime',
},
),
migrations.AddConstraint(
model_name='rssfeed',
constraint=models.UniqueConstraint(fields=('name', 'discord_server_id'), name='unique name & server pair'),
migrations.CreateModel(
name='TrackedContent',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('content_url', models.URLField(help_text='URL of the tracked content.', verbose_name='content url')),
('creation_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, help_text='when this instance was created.', verbose_name='creation datetime')),
('subscription', models.ForeignKey(help_text='The subscription that this content originated from.', on_delete=django.db.models.deletion.CASCADE, related_name='tracked_content', to='home.subscription', verbose_name='subscription')),
],
),
migrations.AddField(
model_name='feedchannel',
name='feeds',
field=models.ManyToManyField(help_text='the feeds to include in this item', related_name='queues', to='home.rssfeed', verbose_name='feeds'),
migrations.AddConstraint(
model_name='subscription',
constraint=models.UniqueConstraint(fields=('name', 'server'), name='unique name & server pair'),
),
]

View File

@ -38,6 +38,30 @@ class Subscription_IconPathGenerator:
return os.path.join("subscriptions", str(instance.uuid), "icon.webp")
class DiscordChannel(models.Model):
"""Represents a Discord Channel."""
id = models.PositiveBigIntegerField(
verbose_name=_("id"),
help_text=_("Unique identifier of the channel, provided by Discord."),
primary_key=True
)
creation_datetime = models.DateTimeField(
verbose_name=_("creation datetime"),
help_text=_("when this instance was created."),
default=timezone.now,
editable=False
)
class Meta:
verbose_name = _("discord channel")
verbose_name_plural = _("discord channels")
get_latest_by = "-creation_datetime"
def __str__(self):
return str(self.id)
class Subscription(models.Model):
"""Stores relevant data for a user submitted RSS Feed."""
@ -76,7 +100,11 @@ class Subscription(models.Model):
verbose_name=_("server id"),
help_text=_("Identifier for the discord server that owns this subscription.")
)
channels = models.ManyToManyField(int)
channels = models.ManyToManyField(
verbose_name=_("channels"),
help_text=_("List of Discord Channels acting as targets for subscription content."),
to=DiscordChannel,
)
class Meta:
verbose_name = "subscription"
@ -84,12 +112,17 @@ class Subscription(models.Model):
get_latest_by = "-creation_datetime"
constraints = [
# Prevent servers from having subscriptions with duplicate names
models.UniqueConstraint(fields=["name", "server_id"], name="unique name & server pair")
models.UniqueConstraint(fields=["name", "server"], name="unique name & server pair")
]
def __str__(self):
return self.name
def save(self, *args, **kwargs):
new_text = "New " if self._state.adding else ""
log.debug("%sSubscription Saved %s", new_text, self.uuid)
super().save(*args, **kwargs)
class TrackedContent(models.Model):
"""Tracks content shared from an RSS Feed, to prevent duplicates."""
@ -110,7 +143,7 @@ class TrackedContent(models.Model):
verbose_name=_("content url"),
help_text=_("URL of the tracked content.")
)
creation_datetime = models.DatetimeField(
creation_datetime = models.DateTimeField(
verbose_name=_("creation datetime"),
help_text=_("when this instance was created."),
default=timezone.now,