From 54cd70bbde7846eb60511151f9390f7932ea0020 Mon Sep 17 00:00:00 2001 From: Corban-Lee Date: Tue, 23 Jul 2024 20:54:35 +0100 Subject: [PATCH] GuildSettings model --- apps/api/serializers.py | 12 ++- apps/api/urls.py | 9 ++- apps/api/views.py | 87 +++++++++++++++++++++- apps/home/migrations/0020_guildsettings.py | 21 ++++++ apps/home/models.py | 29 +++++++- 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 apps/home/migrations/0020_guildsettings.py diff --git a/apps/api/serializers.py b/apps/api/serializers.py index fc8193a..8dff87a 100644 --- a/apps/api/serializers.py +++ b/apps/api/serializers.py @@ -5,7 +5,7 @@ from urllib.parse import unquote from rest_framework import serializers -from apps.home.models import SubChannel, Filter, Subscription, SavedGuilds, TrackedContent, ArticleMutator +from apps.home.models import SubChannel, Filter, Subscription, SavedGuilds, TrackedContent, ArticleMutator, GuildSettings log = logging.getLogger(__name__) @@ -175,6 +175,16 @@ class SavedGuildSerializer(DynamicModelSerializer): fields = ("id", "guild_id", "name", "icon", "added_by", "permissions", "default_embed_colour", "owner") +class GuildSettingsSerializer(DynamicModelSerializer): + """ + Serializer for the GuildSettings model. + """ + + class Meta: + model = GuildSettings + fields = ("id", "guild_id", "default_embed_colour") + + class TrackedContentSerializer_GET(DynamicModelSerializer): """ Serializer for the TrackedContent model. diff --git a/apps/api/urls.py b/apps/api/urls.py index 78e3ee5..eb75d92 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -16,7 +16,9 @@ from .views import ( TrackedContent_ListView, TrackedContent_DetailView, ArticleMutator_ListView, - ArticleMutator_DetailView + ArticleMutator_DetailView, + GuildSettings_ListView, + GuildSettings_DetailView ) urlpatterns = [ @@ -46,6 +48,11 @@ urlpatterns = [ path("/", SavedGuild_DetailView.as_view(), name="saved-guilds-detail") ])), + path("guild-settings/", include([ + path("", GuildSettings_ListView.as_view(), name="guild-settings"), + path("/", GuildSettings_DetailView.as_view(), name="guild-settings-detail") + ])), + path("tracked-content/", include([ path("", TrackedContent_ListView.as_view(), name="tracked-content"), path("/", TrackedContent_DetailView.as_view(), name="tracked-content-detail") diff --git a/apps/api/views.py b/apps/api/views.py index 8fc7f84..11a910c 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -11,7 +11,7 @@ from rest_framework.pagination import PageNumberPagination from rest_framework.authentication import SessionAuthentication, TokenAuthentication from rest_framework.parsers import MultiPartParser, FormParser -from apps.home.models import SubChannel, Filter, Subscription, SavedGuilds, TrackedContent, ArticleMutator +from apps.home.models import SubChannel, Filter, Subscription, SavedGuilds, TrackedContent, ArticleMutator, GuildSettings from apps.authentication.models import DiscordUser from .metadata import ExpandedMetadata from .serializers import ( @@ -22,7 +22,8 @@ from .serializers import ( SavedGuildSerializer, TrackedContentSerializer_GET, TrackedContentSerializer_POST, - ArticleMutatorSerializer + ArticleMutatorSerializer, + GuildSettingsSerializer ) log = logging.getLogger(__name__) @@ -385,6 +386,88 @@ class SavedGuild_DetailView(generics.RetrieveDestroyAPIView): return SavedGuilds.objects.filter(added_by=self.request.user) +# ================================================================================================= +# GuildSettings Views + +class GuildSettings_ListView(generics.ListCreateAPIView): + """ + View to provide a list of GuildSettings model instances. + Can also be used to create a new instance. + + Supports: GET, POST + """ + + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + + pagination_class = None + serializer_class = GuildSettingsSerializer + metadata_class = ExpandedMetadata + + filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter] + filterset_fields = ["id", "guild_id", "default_embed_colour"] + + def get_queryset(self): + if self.request.user.is_superuser: + return GuildSettings.objects.all() + + return GuildSettings.objects.filter(added_by=self.request.user) + + def post(self, request): + + guild_id = request.data["guild_id"] + saved_guilds = SavedGuild.objects.filter(added_by=request.user) + + if not saved_guilds: + return Response( + {"detail": "You must have an instance of saved guild with this guild_id"}, + status=status.HTTP_403_FORBIDDEN, + exception=False + ) + + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + try: + self.perform_create(serializer) + except IntegrityError as err: + return Response( + {"detail": str(err)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + exception=True + ) + + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + + def is_server_admin(self, permissions) -> bool: + + return (int(permissions) & 1 << 3) == 1 << 3 + + +class GuildSettings_DetailView(generics.RetrieveDestroyAPIView): + """ + View to provide details on a particular GuildSettings model instances. + + Supports: GET, DELETE + """ + + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + parser_classes = [MultiPartParser, FormParser] + + serializer_class = GuildSettingsSerializer + + def get_queryset(self): + if self.request.user.is_superuser: + return GuildSettings.objects.all() + + saved_guilds = SavedGuild.objects.filter(added_by=self.request.user) + guild_ids = [guild.guild_id for guild in saved_guilds] + + return GuildSettings.objects.filter(guild_id__in=guild_ids) + + # ================================================================================================= # TrackedContent Views diff --git a/apps/home/migrations/0020_guildsettings.py b/apps/home/migrations/0020_guildsettings.py new file mode 100644 index 0000000..beb6ae8 --- /dev/null +++ b/apps/home/migrations/0020_guildsettings.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.4 on 2024-07-23 15:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0019_savedguilds_default_embed_colour'), + ] + + operations = [ + migrations.CreateModel( + name='GuildSettings', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('guild_id', models.CharField(help_text='Discord snowflake ID for the represented guild.', max_length=128, verbose_name='guild id')), + ('default_embed_colour', models.CharField(blank=True, default='3498db', max_length=6, verbose_name='default embed colour')), + ], + ), + ] diff --git a/apps/home/models.py b/apps/home/models.py index 2df91d4..d334c3c 100644 --- a/apps/home/models.py +++ b/apps/home/models.py @@ -11,6 +11,29 @@ from django.core.validators import MaxValueValidator, MinValueValidator log = logging.getLogger(__name__) +class GuildSettings(models.Model): + """ + Represents settings for a saved Discord Guild `SavedGuild`. + These objects aren't linked through foreignkey because + `SavedGuild` is user user unique, not Discord Guild unique. + """ + + id = models.AutoField(primary_key=True) + + guild_id = models.CharField( + verbose_name=_("guild id"), + max_length=128, + help_text=_("Discord snowflake ID for the represented guild.") + ) + + default_embed_colour = models.CharField( + verbose_name=_("default embed colour"), + max_length=6, + default="3498db", + blank=True + ) + + class SavedGuilds(models.Model): """ Represents a saved Discord Guild (aka Server). @@ -18,7 +41,7 @@ class SavedGuilds(models.Model): to see associated Subscriptions. """ - id = models.AutoField(primary_key=True) + id = models.AutoField(_("ID"), primary_key=True) # Have to use charfield instead of positiveBigIntegerField due to an Sqlite # issue that rounds down the value @@ -82,6 +105,10 @@ class SavedGuilds(models.Model): def __str__(self) -> str: return self.name + @property + def settings(self): + return GuildSettings.objects.get(guild_id=self.guild_id) + class SubChannel(models.Model): """