replace old models with new

This commit is contained in:
Corban-Lee Jones 2024-09-24 17:28:37 +01:00
parent 8b803f5df0
commit 86074d2e13
45 changed files with 444 additions and 2537 deletions

View File

@ -2,7 +2,7 @@
from rest_framework.permissions import BasePermission
from apps.home.models import r_Server
from apps.home.models import Server
from apps.authentication.models import ServerMember

View File

@ -5,23 +5,13 @@ import logging
from rest_framework import serializers
from apps.home.models import (
SubChannel,
Filter,
Server,
ContentFilter,
MessageMutator,
MessageStyle,
Subscription,
SavedGuilds,
TrackedContent,
ArticleMutator,
GuildSettings,
UniqueContentRule,
#rewrite
r_Server,
r_ContentFilter,
r_MessageMutator,
r_MessageStyle,
r_Subscription,
r_Content,
r_UniqueContentRule
Content,
UniqueContentRule
)
log = logging.getLogger(__name__)
@ -127,149 +117,15 @@ class DynamicModelSerializer(serializers.ModelSerializer):
abstract = True
#region Sub Channel
class SubChannelSerializer(DynamicModelSerializer):
"""
Serializer for SubChannel Model.
"""
class ServerSerializer(DynamicModelSerializer):
class Meta:
model = SubChannel
fields = ("id", "channel_id", "channel_name", "subscription")
#region Filter
class FilterSerializer(DynamicModelSerializer):
"""
Serializer for the Filter Model.
"""
class Meta:
model = Filter
fields = ("id", "name", "matching_algorithm", "match", "is_insensitive", "is_whitelist", "guild_id")
#region Article Mutator
class ArticleMutatorSerializer(DynamicModelSerializer):
class Meta:
model = ArticleMutator
fields = ("id", "name", "value")
#region Unique Content Rule
class UniqueContentRuleSerializer(DynamicModelSerializer):
"""
Serializer for the UniqueContentRule model.
"""
class Meta:
model = UniqueContentRule
fields = ("id", "name", "value")
#region Subscription (GET)
class SubscriptionSerializer_GET(DynamicModelSerializer):
"""
Serializer for the Subscription Model.
"""
article_title_mutators = ArticleMutatorSerializer(many=True)
article_desc_mutators = ArticleMutatorSerializer(many=True)
unique_content_rules = UniqueContentRuleSerializer(many=True)
active = serializers.BooleanField(initial=True)
class Meta:
model = Subscription
fields = (
"id", "name", "url", "guild_id", "channels_count", "creation_datetime", "extra_notes", "filters",
"article_title_mutators", "article_desc_mutators", "article_fetch_image", "published_threshold", "embed_colour",
"unique_content_rules", "active"
)
#region Subscription (POST)
class SubscriptionSerializer_POST(DynamicModelSerializer):
"""
Serializer for the Subscription Model.
"""
class Meta:
model = Subscription
fields = (
"id", "name", "url", "guild_id", "channels_count", "creation_datetime", "extra_notes", "filters",
"article_title_mutators", "article_desc_mutators", "article_fetch_image", "published_threshold", "embed_colour",
"unique_content_rules", "active"
)
#region Saved Guild
class SavedGuildSerializer(DynamicModelSerializer):
"""
Serializer for the SavedGuild model.
"""
class Meta:
model = SavedGuilds
fields = ("id", "guild_id", "name", "icon", "added_by", "permissions", "owner")
#region Guild Setting
class GuildSettingsSerializer(DynamicModelSerializer):
"""
Serializer for the GuildSettings model.
"""
class Meta:
model = GuildSettings
fields = ("id", "guild_id", "default_embed_colour", "active")
#region Tracked Content (GET)
class TrackedContentSerializer_GET(DynamicModelSerializer):
"""
Serializer for the TrackedContent model.
"""
subscription = SubscriptionSerializer_GET()
class Meta:
model = TrackedContent
fields = ("id", "guid", "title", "url", "subscription", "channel_id", "message_id", "blocked", "creation_datetime")
#region Tracked Content (POST)
class TrackedContentSerializer_POST(DynamicModelSerializer):
"""
Serializer for the TrackedContent model.
"""
class Meta:
model = TrackedContent
fields = ("id", "guid", "title", "url", "subscription", "channel_id", "message_id", "blocked", "creation_datetime")
#region rewrite
class r_ServerSerializer(DynamicModelSerializer):
class Meta:
model = r_Server
model = Server
fields = ("id", "name", "icon_hash", "active")
class r_ContentFilterSerializer(DynamicModelSerializer):
class ContentFilterSerializer(DynamicModelSerializer):
class Meta:
model = r_ContentFilter
model = ContentFilter
fields = (
"id",
"server",
@ -281,15 +137,15 @@ class r_ContentFilterSerializer(DynamicModelSerializer):
)
class r_MessageMutatorSerializer(DynamicModelSerializer):
class MessageMutatorSerializer(DynamicModelSerializer):
class Meta:
model = r_MessageMutator
model = MessageMutator
fields = ("id", "name", "value")
class r_MessageStyleSerializer(DynamicModelSerializer):
class MessageStyleSerializer(DynamicModelSerializer):
class Meta:
model = r_MessageStyle
model = MessageStyle
fields = (
"id",
"server",
@ -304,14 +160,14 @@ class r_MessageStyleSerializer(DynamicModelSerializer):
)
class r_SubscriptionSerializer(DynamicModelSerializer):
class SubscriptionSerializer(DynamicModelSerializer):
filters = serializers.PrimaryKeyRelatedField(
queryset=r_ContentFilter.objects.all(),
queryset=ContentFilter.objects.all(),
many=True
)
class Meta:
model = r_Subscription
model = Subscription
fields = (
"id",
"server",
@ -332,7 +188,7 @@ class r_SubscriptionSerializer(DynamicModelSerializer):
# Prevent using filters from a different server
selected_filters = data.get("filters", [])
valid_filter_ids = r_ContentFilter.objects.filter(server=server).values_list("id", flat=True)
valid_filter_ids = ContentFilter.objects.filter(server=server).values_list("id", flat=True)
if any(fltr.id not in valid_filter_ids for fltr in selected_filters):
raise serializers.ValidationError(
{"filters": "All filters must belong to the specified server."}
@ -348,9 +204,9 @@ class r_SubscriptionSerializer(DynamicModelSerializer):
return data
class r_ContentSerializer(DynamicModelSerializer):
class ContentSerializer(DynamicModelSerializer):
class Meta:
model = r_Content
model = Content
fields = (
"id",
"subscription",
@ -362,7 +218,7 @@ class r_ContentSerializer(DynamicModelSerializer):
)
class r_UniqueContentRuleSerializer(DynamicModelSerializer):
class UniqueContentRuleSerializer(DynamicModelSerializer):
class Meta:
model = r_UniqueContentRule
model = UniqueContentRule
fields = ("id", "name", "value")

View File

@ -4,123 +4,61 @@ from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
from .views import (
SubChannel_ListView,
SubChannel_DetailView,
Filter_ListView,
Filter_DetailView,
Server_ListView,
Server_DetailView,
ContentFilter_ListView,
ContentFilter_DetailView,
MessageMutator_ListView,
MessageMutator_DetailView,
MessageStyle_ListView,
MessageStyle_DetailView,
Subscription_ListView,
Subscription_DetailView,
Subscription_SubChannelView,
SavedGuild_ListView,
SavedGuild_DetailView,
TrackedContent_ListView,
TrackedContent_DetailView,
ArticleMutator_ListView,
ArticleMutator_DetailView,
GuildSettings_ListView,
GuildSettings_DetailView,
Content_ListView,
Content_DetailView,
UniqueContentRule_ListView,
UniqueContentRule_DetailView,
#rewrite
r_Server_ListView,
r_Server_DetailView,
r_ContentFilter_ListView,
r_ContentFilter_DetailView,
r_MessageMutator_ListView,
r_MessageMutator_DetailView,
r_MessageStyle_ListView,
r_MessageStyle_DetailView,
r_Subscription_ListView,
r_Subscription_DetailView,
r_Content_ListView,
r_Content_DetailView,
r_UniqueContentRule_ListView,
r_UniqueContentRule_DetailView
UniqueContentRule_DetailView
)
urlpatterns = [
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api-token-auth/", obtain_auth_token),
path("subchannel/", include([
path("", SubChannel_ListView.as_view(), name="subchannel"),
path("<str:pk>/", SubChannel_DetailView.as_view(), name="subchannel-detail")
])),
path("servers/", include([
path("", Server_ListView.as_view()),
path("<int:pk>/", include([
path("", Server_DetailView.as_view()),
path("filter/", include([
path("", Filter_ListView.as_view(), name="filter"),
path("<str:pk>/", Filter_DetailView.as_view(), name="filter-detail")
])),
path("filters/", include([
path("", ContentFilter_ListView.as_view()),
path("<int:pk>/", ContentFilter_DetailView.as_view())
])),
path("subscription/", include([
path("", Subscription_ListView.as_view(), name="subscription"),
path("<str:pk>/", include([
path("", Subscription_DetailView.as_view(), name="subscription-detail"),
path("subchannels/", Subscription_SubChannelView.as_view(), name="subscription-channels")
path("message-styles/", include([
path("", MessageStyle_ListView.as_view()),
path("<int:pk>/", MessageStyle_DetailView.as_view())
])),
path("subscriptions/", include([
path("", Subscription_ListView.as_view()),
path("<int:pk>/", include([
path("", Subscription_DetailView.as_view()),
path("content/", include([
path("", Content_ListView.as_view()),
path("<int:pk>/", Content_DetailView.as_view())
]))
]))
]))
]))
])),
path("saved-guilds/", include([
path("", SavedGuild_ListView.as_view(), name="saved-guilds"),
path("<int:pk>/", SavedGuild_DetailView.as_view(), name="saved-guilds-detail")
path("message-mutators/", include([
path("", MessageMutator_ListView.as_view()),
path("<int:pk>/", MessageMutator_DetailView.as_view())
])),
path("guild-settings/", include([
path("", GuildSettings_ListView.as_view(), name="guild-settings"),
path("<int:pk>/", GuildSettings_DetailView.as_view(), name="guild-settings-detail")
])),
path("tracked-content/", include([
path("", TrackedContent_ListView.as_view(), name="tracked-content"),
path("<path:pk>/", TrackedContent_DetailView.as_view(), name="tracked-content-detail")
])),
path("article-mutator/", include([
path("", ArticleMutator_ListView.as_view(), name="article-mutator"),
path("<int:pk>/", ArticleMutator_DetailView.as_view(), name="article-mutator-detail")
])),
path("unique-content-rule/", include([
path("", UniqueContentRule_ListView.as_view(), name="unique-content-rule"),
path("<int:pk>/", UniqueContentRule_DetailView.as_view(), name="unique-content-rule-detail")
])),
#region rewrite
path("r_servers/", include([
path("", r_Server_ListView.as_view()),
path("<int:pk>/", r_Server_DetailView.as_view())
])),
path("r_content-filters/", include([
path("", r_ContentFilter_ListView.as_view()),
path("<int:pk>/", r_ContentFilter_DetailView.as_view())
])),
path("r_message-mutators/", include([
path("", r_MessageMutator_ListView.as_view()),
path("<int:pk>/", r_MessageMutator_DetailView.as_view())
])),
path("r_message-styles/", include([
path("", r_MessageStyle_ListView.as_view()),
path("<int:pk>/", r_MessageStyle_DetailView.as_view())
])),
path("r_subscriptions/", include([
path("", r_Subscription_ListView.as_view()),
path("<int:pk>/", r_Subscription_DetailView.as_view())
])),
path("r_content/", include([
path("", r_Content_ListView.as_view()),
path("<int:pk>/", r_Content_DetailView.as_view())
])),
path("r_unique-content-rules/", include([
path("", r_UniqueContentRule_ListView.as_view()),
path("<int:pk>/", r_UniqueContentRule_DetailView.as_view())
path("unique-content-rules/", include([
path("", UniqueContentRule_ListView.as_view()),
path("<int:pk>/", UniqueContentRule_DetailView.as_view())
]))
]

View File

@ -14,46 +14,24 @@ from rest_framework.authentication import SessionAuthentication, TokenAuthentica
from rest_framework.parsers import MultiPartParser, FormParser
from apps.home.models import (
SubChannel,
Filter,
Server,
ContentFilter,
MessageMutator,
MessageStyle,
Subscription,
SavedGuilds,
TrackedContent,
ArticleMutator,
GuildSettings,
UniqueContentRule,
#rewrite
r_Server,
r_ContentFilter,
r_MessageMutator,
r_MessageStyle,
r_Subscription,
r_Content,
r_UniqueContentRule
Content,
UniqueContentRule
)
from apps.authentication.models import DiscordUser, ServerMember
from .metadata import ExpandedMetadata
from .serializers import (
SubChannelSerializer,
FilterSerializer,
SubscriptionSerializer_GET,
SubscriptionSerializer_POST,
SavedGuildSerializer,
TrackedContentSerializer_GET,
TrackedContentSerializer_POST,
ArticleMutatorSerializer,
GuildSettingsSerializer,
UniqueContentRuleSerializer,
#rewrite
r_ServerSerializer,
r_ContentFilterSerializer,
r_MessageMutatorSerializer,
r_MessageStyleSerializer,
r_SubscriptionSerializer,
r_ContentSerializer,
r_UniqueContentRuleSerializer
ServerSerializer,
ContentFilterSerializer,
MessageMutatorSerializer,
MessageStyleSerializer,
SubscriptionSerializer,
ContentSerializer,
UniqueContentRuleSerializer
)
from .permissions import HasServerAccess
from .errors import NotAMemberError
@ -73,611 +51,6 @@ def is_automated_admin(user):
return user.user_type == DiscordUser.USER_TYPES.AUTOMATED_USER and user.is_superuser
# =================================================================================================
#region SubChannels
class SubChannel_ListView(generics.ListCreateAPIView):
"""
View to provide a list of SubChannel model instances.
Can also be used to create a new instance.
Supports: GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
serializer_class = SubChannelSerializer
queryset = SubChannel.objects.all().order_by("id")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "channel_id", "channel_name", "subscription"]
search_fields = ["channel_name"]
def get_queryset(self):
if self.request.user.is_superuser:
return SubChannel.objects.all()
saved_guilds = SavedGuilds.objects.filter(added_by=self.request.user)
guild_ids = [guild.guild_id for guild in saved_guilds]
return SubChannel.objects.filter(subscription__guild_id__in=guild_ids)
def post(self, request):
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)
class SubChannel_DetailView(generics.RetrieveUpdateDestroyAPIView):
"""
View to provide details on a particular SubChannel model instances.
Supports: GET, PUT, PATCH, DELETE
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = SubChannelSerializer
queryset = SubChannel.objects.all().order_by("id")
def get_queryset(self):
if self.request.user.is_superuser:
return SubChannel.objects.all()
saved_guilds = SavedGuilds.objects.filter(added_by=self.request.user)
guild_ids = [guild.guild_id for guild in saved_guilds]
return SubChannel.objects.filter(subscription__guild_id__in=guild_ids)
#region Unique Content Rule
class UniqueContentRule_ListView(generics.ListAPIView):
"""
View to provide a list of UniqueContentRule model instances.
Can also be used to create a new instance.
Supports GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
serializer_class = UniqueContentRuleSerializer
metadata_class = ExpandedMetadata
queryset = UniqueContentRule.objects.all().order_by("id")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "name", "value"]
class UniqueContentRule_DetailView(generics.RetrieveAPIView):
"""
View to provide details on a particular UniqueContentRule model instances.
Supports: GET
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = UniqueContentRuleSerializer
queryset = UniqueContentRule.objects.all().order_by("id")
# =================================================================================================
#region Filter
class Filter_ListView(generics.ListCreateAPIView):
"""
View to provide a list of Filter model instances.
Can also be used to create a new instance.
Supports: GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
serializer_class = FilterSerializer
metadata_class = ExpandedMetadata
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "name", "matching_algorithm", "match", "is_insensitive", "is_whitelist", "guild_id"]
search_fields = ["name", "match"]
def get_queryset(self):
if self.request.user.is_superuser:
return Filter.objects.all().order_by("id")
saved_guild_ids = SavedGuilds.objects \
.filter(added_by=self.request.user.id) \
.values("guild_id")
return Filter.objects \
.filter(guild_id__in=Subquery(saved_guild_ids)) \
.order_by("id")
def post(self, request):
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)
class Filter_DetailView(generics.RetrieveUpdateDestroyAPIView):
"""
View to provide details on a particular Filter model instances.
Supports: GET, PUT, PATCH, DELETE
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = FilterSerializer
def get_queryset(self):
if self.request.user.is_superuser:
return Filter.objects.all().order_by("id")
saved_guild_ids = SavedGuilds.objects \
.filter(added_by=self.request.user.id) \
.values("guild_id")
return Filter.objects \
.filter(guild_id__in=Subquery(saved_guild_ids)) \
.order_by("id")
# =================================================================================================
#region Subscription
class Subscription_ListView(generics.ListCreateAPIView):
"""
View to provide a list of Subscription model instances.
Can also be used to create a new instance.
Supports: GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
metadata_class = ExpandedMetadata
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = [
"id", "name", "url", "guild_id", "creation_datetime", "extra_notes", "filters", "unique_content_rules",
"article_title_mutators", "article_desc_mutators", "embed_colour", "published_threshold", "active"
]
search_fields = ["name", "url", "extra_notes"]
ordering_fields = ["name", "creation_datetime", "active"]
read_serializer_class = SubscriptionSerializer_GET
write_serializer_class = SubscriptionSerializer_POST
def get_serializer_class(self):
if self.request.method == "POST":
return self.write_serializer_class
return self.read_serializer_class
def get_queryset(self):
if self.request.user.is_superuser:
return Subscription.objects.all().order_by("-creation_datetime")
saved_guild_ids = SavedGuilds.objects \
.filter(added_by=self.request.user.id) \
.values("guild_id")
return Subscription.objects \
.filter(guild_id__in=Subquery(saved_guild_ids)) \
.order_by("-creation_datetime")
def post(self, request):
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)
class Subscription_DetailView(generics.RetrieveUpdateDestroyAPIView):
"""
View to provide details on a particular Subscription model instances.
Supports: GET, PUT, PATCH, DELETE
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = SubscriptionSerializer_POST
def get_queryset(self):
if self.request.user.is_superuser:
return Subscription.objects.all().order_by("-creation_datetime")
saved_guild_ids = SavedGuilds.objects \
.filter(added_by=self.request.user.id) \
.values("guild_id")
return Subscription.objects \
.filter(guild_id__in=Subquery(saved_guild_ids)) \
.order_by("-creation_datetime")
class Subscription_SubChannelView(generics.DestroyAPIView):
"""
View to erase all subscription channels quickly.
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
def delete(self, *args, **kwargs):
if self.request.user.is_superuser:
subscriptions = Subscription.objects.all()
else:
saved_guild_ids = SavedGuilds.objects \
.filter(added_by=self.request.user.id) \
.values("guild_id")
subscriptions = Subscription.objects \
.filter(guild_id__in=Subquery(saved_guild_ids))
if not subscriptions:
return Response(
{"detail": "You are forbidden from viewing this subscription"},
status=status.HTTP_403_FORBIDDEN
)
subscription = subscriptions.filter(id=kwargs["pk"]).first()
if not subscription:
return Response(
{"detail": "not found"},
status=status.HTTP_404_NOT_FOUND
)
channels = SubChannel.objects.filter(subscription=subscription)
channels.delete();
return Response(status=status.HTTP_204_NO_CONTENT)
# =================================================================================================
#region Saved Guilds
class SavedGuild_ListView(generics.ListCreateAPIView):
"""
View to provide a list of SavedGuild 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 = SavedGuildSerializer
metadata_class = ExpandedMetadata
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "guild_id", "name", "icon", "added_by", "permissions", "owner"]
search_fields = ["name"]
def get_queryset(self):
if self.request.user.is_superuser:
return SavedGuilds.objects.all()
return SavedGuilds.objects.filter(added_by=self.request.user)
def post(self, request):
# TODO:
# the data used for admin/owner verification is provided
# from the client, this is a potential attack vector, and
# should be rewritten.
is_owner = request.data["owner"].lower() == "true"
# Check user is admin in server
if not (self.is_server_admin(request.data["permissions"]) or is_owner):
return Response(
{"detail": "You must be a server administrator"},
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 SavedGuild_DetailView(generics.RetrieveDestroyAPIView):
"""
View to provide details on a particular SavedGuild model instances.
Supports: GET, DELETE
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = SavedGuildSerializer
def get_queryset(self):
if self.request.user.is_superuser:
return SavedGuilds.objects.all()
return SavedGuilds.objects.filter(added_by=self.request.user)
# =================================================================================================
#region Guild Settings
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 = DefaultPagination
serializer_class = GuildSettingsSerializer
metadata_class = ExpandedMetadata
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "guild_id", "default_embed_colour", "active"]
def get_queryset(self):
if self.request.user.is_superuser:
return GuildSettings.objects.all()
saved_guilds = SavedGuilds.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)
def post(self, request):
saved_guilds = SavedGuilds.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.RetrieveUpdateDestroyAPIView):
"""
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 = SavedGuilds.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)
# =================================================================================================
#region Tracked Content
class TrackedContent_ListView(generics.ListCreateAPIView):
"""
View to provide a list of TrackedContent model instances.
Can also be used to create a new instance.
Supports: GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = DefaultPagination
metadata_class = ExpandedMetadata
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["guid", "title", "url", "subscription", "subscription__guild_id", "channel_id", "blocked", "creation_datetime"]
search_fields = ["guid", "title", "url", "subscription__name", "subscription__url"]
ordering_fields = ["title", "subscription", "creation_datetime", "blocked"]
read_serializer_class = TrackedContentSerializer_GET
write_serializer_class = TrackedContentSerializer_POST
def get_serializer_class(self):
if self.request.method == "POST":
return self.write_serializer_class
return self.read_serializer_class
def get_queryset(self):
if self.request.user.is_superuser:
return TrackedContent.objects.all().order_by("-creation_datetime")
saved_guilds = SavedGuilds.objects.filter(added_by=self.request.user)
guild_ids = [guild.guild_id for guild in saved_guilds]
return TrackedContent.objects.filter(subscription__guild_id__in=guild_ids).order_by("-creation_datetime")
def post(self, request):
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)
class TrackedContent_DetailView(generics.RetrieveUpdateDestroyAPIView):
"""
View to provide details on a particular TrackedContent model instances.
Supports: GET, PUT, PATCH, DELETE
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = TrackedContentSerializer_POST
def get_queryset(self):
if self.request.user.is_superuser:
return TrackedContent.objects.all()
saved_guilds = SavedGuilds.objects.filter(added_by=self.request.user)
guild_ids = [guild.guild_id for guild in saved_guilds]
return TrackedContent.objects.filter(subscription__guild_id__in=guild_ids)
# =================================================================================================
#region Article Mutator
class ArticleMutator_ListView(generics.ListAPIView):
"""
View to provide a list of ArticleMutator model instances.
Can also be used to create a new instance.
Supports: GET, POST
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
metadata_class = ExpandedMetadata
queryset = ArticleMutator.objects.all().order_by("id")
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["id", "name", "value"]
serializer_class = ArticleMutatorSerializer
class ArticleMutator_DetailView(generics.RetrieveAPIView):
"""
View to provide details on a particular ArticleMutator model instances.
Supports: GET
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
serializer_class = ArticleMutatorSerializer
queryset = ArticleMutator.objects.all().order_by("id")
#region rewrite
class ListView(generics.ListAPIView):
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
@ -712,120 +85,122 @@ class DeletableDetailView(generics.RetrieveDestroyAPIView):
parser_classes = [MultiPartParser, FormParser]
class r_Server_ListView(ListView):
class Server_ListView(ListView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_ServerSerializer
serializer_class = ServerSerializer
def get_queryset(self):
return r_Server.objects.all()
servers = ServerMember.objects.filter(user=self.request.user).values_list("server", flat=True)
return Server.objects.filter(id__in=servers)
class r_Server_DetailView(DetailView):
serializer_class = r_ServerSerializer
class Server_DetailView(DetailView):
serializer_class = ServerSerializer
def get_queryset(self):
return r_Server.objects.all()
servers = ServerMember.objects.filter(user=self.request.user).values_list("server", flat=True)
return Server.objects.filter(id__in=servers)
class r_ContentFilter_ListView(ListCreateView):
class ContentFilter_ListView(ListCreateView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_ContentFilterSerializer
serializer_class = ContentFilterSerializer
def get_queryset(self):
return r_ContentFilter.objects.all()
return ContentFilter.objects.all()
class r_ContentFilter_DetailView(ChangableDetailView):
serializer_class = r_ContentFilterSerializer
class ContentFilter_DetailView(ChangableDetailView):
serializer_class = ContentFilterSerializer
def get_queryset(self):
return r_ContentFilter.objects.all()
return ContentFilter.objects.all()
class r_MessageMutator_ListView(ListView): # instances of this one are pre-defined ONLY
class MessageMutator_ListView(ListView): # instances of this one are pre-defined ONLY
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_MessageMutatorSerializer
serializer_class = MessageMutatorSerializer
def get_queryset(self):
return r_MessageMutator.objects.all()
return MessageMutator.objects.all()
class r_MessageMutator_DetailView(DetailView):
serializer_class = r_MessageMutatorSerializer
class MessageMutator_DetailView(DetailView):
serializer_class = MessageMutatorSerializer
def get_queryset(self):
return r_MessageMutator.objects.all()
return MessageMutator.objects.all()
class r_MessageStyle_ListView(ListCreateView):
class MessageStyle_ListView(ListCreateView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_MessageStyleSerializer
serializer_class = MessageStyleSerializer
def get_queryset(self):
return r_MessageStyle.objects.all()
return MessageStyle.objects.all()
class r_MessageStyle_DetailView(ChangableDetailView):
serializer_class = r_MessageStyleSerializer
class MessageStyle_DetailView(ChangableDetailView):
serializer_class = MessageStyleSerializer
def get_queryset(self):
return r_MessageStyle.objects.all()
return MessageStyle.objects.all()
class r_Subscription_ListView(ListCreateView):
class Subscription_ListView(ListCreateView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_SubscriptionSerializer
serializer_class = SubscriptionSerializer
def get_queryset(self):
return r_Subscription.objects.all()
return Subscription.objects.all()
class r_Subscription_DetailView(ChangableDetailView):
serializer_class = r_SubscriptionSerializer
class Subscription_DetailView(ChangableDetailView):
serializer_class = SubscriptionSerializer
def get_queryset(self):
return r_Subscription.objects.all()
return Subscription.objects.all()
class r_Content_ListView(ListCreateView):
class Content_ListView(ListCreateView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_ContentSerializer
serializer_class = ContentSerializer
def get_queryset(self):
return r_Content.objects.all()
return Content.objects.all()
class r_Content_DetailView(ChangableDetailView):
serializer_class = r_ContentSerializer
class Content_DetailView(ChangableDetailView):
serializer_class = ContentSerializer
def get_queryset(self):
return r_Content.objects.all()
return Content.objects.all()
class r_UniqueContentRule_ListView(ListCreateView):
class UniqueContentRule_ListView(ListCreateView):
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_UniqueContentRuleSerializer
serializer_class = UniqueContentRuleSerializer
def get_queryset(self):
return r_UniqueContentRule.objects.all()
return UniqueContentRule.objects.all()
class r_UniqueContentRule_DetailView(ChangableDetailView):
serializer_class = r_UniqueContentRuleSerializer
class UniqueContentRule_DetailView(ChangableDetailView):
serializer_class = UniqueContentRuleSerializer
def get_queryset(self):
return r_UniqueContentRule.objects.all()
return UniqueContentRule.objects.all()

View File

@ -1,6 +1,8 @@
# Generated by Django 5.0.4 on 2024-05-03 13:14
# Generated by Django 5.0.4 on 2024-09-24 14:05
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
@ -10,6 +12,7 @@ class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('home', '0001_initial'),
]
operations = [
@ -26,6 +29,8 @@ class Migration(migrations.Migration):
('mfa_enabled', models.BooleanField(help_text='whether the user has two factor enabled on their account', verbose_name='mfa enabled')),
('last_login', models.DateTimeField(default=django.utils.timezone.now, help_text='datetime of the previous login', verbose_name='last login')),
('access_token', models.CharField(help_text='token for the application to make api calls on behalf of the user.', max_length=100, verbose_name='access token')),
('token_expires', models.DateTimeField(help_text='when to request a new access token.', verbose_name='token expires')),
('refresh_token', models.CharField(help_text='token for the application to request a new access token.', max_length=100, verbose_name='refresh token')),
('user_type', models.CharField(choices=[('D', 'discord'), ('A', 'automated')], default='D', help_text='What type of user is this?', max_length=32, verbose_name='user type')),
('is_active', models.BooleanField(default=True, help_text='Use as a "soft delete" rather than deleting the user.', verbose_name='active status')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
@ -37,4 +42,14 @@ class Migration(migrations.Migration):
'abstract': False,
},
),
migrations.CreateModel(
name='ServerMember',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('permissions', models.CharField(max_length=32)),
('is_owner', models.BooleanField(default=False)),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.server')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.4 on 2024-05-31 22:41
from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
('authentication', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='discorduser',
name='token_expires',
field=models.DateTimeField(default=timezone.now, help_text='when to request a new access token.', verbose_name='token expires'),
preserve_default=False,
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-01 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0002_discorduser_token_expires'),
]
operations = [
migrations.AddField(
model_name='discorduser',
name='refresh_token',
field=models.CharField(default='1', help_text='token for the application to request a new access token.', max_length=100, verbose_name='refresh token'),
preserve_default=False,
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-19 20:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0003_discorduser_refresh_token'),
('home', '0026_temp_testonly_migration'),
]
operations = [
migrations.CreateModel(
name='ServerMember',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('nick', models.CharField(max_length=32)),
('permissions', models.CharField(max_length=32)),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-23 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0004_servermember'),
]
operations = [
migrations.AlterField(
model_name='servermember',
name='nick',
field=models.CharField(blank=True, max_length=32, null=True),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-23 20:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0005_alter_servermember_nick'),
]
operations = [
migrations.RemoveField(
model_name='servermember',
name='nick',
),
migrations.AddField(
model_name='servermember',
name='is_owner',
field=models.BooleanField(default=False),
),
]

View File

@ -168,7 +168,7 @@ class ServerMember(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(to=DiscordUser, on_delete=models.CASCADE)
server = models.ForeignKey(to="home.r_Server", on_delete=models.CASCADE)
server = models.ForeignKey(to="home.Server", on_delete=models.CASCADE)
permissions = models.CharField(max_length=32)
is_owner = models.BooleanField(default=False)

View File

@ -3,83 +3,23 @@
from django.contrib import admin
from .models import (
Server,
ContentFilter,
MessageMutator,
MessageStyle,
Subscription,
SavedGuilds,
Filter,
SubChannel,
TrackedContent,
ArticleMutator,
GuildSettings,
# temp rewrite
r_Server,
r_ContentFilter,
r_MessageMutator,
r_MessageStyle,
r_Subscription,
r_Content,
r_UniqueContentRule
Content,
UniqueContentRule
)
@admin.register(Subscription)
class SubscriptionAdmin(admin.ModelAdmin):
list_display = [
"id", "name", "url", "guild_id",
"creation_datetime", "active"
]
@admin.register(SubChannel)
class SubChannelAdmin(admin.ModelAdmin):
list_display = [
"id", "channel_id", "subscription"
]
@admin.register(Filter)
class FilterAdmin(admin.ModelAdmin):
list_display = [
"id", "name", "guild_id"
]
@admin.register(TrackedContent)
class TrackedContentAdmin(admin.ModelAdmin):
list_display = [
"guid", "title", "url", "subscription", "blocked", "creation_datetime"
]
@admin.register(SavedGuilds)
class SavedGuildAdmin(admin.ModelAdmin):
list_display = [
"id", "name", "icon"
]
@admin.register(ArticleMutator)
class ArticleMutatorAdmin(admin.ModelAdmin):
list_display = [
"id", "name", "value"
]
@admin.register(GuildSettings)
class GuildSettingsAdmin(admin.ModelAdmin):
list_display = [
"id", "guild_id", "default_embed_colour", "active"
]
#region rewrite
@admin.register(r_Server)
class r_ServerAdmin(admin.ModelAdmin):
@admin.register(Server)
class ServerAdmin(admin.ModelAdmin):
list_display = ["id", "name", "icon_hash", "active"]
@admin.register(r_ContentFilter)
class r_ContentFilterAdmin(admin.ModelAdmin):
@admin.register(ContentFilter)
class ContentFilterAdmin(admin.ModelAdmin):
list_display = [
"id",
"server",
@ -91,8 +31,8 @@ class r_ContentFilterAdmin(admin.ModelAdmin):
]
@admin.register(r_MessageMutator)
class r_MessageMutatorAdmin(admin.ModelAdmin):
@admin.register(MessageMutator)
class MessageMutatorAdmin(admin.ModelAdmin):
list_display = [
"id",
"name",
@ -100,8 +40,8 @@ class r_MessageMutatorAdmin(admin.ModelAdmin):
]
@admin.register(r_MessageStyle)
class r_MessageStyleAdmin(admin.ModelAdmin):
@admin.register(MessageStyle)
class MessageStyleAdmin(admin.ModelAdmin):
list_display = [
"id",
"server",
@ -116,8 +56,8 @@ class r_MessageStyleAdmin(admin.ModelAdmin):
]
@admin.register(r_Subscription)
class r_Subscription(admin.ModelAdmin):
@admin.register(Subscription)
class Subscription(admin.ModelAdmin):
list_display = [
"id",
"server",
@ -131,8 +71,8 @@ class r_Subscription(admin.ModelAdmin):
]
@admin.register(r_Content)
class r_ContentAdmin(admin.ModelAdmin):
@admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
list_display = [
"id",
"subscription",
@ -144,8 +84,8 @@ class r_ContentAdmin(admin.ModelAdmin):
]
@admin.register(r_UniqueContentRule)
class r_UniqueContentRule(admin.ModelAdmin):
@admin.register(UniqueContentRule)
class UniqueContentRule(admin.ModelAdmin):
list_display = [
"id",
"name",

View File

@ -1,8 +1,7 @@
# Generated by Django 5.0.4 on 2024-06-17 01:11
# Generated by Django 5.0.4 on 2024-09-24 14:05
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
@ -11,53 +10,70 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Filter',
name='MessageMutator',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
migrations.CreateModel(
name='Server',
fields=[
('id', models.PositiveIntegerField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=128)),
('icon_hash', models.CharField(blank=True, max_length=128, null=True)),
('active', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='UniqueContentRule',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
migrations.CreateModel(
name='MessageStyle',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('is_embed', models.BooleanField(default=True)),
('is_hyperlinked', models.BooleanField()),
('show_author', models.BooleanField()),
('show_timestamp', models.BooleanField()),
('show_images', models.BooleanField()),
('fetch_images', models.BooleanField()),
('description_mutator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='desc_mutated_messagestyle', to='home.messagemutator')),
('title_mutator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='title_mutated_messagestyle', to='home.messagemutator')),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.server')),
],
),
migrations.CreateModel(
name='ContentFilter',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=32)),
('keywords', models.CharField(blank=True, max_length=128, null=True)),
('regex', models.CharField(blank=True, max_length=128, null=True)),
('whitelist', models.BooleanField(default=False)),
('guild_id', models.CharField(max_length=128)),
('match', models.CharField(max_length=256)),
('matching_algorithm', models.PositiveIntegerField(choices=[(0, 'None'), (1, 'Any: Item contains any of these words (space separated)'), (2, 'All: Item contains all of these words (space separated)'), (3, 'Exact: Item contains this string'), (4, 'Regular expression: Item matches this regex'), (5, 'Fuzzy: Item contains a word similar to this word')])),
('is_insensitive', models.BooleanField()),
('is_whitelist', models.BooleanField()),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.server')),
],
options={
'verbose_name': 'filter',
'verbose_name_plural': 'filters',
'get_latest_by': 'id',
},
),
migrations.CreateModel(
name='SavedGuilds',
name='BotLogicLogs',
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')),
('name', models.CharField(help_text='Name of the represented guild.', max_length=128)),
('icon', models.CharField(help_text="Hash for the represented guild's icon.", max_length=128)),
('permissions', models.CharField(help_text='Guild permissions for the user who added this instance.', max_length=64)),
('owner', models.BooleanField(default=False, help_text="Does the 'added by' user own this guild?")),
('level', models.CharField(max_length=32)),
('message', models.CharField(max_length=256)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.server')),
],
options={
'verbose_name': 'saved guild',
'verbose_name_plural': 'saved guilds',
'get_latest_by': '-creation_datetime',
},
),
migrations.CreateModel(
name='SubChannel',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('channel_id', models.CharField(help_text='Discord snowflake ID for the represented Channel.', max_length=128, verbose_name='channel id')),
],
options={
'verbose_name': 'SubChannel',
'verbose_name_plural': 'SubChannels',
'get_latest_by': 'id',
},
),
migrations.CreateModel(
name='Subscription',
@ -65,76 +81,26 @@ class Migration(migrations.Migration):
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=32)),
('url', models.URLField()),
('guild_id', models.CharField(max_length=128)),
('creation_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('extra_notes', models.CharField(blank=True, max_length=250, null=True)),
('uwuify', models.BooleanField(default=False)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
('extra_notes', models.CharField(blank=True, default='', max_length=250)),
('active', models.BooleanField(default=True)),
('filters', models.ManyToManyField(blank=True, to='home.contentfilter')),
('message_style', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.messagestyle')),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.server')),
('unique_rules', models.ManyToManyField(to='home.uniquecontentrule')),
],
options={
'verbose_name': 'subscription',
'verbose_name_plural': 'subscriptions',
'get_latest_by': '-creation_datetime',
},
),
migrations.CreateModel(
name='TrackedContent',
name='Content',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('guid', models.CharField(max_length=128)),
('title', models.CharField(max_length=128)),
('url', models.URLField(unique=True)),
('blocked', models.BooleanField(default=False)),
('creation_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('guild_id', models.CharField(max_length=128)),
('item_id', models.CharField(max_length=1024)),
('item_guid', models.CharField(max_length=1024)),
('item_url', models.CharField(max_length=1024)),
('item_title', models.CharField(max_length=1024)),
('item_content_hash', models.CharField(max_length=1024)),
('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.subscription')),
],
options={
'verbose_name': 'tracked contents',
'get_latest_by': '-creation_datetime',
},
),
migrations.AddConstraint(
model_name='filter',
constraint=models.UniqueConstraint(fields=('name', 'guild_id'), name='unique name & guild id pair'),
),
migrations.AddField(
model_name='savedguilds',
name='added_by',
field=models.ForeignKey(help_text='The user who added created this instance.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='added by'),
),
migrations.AddField(
model_name='subscription',
name='filters',
field=models.ManyToManyField(blank=True, to='home.filter'),
),
migrations.AddField(
model_name='subchannel',
name='subscription',
field=models.ForeignKey(help_text='The linked Subscription, must be unique.', on_delete=django.db.models.deletion.CASCADE, to='home.subscription'),
),
migrations.AddField(
model_name='trackedcontent',
name='subscription',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.subscription'),
),
migrations.AddConstraint(
model_name='savedguilds',
constraint=models.UniqueConstraint(fields=('added_by', 'guild_id'), name='unique added_by & guild_id pair'),
),
migrations.AddConstraint(
model_name='subscription',
constraint=models.UniqueConstraint(fields=('name', 'guild_id'), name='unique name & server pair'),
),
migrations.AddConstraint(
model_name='subchannel',
constraint=models.UniqueConstraint(fields=('channel_id', 'subscription'), name='unique channel id and subscription pair'),
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('guid', 'guild_id'), name='unique guid & guild_id pair'),
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('url', 'guild_id'), name='unique url & guild_id pair'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-17 01:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ArticleMutator',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 5.0.4 on 2024-09-24 14:06
# This migration was manually configured to create predefined data by corbz
from django.db import migrations
def add_mutators(apps, schema_editor):
model = apps.get_model("home", "MessageMutator")
model.objects.create(name="Uwuify", value="uwuify")
model.objects.create(name="Uwuify (NSFW)", value="uwuify_nsfw")
model.objects.create(name="Gothic Script", value="gothic")
model.objects.create(name="Emoji Substitute", value="emj_substitute")
model.objects.create(name="Zalgo", value="zalgo")
model.objects.create(name="Morse Code", value="morse_code")
model.objects.create(name="Binary", value="to_bin")
model.objects.create(name="Hexadecimal", value="to_hex")
model.objects.create(name="Remove Vowels", value="rm_vowels")
model.objects.create(name="Double Characters", value="dbl_chars")
model.objects.create(name="Small Case", value="small_caps")
model.objects.create(name="L33t Sp34k", value="leet_speak")
model.objects.create(name="Pig Latin", value="pig_latin")
model.objects.create(name="Upside Down", value="flipped")
model.objects.create(name="All Reversed", value="reverse_text")
model.objects.create(name="Reversed Words", value="backwards_words")
model.objects.create(name="Shuffle Words", value="shuffle_words")
model.objects.create(name="Random Case", value="rnd_case")
model.objects.create(name="Gibberish", value="gibberish")
model.objects.create(name="Shakespearean", value="shakespear")
def add_rules(apps, schema_editor):
model = apps.get_model("home", "UniqueContentRule")
model.objects.create(name="GUID", value="GUID")
model.objects.create(name="ID", value="ID")
model.objects.create(name="URL", value="URL")
model.objects.create(name="Title", value="TITLE")
model.objects.create(name="Content Hash", value="CONTENT")
class Migration(migrations.Migration):
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.RunPython(add_mutators),
migrations.RunPython(add_rules)
]

View File

@ -1,37 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-17 01:23
from django.db import migrations
def add_mutators(apps, schema_editor):
ArticleMutator = apps.get_model("home", "ArticleMutator")
ArticleMutator.objects.create(name="Uwuify", value="uwuify")
ArticleMutator.objects.create(name="Uwuify (NSFW)", value="uwuify_nsfw")
ArticleMutator.objects.create(name="Gothic Script", value="gothic")
ArticleMutator.objects.create(name="Emoji Substitute", value="emj_substitute")
ArticleMutator.objects.create(name="Zalgo", value="zalgo")
ArticleMutator.objects.create(name="Morse Code", value="morse_code")
ArticleMutator.objects.create(name="Binary", value="to_bin")
ArticleMutator.objects.create(name="Hexadecimal", value="to_hex")
ArticleMutator.objects.create(name="Remove Vowels", value="rm_vowels")
ArticleMutator.objects.create(name="Double Characters", value="dbl_chars")
ArticleMutator.objects.create(name="Small Case", value="small_caps")
ArticleMutator.objects.create(name="L33t Sp34k", value="leet_speak")
ArticleMutator.objects.create(name="Pig Latin", value="pig_latin")
ArticleMutator.objects.create(name="Upside Down", value="flipped")
ArticleMutator.objects.create(name="All Reversed", value="reverse_text")
ArticleMutator.objects.create(name="Reversed Words", value="backwards_words")
ArticleMutator.objects.create(name="Shuffle Words", value="shuffle_words")
ArticleMutator.objects.create(name="Random Case", value="rnd_case")
ArticleMutator.objects.create(name="Gibberish", value="gibberish")
ArticleMutator.objects.create(name="Shakespearean", value="shakespear")
class Migration(migrations.Migration):
dependencies = [
('home', '0002_articlemutator'),
]
operations = [
migrations.RunPython(add_mutators)
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-17 01:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('home', '0003_initial_mutator_data'),
]
operations = [
migrations.RemoveField(
model_name='subscription',
name='uwuify',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-17 01:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0004_remove_subscription_uwuify'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='mutators',
field=models.ManyToManyField(blank=True, to='home.articlemutator'),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-18 11:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0005_subscription_mutators'),
]
operations = [
migrations.AlterField(
model_name='trackedcontent',
name='guid',
field=models.CharField(max_length=256),
),
migrations.AlterField(
model_name='trackedcontent',
name='title',
field=models.CharField(max_length=728),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-25 08:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0006_alter_trackedcontent_guid_alter_trackedcontent_title'),
]
operations = [
migrations.RemoveField(
model_name='subscription',
name='mutators',
),
migrations.AddField(
model_name='subscription',
name='article_desc_mutators',
field=models.ManyToManyField(blank=True, related_name='desc_mutated_subscriptions', to='home.articlemutator'),
),
migrations.AddField(
model_name='subscription',
name='article_title_mutators',
field=models.ManyToManyField(blank=True, related_name='title_mutated_subscriptions', to='home.articlemutator'),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-25 09:41
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0007_remove_subscription_mutators_and_more'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='article_fetch_limit',
field=models.PositiveSmallIntegerField(default=10, validators=[django.core.validators.MaxValueValidator(1), django.core.validators.MinValueValidator(10)]),
),
migrations.AddField(
model_name='subscription',
name='reset_article_fetch_limit',
field=models.BooleanField(default=False),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-06-26 13:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0008_subscription_article_fetch_limit_and_more'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='embed_colour',
field=models.CharField(default='3498db', max_length=6),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-02 11:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0009_subscription_embed_colour'),
]
operations = [
migrations.AlterField(
model_name='subscription',
name='embed_colour',
field=models.CharField(blank=True, default='3498db', max_length=6),
),
migrations.AlterField(
model_name='trackedcontent',
name='url',
field=models.URLField(),
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-02 12:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0010_alter_subscription_embed_colour_and_more'),
]
operations = [
migrations.RemoveConstraint(
model_name='trackedcontent',
name='unique guid & guild_id pair',
),
migrations.RemoveConstraint(
model_name='trackedcontent',
name='unique url & guild_id pair',
),
migrations.RenameField(
model_name='trackedcontent',
old_name='guild_id',
new_name='channel_id',
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('guid', 'channel_id'), name='unique guid & guild_id pair'),
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('url', 'channel_id'), name='unique url & guild_id pair'),
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-05 13:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0011_remove_trackedcontent_unique_guid_guild_id_pair_and_more'),
]
operations = [
migrations.RemoveConstraint(
model_name='trackedcontent',
name='unique guid & guild_id pair',
),
migrations.RemoveConstraint(
model_name='trackedcontent',
name='unique url & guild_id pair',
),
migrations.AddField(
model_name='subscription',
name='article_fetch_image',
field=models.BooleanField(default=True, help_text='Will the resulting article have an image?'),
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('guid', 'channel_id'), name='unique guid & channel_id pair'),
),
migrations.AddConstraint(
model_name='trackedcontent',
constraint=models.UniqueConstraint(fields=('url', 'channel_id'), name='unique url & channel_id pair'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-10 09:08
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0012_remove_trackedcontent_unique_guid_guild_id_pair_and_more'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='published_theshold',
field=models.DateField(blank=True, default=django.utils.timezone.now),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-10 09:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('home', '0013_subscription_published_theshold'),
]
operations = [
migrations.RenameField(
model_name='subscription',
old_name='published_theshold',
new_name='published_threshold',
),
]

View File

@ -1,67 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-10 13:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0014_rename_published_theshold_subscription_published_threshold'),
]
operations = [
migrations.AlterModelOptions(
name='filter',
options={'ordering': ('name',)},
),
migrations.RemoveField(
model_name='filter',
name='keywords',
),
migrations.RemoveField(
model_name='filter',
name='regex',
),
migrations.RemoveField(
model_name='filter',
name='whitelist',
),
migrations.RemoveField(
model_name='subscription',
name='article_fetch_limit',
),
migrations.RemoveField(
model_name='subscription',
name='reset_article_fetch_limit',
),
migrations.AddField(
model_name='filter',
name='is_insensitive',
field=models.BooleanField(default=True, verbose_name='is insensitive'),
),
migrations.AddField(
model_name='filter',
name='is_whitelist',
field=models.BooleanField(default=False, verbose_name='is whitelist'),
),
migrations.AddField(
model_name='filter',
name='match',
field=models.CharField(blank=True, max_length=256, verbose_name='match'),
),
migrations.AddField(
model_name='filter',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(0, 'None'), (1, 'Any word'), (2, 'All words'), (3, 'Exact match'), (4, 'Regular expression'), (5, 'Fuzzy word'), (6, 'Automatic')], default=1, verbose_name='matching algorithm'),
),
migrations.AlterField(
model_name='filter',
name='guild_id',
field=models.CharField(max_length=128, verbose_name='guild id'),
),
migrations.AlterField(
model_name='filter',
name='name',
field=models.CharField(max_length=128, verbose_name='name'),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-10 19:19
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0015_alter_filter_options_remove_filter_keywords_and_more'),
]
operations = [
migrations.AlterField(
model_name='filter',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(0, 'None'), (1, 'Any: Item contains any of these words (space separated)'), (2, 'All: Item contains all of these words (space separated)'), (3, 'Exact: Item contains this string'), (4, 'Regular expression: Item matches this regex'), (5, 'Fuzzy: Item contains a word similar to this word')], default=1, verbose_name='matching algorithm'),
),
migrations.AlterField(
model_name='subscription',
name='published_threshold',
field=models.DateTimeField(blank=True, default=django.utils.timezone.now),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-11 21:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0016_alter_filter_matching_algorithm_and_more'),
]
operations = [
migrations.AddField(
model_name='trackedcontent',
name='message_id',
field=models.CharField(max_length=128),
preserve_default=False,
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-20 18:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0017_trackedcontent_message_id'),
]
operations = [
migrations.AddField(
model_name='subchannel',
name='channel_name',
field=models.CharField(default='placeholder-channel-name', help_text='Name of the represented Channel.', max_length=256, verbose_name='channel name'),
preserve_default=False,
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-07-23 14:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0018_subchannel_channel_name'),
]
operations = [
migrations.AddField(
model_name='savedguilds',
name='default_embed_colour',
field=models.CharField(blank=True, default='3498db', max_length=6),
),
]

View File

@ -1,21 +0,0 @@
# 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')),
],
),
]

View File

@ -1,122 +0,0 @@
# Generated by Django 5.0.4 on 2024-08-14 20:41
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0020_guildsettings'),
]
operations = [
migrations.AlterModelOptions(
name='guildsettings',
options={'get_latest_by': 'id', 'verbose_name': 'guild settings', 'verbose_name_plural': 'guild settings'},
),
migrations.RemoveField(
model_name='savedguilds',
name='default_embed_colour',
),
migrations.AddField(
model_name='guildsettings',
name='active',
field=models.BooleanField(default=True, help_text='Subscriptions of inactive guilds will also be treated as inactive', verbose_name='Active'),
),
migrations.AlterField(
model_name='guildsettings',
name='guild_id',
field=models.CharField(help_text='Discord snowflake ID for the represented guild.', max_length=128, unique=True, verbose_name='guild id'),
),
migrations.AlterField(
model_name='savedguilds',
name='id',
field=models.AutoField(primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='subscription',
name='active',
field=models.BooleanField(default=True, verbose_name='Active'),
),
migrations.AlterField(
model_name='subscription',
name='article_fetch_image',
field=models.BooleanField(default=True, help_text='Will the resulting article have an image?', verbose_name='Fetch Article Images'),
),
migrations.AlterField(
model_name='subscription',
name='creation_datetime',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Created At'),
),
migrations.AlterField(
model_name='subscription',
name='embed_colour',
field=models.CharField(blank=True, default='3498db', max_length=6, verbose_name='Embed Colour'),
),
migrations.AlterField(
model_name='subscription',
name='extra_notes',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Extra Notes'),
),
migrations.AlterField(
model_name='subscription',
name='guild_id',
field=models.CharField(max_length=128, verbose_name='Guild ID'),
),
migrations.AlterField(
model_name='subscription',
name='name',
field=models.CharField(max_length=32, verbose_name='Name'),
),
migrations.AlterField(
model_name='subscription',
name='published_threshold',
field=models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='Published Threshold'),
),
migrations.AlterField(
model_name='subscription',
name='url',
field=models.URLField(verbose_name='URL'),
),
migrations.AlterField(
model_name='trackedcontent',
name='blocked',
field=models.BooleanField(default=False, verbose_name='Blocked'),
),
migrations.AlterField(
model_name='trackedcontent',
name='channel_id',
field=models.CharField(max_length=128, verbose_name='Channel ID'),
),
migrations.AlterField(
model_name='trackedcontent',
name='creation_datetime',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Created At'),
),
migrations.AlterField(
model_name='trackedcontent',
name='guid',
field=models.CharField(help_text='RSS provided GUID of the content', max_length=256, verbose_name='GUID'),
),
migrations.AlterField(
model_name='trackedcontent',
name='id',
field=models.AutoField(primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='trackedcontent',
name='message_id',
field=models.CharField(max_length=128, verbose_name='Message ID'),
),
migrations.AlterField(
model_name='trackedcontent',
name='title',
field=models.CharField(max_length=728, verbose_name='Title'),
),
migrations.AlterField(
model_name='trackedcontent',
name='url',
field=models.URLField(verbose_name='URL'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.0.4 on 2024-08-14 20:46
from django.db import migrations
def create_missing_guild_settings(apps, scheme_editor):
SavedGuilds = apps.get_model("home", "SavedGuilds")
GuildSettings = apps.get_model("home", "GuildSettings")
for saved_guild in SavedGuilds.objects.all():
GuildSettings.objects.get_or_create(guild_id=saved_guild.guild_id)
class Migration(migrations.Migration):
dependencies = [
('home', '0021_alter_guildsettings_options_and_more'),
]
operations = [
migrations.RunPython(create_missing_guild_settings),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-13 23:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0022_populate_guild_settings'),
]
operations = [
migrations.CreateModel(
name='UniqueContentRule',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
migrations.AddField(
model_name='subscription',
name='unique_content_rules',
field=models.ManyToManyField(to='home.uniquecontentrule'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-13 23:32
from django.db import migrations
def add_rules(apps, schema_editor):
Rules = apps.get_model("home", "UniqueContentRule")
Rules.objects.create(name="GUID", value="GUID")
Rules.objects.create(name="ID", value="ID")
Rules.objects.create(name="URL", value="URL")
Rules.objects.create(name="Title", value="TITLE")
Rules.objects.create(name="Content Hash", value="CONTENT")
class Migration(migrations.Migration):
dependencies = [
('home', '0023_uniquecontentrule_subscription_unique_content_rules'),
]
operations = [
migrations.RunPython(add_rules)
]

View File

@ -1,25 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-15 13:40
from django.db import migrations
def set_rules_to_ruleless_subs(apps, scheme_editor):
Subs = apps.get_model("home", "Subscription")
Rules = apps.get_model("home", "UniqueContentRule")
no_rule_subs = Subs.objects.filter(unique_content_rules=None)
guid_rule = Rules.objects.get(value="GUID")
id_rule = Rules.objects.get(value="ID")
for sub in no_rule_subs:
sub.unique_content_rules.set([guid_rule.pk, id_rule.pk])
class Migration(migrations.Migration):
dependencies = [
('home', '0024_initial_uniquecontentrule_data'),
]
operations = [
migrations.RunPython(set_rules_to_ruleless_subs)
]

View File

@ -1,104 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-18 20:26
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0025_set_uniquerules'),
]
operations = [
migrations.CreateModel(
name='r_MessageMutator',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
migrations.CreateModel(
name='r_Server',
fields=[
('id', models.PositiveIntegerField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=128)),
('icon_hash', models.CharField(max_length=128)),
('active', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='r_UniqueContentRule',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('value', models.CharField(max_length=32)),
],
),
migrations.CreateModel(
name='r_MessageStyle',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('is_embed', models.BooleanField(default=True)),
('is_hyperlinked', models.BooleanField()),
('show_author', models.BooleanField()),
('show_timestamp', models.BooleanField()),
('show_images', models.BooleanField()),
('fetch_images', models.BooleanField()),
('description_mutator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='desc_mutated_messagestyle', to='home.r_messagemutator')),
('title_mutator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='title_mutated_messagestyle', to='home.r_messagemutator')),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
],
),
migrations.CreateModel(
name='r_ContentFilter',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=32)),
('match', models.CharField(max_length=256)),
('matching_algorithm', models.PositiveIntegerField(choices=[(0, 'None'), (1, 'Any: Item contains any of these words (space separated)'), (2, 'All: Item contains all of these words (space separated)'), (3, 'Exact: Item contains this string'), (4, 'Regular expression: Item matches this regex'), (5, 'Fuzzy: Item contains a word similar to this word')])),
('is_insensitive', models.BooleanField()),
('is_whitelist', models.BooleanField()),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
],
),
migrations.CreateModel(
name='BotLogicLogs',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('level', models.CharField(max_length=32)),
('message', models.CharField(max_length=256)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
],
),
migrations.CreateModel(
name='r_Subscription',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=32)),
('url', models.URLField()),
('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
('extra_notes', models.CharField(blank=True, default='', max_length=250)),
('active', models.BooleanField(default=True)),
('filters', models.ManyToManyField(blank=True, to='home.r_contentfilter')),
('message_style', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.r_messagestyle')),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
],
),
migrations.CreateModel(
name='r_Content',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('item_id', models.CharField(max_length=1024)),
('item_guid', models.CharField(max_length=1024)),
('item_url', models.CharField(max_length=1024)),
('item_title', models.CharField(max_length=1024)),
('item_content_hash', models.CharField(max_length=1024)),
('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_subscription')),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.4 on 2024-09-23 20:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0026_temp_testonly_migration'),
]
operations = [
migrations.AlterField(
model_name='r_server',
name='icon_hash',
field=models.CharField(blank=True, max_length=128, null=True),
),
]

View File

@ -9,421 +9,25 @@ from django.core.exceptions import ValidationError
log = logging.getLogger(__name__)
#region Guild Settings
# region Server
class GuildSettings(models.Model):
class Server(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.
Represents a Discord Server.
Instances of this model are automatically handled, and manual intervension
should be avoided if possible.
"""
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."),
unique=True
)
default_embed_colour = models.CharField(
verbose_name=_("default embed colour"),
max_length=6,
default="3498db",
blank=True
)
active = models.BooleanField(
verbose_name=_("Active"),
default=True,
help_text=_("Subscriptions of inactive guilds will also be treated as inactive")
)
class Meta:
"""
Metadata for the GuildSettings model.
"""
verbose_name = "guild settings"
verbose_name_plural = "guild settings"
get_latest_by = "id"
#region Saved Guilds
class SavedGuilds(models.Model):
"""
Represents a saved Discord Guild (aka Server).
These are shown in the UI on the sidebar, and can be selected
to see associated Subscriptions.
"""
id = models.AutoField(_("ID"), primary_key=True)
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
# issue that rounds down the value
# https://github.com/sequelize/sequelize/issues/9335
guild_id = models.CharField(
verbose_name=_("guild id"),
max_length=128,
help_text=_("Discord snowflake ID for the represented guild.")
)
name = models.CharField(
max_length=128,
help_text=_("Name of the represented guild.")
)
icon = models.CharField(
max_length=128,
help_text=_("Hash for the represented guild's icon.")
)
added_by = models.ForeignKey(
verbose_name=_("added by"),
to="authentication.DiscordUser",
on_delete=models.CASCADE,
help_text=_("The user who added created this instance.")
)
permissions = models.CharField(
max_length=64,
help_text=_("Guild permissions for the user who added this instance.")
)
owner = models.BooleanField(
default=False,
help_text=_("Does the 'added by' user own this guild?")
)
class Meta:
"""
Metadata for the SavedGuilds Model.
"""
verbose_name = "saved guild"
verbose_name_plural = "saved guilds"
get_latest_by = "-creation_datetime"
constraints = [
# Prevent servers from having subscriptions with duplicate names
models.UniqueConstraint(
fields=["added_by", "guild_id"],
name="unique added_by & guild_id pair"
)
]
def __str__(self) -> str:
return self.name
@property
def settings(self):
return GuildSettings.objects.get(guild_id=self.guild_id)
def save(self, *args, **kwargs):
GuildSettings.objects.get_or_create(guild_id=self.guild_id)
super().save(*args, **kwargs)
#region Sub Channels
class SubChannel(models.Model):
"""
Represents a Discord TextChannel, saved against a Subscription.
SubChannels are used as targets to send content from Subscriptions.
"""
id = models.AutoField(primary_key=True)
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
# issue that rounds down the value
# https://github.com/sequelize/sequelize/issues/9335
channel_id = models.CharField(
verbose_name=_("channel id"),
max_length=128,
help_text=_("Discord snowflake ID for the represented Channel.")
)
channel_name = models.CharField(
verbose_name=_("channel name"),
max_length=256,
help_text=_("Name of the represented Channel.")
)
subscription = models.ForeignKey(
to="home.Subscription",
on_delete=models.CASCADE,
help_text=_("The linked Subscription, must be unique.")
)
class Meta:
"""
Metadata for the SubChannel Model.
"""
verbose_name = "SubChannel"
verbose_name_plural = "SubChannels"
get_latest_by = "id"
constraints = [
# Prevent servers from having subscriptions with duplicate names
models.UniqueConstraint(
fields=["channel_id", "subscription"],
name="unique channel id and subscription pair")
]
def __str__(self) -> str:
return self.channel_id
#region Filters
# using a brilliant matching model design from paperless-ngx src
class Filter(models.Model):
MATCH_NONE = 0
MATCH_ANY = 1
MATCH_ALL = 2
MATCH_LITERAL = 3
MATCH_REGEX = 4
MATCH_FUZZY = 5
MATCH_AUTO = 6
MATCHING_ALGORITHMS = (
(MATCH_NONE, _("None")),
(MATCH_ANY, _("Any: Item contains any of these words (space separated)")),
(MATCH_ALL, _("All: Item contains all of these words (space separated)")),
(MATCH_LITERAL, _("Exact: Item contains this string")),
(MATCH_REGEX, _("Regular expression: Item matches this regex")),
(MATCH_FUZZY, _("Fuzzy: Item contains a word similar to this word")),
# (MATCH_AUTO, _("Automatic")),
)
id = models.AutoField(primary_key=True)
name = models.CharField(_("name"), max_length=128)
match = models.CharField(_("match"), max_length=256, blank=True)
matching_algorithm = models.PositiveIntegerField(
_("matching algorithm"),
choices=MATCHING_ALGORITHMS,
default=MATCH_ANY,
)
is_insensitive = models.BooleanField(_("is insensitive"), default=True)
is_whitelist = models.BooleanField(_("is whitelist"), default=False)
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
# issue that rounds down the value
# https://github.com/sequelize/sequelize/issues/9335
guild_id = models.CharField(_("guild id"), max_length=128)
class Meta:
ordering = ("name",)
constraints = [
models.UniqueConstraint(
fields=["name", "guild_id"],
name="unique name & guild id pair"
)
]
def __str__(self):
return f"{self.guild_id} - {self.name}"
#region Subscription
class Subscription(models.Model):
"""
The Subscription Model.
'Subscription' in the context of PYRSS is an RSS Feed with various settings.
"""
id = models.AutoField(primary_key=True)
name = models.CharField(
_("Name"),
max_length=32,
null=False,
blank=False
)
url = models.URLField(_("URL"))
# NOTE:
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
# issue that rounds down the value
# https://github.com/sequelize/sequelize/issues/9335
guild_id = models.CharField(
_("Guild ID"),
max_length=128
)
creation_datetime = models.DateTimeField(
_("Created At"),
default=timezone.now,
editable=False
)
extra_notes = models.CharField(
_("Extra Notes"),
max_length=250,
null=True,
blank=True,
)
filters = models.ManyToManyField(to="home.Filter", blank=True)
unique_content_rules = models.ManyToManyField(
to="home.UniqueContentRule",
blank=False
);
article_title_mutators = models.ManyToManyField(
to="home.ArticleMutator",
related_name="title_mutated_subscriptions",
blank=True,
)
article_desc_mutators = models.ManyToManyField(
to="home.ArticleMutator",
related_name="desc_mutated_subscriptions",
blank=True,
)
embed_colour = models.CharField(
_("Embed Colour"),
max_length=6,
default="3498db",
blank=True
)
published_threshold = models.DateTimeField(_("Published Threshold"), default=timezone.now, blank=True)
article_fetch_image = models.BooleanField(
_("Fetch Article Images"),
default=True,
help_text="Will the resulting article have an image?"
)
active = models.BooleanField(_("Active"), default=True)
class Meta:
"""
Metadata for the Subscription Model.
"""
verbose_name = "subscription"
verbose_name_plural = "subscriptions"
get_latest_by = "-creation_datetime"
constraints = [
# Prevent servers from having subscriptions with duplicate names
models.UniqueConstraint(fields=["name", "guild_id"], name="unique name & server pair")
]
@property
def channels_count(self) -> int:
"""
Returns the number of 'SubChannel' objects assocaited
with this subscription.
"""
return len(SubChannel.objects.filter(subscription=self))
def save(self, *args, **kwargs):
new_text = "New " if self._state.adding else ""
log.debug("%sSubscription Saved %s", new_text, self.id)
super().save(*args, **kwargs)
def __str__(self) -> str:
return self.name
#region Tracked Content
class TrackedContent(models.Model):
"""
Tracked Content Model
'Tracked Content' identifies articles and tracks them being sent.
This is used to ensure duplicate articles aren't sent in feeds.
"""
id = models.AutoField(_("ID"), primary_key=True)
guid = models.CharField(
_("GUID"),
max_length=256,
help_text=_("RSS provided GUID of the content")
)
title = models.CharField(_("Title"), max_length=728)
url = models.URLField(_("URL"))
subscription = models.ForeignKey(to=Subscription, on_delete=models.CASCADE)
channel_id = models.CharField(_("Channel ID"), max_length=128)
message_id = models.CharField(_("Message ID"), max_length=128)
blocked = models.BooleanField(_("Blocked"), default=False)
creation_datetime = models.DateTimeField(
_("Created At"),
default=timezone.now,
editable=False
)
class Meta:
verbose_name = "tracked content"
verbose_name = "tracked contents"
get_latest_by = "-creation_datetime"
constraints = [
models.UniqueConstraint(fields=["guid", "channel_id"], name="unique guid & channel_id pair"),
models.UniqueConstraint(fields=["url", "channel_id"], name="unique url & channel_id pair")
]
def __str__(self) -> str:
return self.title
#region Article Mutator
class ArticleMutator(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64)
value = models.CharField(max_length=32)
def __str__(self) -> str:
return self.name
#region UniqueContentRules
class UniqueContentRule(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64)
value = models.CharField(max_length=32)
def __str__(self) -> str:
return self.name
#region Rewrite - - - - - - - -
class r_Server(models.Model):
id = models.PositiveIntegerField(primary_key=True)
name = models.CharField(max_length=128)
icon_hash = models.CharField(max_length=128, blank=True, null=True)
active = models.BooleanField(default=True)
class Meta:
verbose_name = "server"
verbose_name_plural = "servers"
get_latest_by = "name"
@property
def icon_url(self):
return f"https://cdn.discordapp.com/icons/{self.id}/{self.icon_hash}.webp?size=80"
@ -432,9 +36,16 @@ class r_Server(models.Model):
return self.name
class r_ContentFilter(models.Model):
# region Content Filter
class ContentFilter(models.Model):
"""
Filters for the content produced by Subscriptions.
Owned by the related server.
"""
id = models.AutoField(primary_key=True)
server = models.ForeignKey(to=r_Server, on_delete=models.CASCADE)
server = models.ForeignKey(to=Server, on_delete=models.CASCADE)
MATCH_NONE = 0
MATCH_ANY = 1
@ -459,23 +70,47 @@ class r_ContentFilter(models.Model):
is_insensitive = models.BooleanField()
is_whitelist = models.BooleanField()
class Meta:
verbose_name = "filter"
verbose_name_plural = "filters"
get_latest_by = "id"
def __str__(self):
return self.name
# Instances of this model are predefined only
class r_MessageMutator(models.Model):
# region Message Mutator
class MessageMutator(models.Model):
"""
Mutators to be applied via the Bot.
Instances of this model are predefined via migrations.
Manual editing should be avoided at all costs!
"""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64)
value = models.CharField(max_length=32)
class Meta:
verbose_name = "message mutator"
verbose_name_plural = "message mutators"
get_latest_by = "id"
def __str__(self):
return self.name
class r_MessageStyle(models.Model):
# region Message Style
class MessageStyle(models.Model):
"""
Custom styles to be applied via the Bot.
Owned by the related server.
"""
id = models.AutoField(primary_key=True)
server = models.ForeignKey(to=r_Server, on_delete=models.CASCADE)
server = models.ForeignKey(to=Server, on_delete=models.CASCADE)
is_embed = models.BooleanField(default=True)
is_hyperlinked = models.BooleanField() # title only
@ -485,27 +120,61 @@ class r_MessageStyle(models.Model):
fetch_images = models.BooleanField() # if not included with RSS item
title_mutator = models.ForeignKey(
to=r_MessageMutator,
to=MessageMutator,
related_name="title_mutated_messagestyle",
on_delete=models.SET_NULL,
null=True,
blank=True
)
description_mutator = models.ForeignKey(
to=r_MessageMutator,
to=MessageMutator,
related_name="desc_mutated_messagestyle",
on_delete=models.SET_NULL,
null=True,
blank=True
)
class Meta:
verbose_name = "message style"
verbose_name_plural = "message styles"
get_latest_by = "id"
def __str__(self):
return f"{self.server.name} - {self.id}"
class r_Subscription(models.Model):
# region Unique Content Rule
class UniqueContentRule(models.Model):
"""
Definitions for what content should be unique
Instances of this model are predefined via migrations.
Manual editing should be avoided at all costs!
"""
id = models.AutoField(primary_key=True)
server = models.ForeignKey(to=r_Server, on_delete=models.CASCADE, blank=False)
name = models.CharField(max_length=64)
value = models.CharField(max_length=32)
class Meta:
verbose_name = "unique content rule"
verbose_name_plural = "unique content rules"
get_latest_by = "id"
def __str__(self):
return self.name
# region Subscription
class Subscription(models.Model):
"""
These represent RSSFeeds, storing relevant settings for managing them.
Owned by the related server.
"""
id = models.AutoField(primary_key=True)
server = models.ForeignKey(to=Server, on_delete=models.CASCADE, blank=False)
name = models.CharField(max_length=32, blank=False)
url = models.URLField()
@ -514,16 +183,28 @@ class r_Subscription(models.Model):
extra_notes = models.CharField(max_length=250, default="", blank=True)
active = models.BooleanField(default=True)
filters = models.ManyToManyField(to=r_ContentFilter, blank=True)
message_style = models.ForeignKey(to=r_MessageStyle, on_delete=models.SET_NULL, null=True, blank=True)
filters = models.ManyToManyField(to=ContentFilter, blank=True)
message_style = models.ForeignKey(to=MessageStyle, on_delete=models.SET_NULL, null=True, blank=True)
unique_rules = models.ManyToManyField(to=UniqueContentRule, blank=False)
class Meta:
verbose_name = "subscription"
verbose_name_plural = "subscriptions"
get_latest_by = "updated_at"
def __str__(self):
return self.name
class r_Content(models.Model):
# region Content
class Content(models.Model):
"""
Represents a processed item created from a Subscription.
"""
id = models.AutoField(primary_key=True)
subscription = models.ForeignKey(to=r_Subscription, on_delete=models.CASCADE)
subscription = models.ForeignKey(to=Subscription, on_delete=models.CASCADE)
# 'item_' prefix is to differentiate between the internal identifiers and the stored data
item_id = models.CharField(max_length=1024)
@ -532,28 +213,30 @@ class r_Content(models.Model):
item_title = models.CharField(max_length=1024)
item_content_hash = models.CharField(max_length=1024)
class Meta:
verbose_name = "content"
verbose_name_plural = "content"
get_latest_by = "id"
def __str__(self):
return f"{self.subscription.name} - {self.id}"
# Instances of this model are predefined only
class r_UniqueContentRule(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64)
value = models.CharField(max_length=32)
def __str__(self):
return self.name
# region Bot Logic Logs
# Relevant logs from the bot logic
class BotLogicLogs(models.Model):
id = models.AutoField(primary_key=True)
server = models.ForeignKey(to=r_Server, on_delete=models.CASCADE)
server = models.ForeignKey(to=Server, on_delete=models.CASCADE)
level = models.CharField(max_length=32)
message = models.CharField(max_length=256)
created_at = models.DateTimeField(default=timezone.now, editable=False)
class Meta:
verbose_name = "bot logic log"
verbose_name_plural = "bot logic logs"
get_latest_by = "id"
def __str__(self):
return f"{self.server.name} - {self.id}"

View File

@ -9,7 +9,7 @@ from django.shortcuts import redirect
from django.http import JsonResponse, HttpResponse
from django.views.generic import TemplateView, View
from apps.home.models import r_Server
from apps.home.models import Server
from apps.authentication.models import DiscordUser, ServerMember
@ -68,7 +68,7 @@ class GuildsView(View):
await self.delete_member(user, data["id"])
return
server = await r_Server.objects.aget_or_create(
server = await Server.objects.aget_or_create(
id=data["id"],
name=data["name"],
icon_hash=data["icon"]

View File

@ -32,30 +32,6 @@ function makeQuerystring(filters, sort) {
return sort ? querystring += `ordering=${sort}` : querystring;
}
// Saved Guilds
async function getSavedGuilds(userId) {
return await ajaxRequest(`/api/saved-guilds/?added_by=${userId}`, "GET");
}
async function getSavedGuild(id) {
return await ajaxRequest(`/api/saved-guilds/${id}/`, "GET");
}
async function newSavedGuild(formData) {
return await ajaxRequest("/api/saved-guilds/", "POST", formData);
}
async function deleteSavedGuild(id) {
return await ajaxRequest(`/api/saved-guilds/${id}/`, "DELETE");
}
// Loading Guilds
async function loadGuilds() {
return await ajaxRequest("/guilds/", "GET");
}
// Loading Channels
async function loadChannels(guildId) {
return await ajaxRequest(`/channels?guild=${guildId}`, "GET");
@ -63,30 +39,30 @@ async function loadChannels(guildId) {
// Subscriptions
async function getSubscriptions(filters, sort) {
let querystring = makeQuerystring(filters, sort);
return await ajaxRequest(`/api/subscription/${querystring}`, "GET");
}
// async function getSubscriptions(filters, sort) {
// let querystring = makeQuerystring(filters, sort);
// return await ajaxRequest(`/api/subscription/${querystring}`, "GET");
// }
async function getSubscription(id) {
return await ajaxRequest(`/api/subscription/${id}/`, "GET");
}
// async function getSubscription(id) {
// return await ajaxRequest(`/api/subscription/${id}/`, "GET");
// }
async function newSubscription(formData) {
return await ajaxRequest("/api/subscription/", "POST", formData);
}
// async function newSubscription(formData) {
// return await ajaxRequest("/api/subscription/", "POST", formData);
// }
async function deleteSubscription(id) {
return await ajaxRequest(`/api/subscription/${id}/`, "DELETE");
}
// async function deleteSubscription(id) {
// return await ajaxRequest(`/api/subscription/${id}/`, "DELETE");
// }
async function editSubscription(id, formData) {
return await ajaxRequest(`/api/subscription/${id}/`, "PUT", formData);
}
// async function editSubscription(id, formData) {
// return await ajaxRequest(`/api/subscription/${id}/`, "PUT", formData);
// }
async function getSubscriptionOptions() {
return await ajaxRequest("/api/subscription/", "OPTIONS")
}
// async function getSubscriptionOptions() {
// return await ajaxRequest("/api/subscription/", "OPTIONS")
// }
// SubChannels
@ -186,10 +162,47 @@ async function getUniqueContentRule(ruleId) {
//#region rewrite
// #region Servers
async function generateServers() {
return ajaxRequest("/generate-servers/", "GET");
}
async function getServers() {
return ajaxRequest("/api/r_servers/", "GET");
}
}
// #endregion
// #region Subscriptions
async function getSubscriptions(filters, sort) {
let querystring = makeQuerystring(filters, sort);
return await ajaxRequest(`/api/r_subscriptions/${querystring}`, "GET");
}
async function getSubscription(id) {
return await ajaxRequest(`/api/r_subscriptions/${id}/`, "GET");
}
async function newSubscription(formData) {
return await ajaxRequest("/api/r_subscriptions/", "POST", formData);
}
async function deleteSubscription(id) {
return await ajaxRequest(`/api/r_subscriptions/${id}/`, "DELETE");
}
async function editSubscription(id, formData) {
return await ajaxRequest(`/api/r_subscriptions/${id}/`, "PUT", formData);
}
async function getSubscriptionOptions() {
return await ajaxRequest("/api/r_subscriptions/", "OPTIONS")
}
// #endregion

View File

@ -1,11 +1,11 @@
$(document).ready(async function() {
await loadServers();
await initSubscriptionTable();
await initFiltersTable();
await initContentTable();
$("#subscriptionsTab").click();
await loadServers();
});
$(document).on("selectedServerChange", function() {

View File

@ -97,7 +97,6 @@ function selectServer(id) {
selectServer = null;
return;
}
debugger
// Change appearance of selected vs none-selected items
$("#serverList .server-item").removeClass("active");