PYRSS-Website/apps/api/serializers.py

369 lines
10 KiB
Python

# -*- encoding: utf-8 -*-
import logging
from rest_framework import serializers
from apps.home.models import (
SubChannel,
Filter,
Subscription,
SavedGuilds,
TrackedContent,
ArticleMutator,
GuildSettings,
UniqueContentRule,
#rewrite
r_Server,
r_ContentFilter,
r_MessageMutator,
r_MessageStyle,
r_Subscription,
r_Content,
r_UniqueContentRule
)
log = logging.getLogger(__name__)
#region Dynamic Model
# This DynamicModelSerializer is from a StackOverflow user in an obscure thread.
# I wish that I could remember which thread, because god bless that man.
class DynamicModelSerializer(serializers.ModelSerializer):
"""
For use with GET requests, to specify which fields to include or exclude
Mimics some graphql functionality.
Usage: Inherit your ModelSerializer with this class. Add "only_fields" or
"exclude_fields" to the query parameters of your GET request.
This also works with nested foreign keys, for example:
?only_fields=name,age&company__only_fields=id,name
Some more examples:
?only_fields=company,name&company__exclude_fields=name
?exclude_fields=name&company__only_fields=id
?company__exclude_fields=name
Note: the Foreign Key serializer must also inherit from this class
"""
def only_keep_fields(self, fields_to_keep):
fields_to_keep = set(fields_to_keep.split(","))
all_fields = set(self.fields.keys())
for field in all_fields - fields_to_keep:
self.fields.pop(field, None)
def exclude_fields(self, fields_to_exclude):
fields_to_exclude = fields_to_exclude.split(",")
for field in fields_to_exclude:
self.fields.pop(field, None)
def remove_unwanted_fields(self, dynamic_params):
if fields_to_keep := dynamic_params.pop("only_fields", None):
self.only_keep_fields(fields_to_keep)
if fields_to_exclude := dynamic_params.pop("exclude_fields", None):
self.exclude_fields(fields_to_exclude)
def get_or_create_dynamic_params(self, child):
if "dynamic_params" not in self.fields[child]._context:
self.fields[child]._context.update({"dynamic_params": {}})
return self.fields[child]._context["dynamic_params"]
@staticmethod
def split_param(dynamic_param):
crumbs = dynamic_param.split("__")
return crumbs[0], "__".join(crumbs[1:]) if len(crumbs) > 1 else None
def set_dynamic_params_for_children(self, dynamic_params):
for param, fields in dynamic_params.items():
child, child_dynamic_param = self.split_param(param)
if child in set(self.fields.keys()):
dynamic_params = self.get_or_create_dynamic_params(child)
dynamic_params.update({child_dynamic_param: fields})
@staticmethod
def is_param_dynamic(p):
return p.endswith("only_fields") or p.endswith("exclude_fields")
def get_dynamic_params_for_root(self, request):
query_params = request.query_params.items()
return {k: v for k, v in query_params if self.is_param_dynamic(k)}
def get_dynamic_params(self):
"""
When dynamic params get passed down in set_context_for_children
If the child is a subclass of ListSerializer (has many=True)
The context must be fetched from ListSerializer Class
"""
if isinstance(self.parent, serializers.ListSerializer):
return self.parent._context.get("dynamic_params", {})
return self._context.get("dynamic_params", {})
def __init__(self, *args, **kwargs):
request = kwargs.get("context", {}).get("request")
super().__init__(*args, **kwargs)
is_root = bool(request)
if is_root:
if request.method != "GET":
return
dynamic_params = self.get_dynamic_params_for_root(request)
self._context.update({"dynamic_params": dynamic_params})
def to_representation(self, *args, **kwargs):
if dynamic_params := self.get_dynamic_params().copy():
self.remove_unwanted_fields(dynamic_params)
self.set_dynamic_params_for_children(dynamic_params)
return super().to_representation(*args, **kwargs)
class Meta:
abstract = True
#region Sub Channel
class SubChannelSerializer(DynamicModelSerializer):
"""
Serializer for SubChannel Model.
"""
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
fields = ("id", "name", "icon_hash", "active")
class r_ContentFilterSerializer(DynamicModelSerializer):
class Meta:
model = r_ContentFilter
fields = (
"id",
"server",
"name",
"match",
"matching_algorithm",
"is_insensitive",
"is_whitelist"
)
class r_MessageMutatorSerializer(DynamicModelSerializer):
class Meta:
model = r_MessageMutator
fields = ("id", "name", "value")
class r_MessageStyleSerializer(DynamicModelSerializer):
class Meta:
model = r_MessageStyle
fields = (
"id",
"server",
"is_embed",
"is_hyperlinked",
"show_author",
"show_timestamp",
"show_images",
"fetch_images",
"title_mutator",
"description_mutator"
)
class r_SubscriptionSerializer(DynamicModelSerializer):
filters = serializers.PrimaryKeyRelatedField(
queryset=r_ContentFilter.objects.all(),
many=True
)
class Meta:
model = r_Subscription
fields = (
"id",
"server",
"name",
"url",
"created_at",
"updated_at",
"extra_notes",
"active",
"filters",
"message_style"
)
def validate(self, data):
server = data.get("server") or self.context.get("server")
if not server:
return data
# 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)
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."}
)
# Prevent using message styles from a different server
message_style = data.get("message_style")
if message_style and message_style.server != server:
raise serializers.ValidationError(
{"message_style": "Message style must belong to the specified server."}
)
return data
class r_ContentSerializer(DynamicModelSerializer):
class Meta:
model = r_Content
fields = (
"id",
"subscription",
"item_id",
"item_guid",
"item_url",
"item_title",
"item_content_hash"
)
class r_UniqueContentRuleSerializer(DynamicModelSerializer):
class Meta:
model = r_UniqueContentRule
fields = ("id", "name", "value")