Model and API rewrite changes
This commit is contained in:
parent
dfeb368be5
commit
7a78cf1215
@ -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:
|
||||
|
@ -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")
|
||||
]))
|
||||
]
|
@ -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")
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user