kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			
		
			
				
	
	
		
			368 wiersze
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			368 wiersze
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
| import collections
 | |
| import io
 | |
| import os
 | |
| 
 | |
| import PIL
 | |
| from django.core.exceptions import ObjectDoesNotExist
 | |
| from django.core.files.uploadedfile import SimpleUploadedFile
 | |
| from django.utils.encoding import smart_text
 | |
| from django.utils.translation import ugettext_lazy as _
 | |
| from drf_spectacular.types import OpenApiTypes
 | |
| from drf_spectacular.utils import extend_schema_field
 | |
| from rest_framework import serializers
 | |
| 
 | |
| from . import models, utils
 | |
| 
 | |
| 
 | |
| class RelatedField(serializers.RelatedField):
 | |
|     default_error_messages = {
 | |
|         "does_not_exist": _("Object with {related_field_name}={value} does not exist."),
 | |
|         "invalid": _("Invalid value."),
 | |
|     }
 | |
| 
 | |
|     def __init__(self, related_field_name, serializer, **kwargs):
 | |
|         self.related_field_name = related_field_name
 | |
|         self.serializer = serializer
 | |
|         self.filters = kwargs.pop("filters", None)
 | |
|         self.queryset_filter = kwargs.pop("queryset_filter", None)
 | |
|         try:
 | |
|             kwargs["queryset"] = kwargs.pop("queryset")
 | |
|         except KeyError:
 | |
|             kwargs["queryset"] = self.serializer.Meta.model.objects.all()
 | |
|         super().__init__(**kwargs)
 | |
| 
 | |
|     def get_filters(self, data):
 | |
|         filters = {self.related_field_name: data}
 | |
|         if self.filters:
 | |
|             filters.update(self.filters(self.context))
 | |
|         return filters
 | |
| 
 | |
|     def filter_queryset(self, queryset):
 | |
|         if self.queryset_filter:
 | |
|             queryset = self.queryset_filter(queryset, self.context)
 | |
|         return queryset
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         try:
 | |
|             queryset = self.get_queryset()
 | |
|             filters = self.get_filters(data)
 | |
|             queryset = self.filter_queryset(queryset)
 | |
|             return queryset.get(**filters)
 | |
|         except ObjectDoesNotExist:
 | |
|             self.fail(
 | |
|                 "does_not_exist",
 | |
|                 related_field_name=self.related_field_name,
 | |
|                 value=smart_text(data),
 | |
|             )
 | |
|         except (TypeError, ValueError):
 | |
|             self.fail("invalid")
 | |
| 
 | |
|     def to_representation(self, obj):
 | |
|         return self.serializer.to_representation(obj)
 | |
| 
 | |
|     def get_choices(self, cutoff=None):
 | |
|         queryset = self.get_queryset()
 | |
|         if queryset is None:
 | |
|             # Ensure that field.choices returns something sensible
 | |
|             # even when accessed with a read-only field.
 | |
|             return {}
 | |
| 
 | |
|         if cutoff is not None:
 | |
|             queryset = queryset[:cutoff]
 | |
| 
 | |
|         return collections.OrderedDict(
 | |
|             [
 | |
|                 (
 | |
|                     self.to_representation(item)[self.related_field_name],
 | |
|                     self.display_value(item),
 | |
|                 )
 | |
|                 for item in queryset
 | |
|                 if self.serializer
 | |
|             ]
 | |
|         )
 | |
| 
 | |
| 
 | |
| class Action:
 | |
|     def __init__(self, name, allow_all=False, qs_filter=None):
 | |
|         self.name = name
 | |
|         self.allow_all = allow_all
 | |
|         self.qs_filter = qs_filter
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return f"<Action {self.name}>"
 | |
| 
 | |
| 
 | |
| class ActionSerializer(serializers.Serializer):
 | |
|     """
 | |
|     A special serializer that can operate on a list of objects
 | |
|     and apply actions on it.
 | |
|     """
 | |
| 
 | |
|     action = serializers.CharField(required=True)
 | |
|     objects = serializers.JSONField(required=True)
 | |
|     filters = serializers.DictField(required=False)
 | |
|     actions = None
 | |
|     pk_field = "pk"
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         self.actions_by_name = {a.name: a for a in self.actions}
 | |
|         self.queryset = kwargs.pop("queryset")
 | |
|         if self.actions is None:
 | |
|             raise ValueError(
 | |
|                 "You must declare a list of actions on " "the serializer class"
 | |
|             )
 | |
| 
 | |
|         for action in self.actions_by_name.keys():
 | |
|             handler_name = f"handle_{action}"
 | |
|             assert hasattr(self, handler_name), "{} miss a {} method".format(
 | |
|                 self.__class__.__name__, handler_name
 | |
|             )
 | |
|         super().__init__(self, *args, **kwargs)
 | |
| 
 | |
|     def validate_action(self, value):
 | |
|         try:
 | |
|             return self.actions_by_name[value]
 | |
|         except KeyError:
 | |
|             raise serializers.ValidationError(
 | |
|                 "{} is not a valid action. Pick one of {}.".format(
 | |
|                     value, ", ".join(self.actions_by_name.keys())
 | |
|                 )
 | |
|             )
 | |
| 
 | |
|     def validate_objects(self, value):
 | |
|         if value == "all":
 | |
|             return self.queryset.all().order_by("id")
 | |
|         if type(value) in [list, tuple]:
 | |
|             return self.queryset.filter(**{f"{self.pk_field}__in": value}).order_by(
 | |
|                 self.pk_field
 | |
|             )
 | |
| 
 | |
|         raise serializers.ValidationError(
 | |
|             "{} is not a valid value for objects. You must provide either a "
 | |
|             'list of identifiers or the string "all".'.format(value)
 | |
|         )
 | |
| 
 | |
|     def validate(self, data):
 | |
|         allow_all = data["action"].allow_all
 | |
|         if not allow_all and self.initial_data["objects"] == "all":
 | |
|             raise serializers.ValidationError(
 | |
|                 "You cannot apply this action on all objects"
 | |
|             )
 | |
|         final_filters = data.get("filters", {}) or {}
 | |
|         if self.filterset_class and final_filters:
 | |
|             qs_filterset = self.filterset_class(final_filters, queryset=data["objects"])
 | |
|             try:
 | |
|                 assert qs_filterset.form.is_valid()
 | |
|             except (AssertionError, TypeError):
 | |
|                 raise serializers.ValidationError("Invalid filters")
 | |
|             data["objects"] = qs_filterset.qs
 | |
| 
 | |
|         if data["action"].qs_filter:
 | |
|             data["objects"] = data["action"].qs_filter(data["objects"])
 | |
| 
 | |
|         data["count"] = data["objects"].count()
 | |
|         if data["count"] < 1:
 | |
|             raise serializers.ValidationError("No object matching your request")
 | |
|         return data
 | |
| 
 | |
|     def save(self):
 | |
|         handler_name = "handle_{}".format(self.validated_data["action"].name)
 | |
|         handler = getattr(self, handler_name)
 | |
|         result = handler(self.validated_data["objects"])
 | |
|         payload = {
 | |
|             "updated": self.validated_data["count"],
 | |
|             "action": self.validated_data["action"].name,
 | |
|             "result": result,
 | |
|         }
 | |
|         return payload
 | |
| 
 | |
| 
 | |
| def track_fields_for_update(*fields):
 | |
|     """
 | |
|     Apply this decorator to serializer to call function when specific values
 | |
|     are updated on an object:
 | |
| 
 | |
|     .. code-block:: python
 | |
| 
 | |
|         @track_fields_for_update('privacy_level')
 | |
|         class LibrarySerializer(serializers.ModelSerializer):
 | |
|             def on_updated_privacy_level(self, obj, old_value, new_value):
 | |
|                 print('Do someting')
 | |
|     """
 | |
| 
 | |
|     def decorator(serializer_class):
 | |
|         original_update = serializer_class.update
 | |
| 
 | |
|         def new_update(self, obj, validated_data):
 | |
|             tracked_fields_before = {f: getattr(obj, f) for f in fields}
 | |
|             obj = original_update(self, obj, validated_data)
 | |
|             tracked_fields_after = {f: getattr(obj, f) for f in fields}
 | |
| 
 | |
|             if tracked_fields_before != tracked_fields_after:
 | |
|                 self.on_updated_fields(obj, tracked_fields_before, tracked_fields_after)
 | |
|             return obj
 | |
| 
 | |
|         serializer_class.update = new_update
 | |
|         return serializer_class
 | |
| 
 | |
|     return decorator
 | |
| 
 | |
| 
 | |
| class StripExifImageField(serializers.ImageField):
 | |
|     def to_internal_value(self, data):
 | |
|         file_obj = super().to_internal_value(data)
 | |
| 
 | |
|         image = PIL.Image.open(file_obj)
 | |
|         data = list(image.getdata())
 | |
|         image_without_exif = PIL.Image.new(image.mode, image.size)
 | |
|         image_without_exif.putdata(data)
 | |
| 
 | |
|         with io.BytesIO() as output:
 | |
|             image_without_exif.save(
 | |
|                 output,
 | |
|                 format=PIL.Image.EXTENSION[os.path.splitext(file_obj.name)[-1].lower()],
 | |
|                 quality=100,
 | |
|             )
 | |
|             content = output.getvalue()
 | |
| 
 | |
|         return SimpleUploadedFile(
 | |
|             file_obj.name, content, content_type=file_obj.content_type
 | |
|         )
 | |
| 
 | |
| 
 | |
| from funkwhale_api.federation import serializers as federation_serializers  # noqa
 | |
| 
 | |
| TARGET_ID_TYPE_MAPPING = {
 | |
|     "music.Track": ("id", "track"),
 | |
|     "music.Artist": ("id", "artist"),
 | |
|     "music.Album": ("id", "album"),
 | |
| }
 | |
| 
 | |
| 
 | |
| class APIMutationSerializer(serializers.ModelSerializer):
 | |
|     created_by = federation_serializers.APIActorSerializer(read_only=True)
 | |
|     target = serializers.SerializerMethodField()
 | |
| 
 | |
|     class Meta:
 | |
|         model = models.Mutation
 | |
|         fields = [
 | |
|             "fid",
 | |
|             "uuid",
 | |
|             "type",
 | |
|             "creation_date",
 | |
|             "applied_date",
 | |
|             "is_approved",
 | |
|             "is_applied",
 | |
|             "created_by",
 | |
|             "approved_by",
 | |
|             "summary",
 | |
|             "payload",
 | |
|             "previous_state",
 | |
|             "target",
 | |
|         ]
 | |
|         read_only_fields = [
 | |
|             "uuid",
 | |
|             "creation_date",
 | |
|             "fid",
 | |
|             "is_applied",
 | |
|             "created_by",
 | |
|             "approved_by",
 | |
|             "previous_state",
 | |
|         ]
 | |
| 
 | |
|     @extend_schema_field(OpenApiTypes.OBJECT)
 | |
|     def get_target(self, obj):
 | |
|         target = obj.target
 | |
|         if not target:
 | |
|             return
 | |
| 
 | |
|         id_field, type = TARGET_ID_TYPE_MAPPING[target._meta.label]
 | |
|         return {"type": type, "id": getattr(target, id_field), "repr": str(target)}
 | |
| 
 | |
|     def validate_type(self, value):
 | |
|         if value not in self.context["registry"]:
 | |
|             raise serializers.ValidationError(f"Invalid mutation type {value}")
 | |
|         return value
 | |
| 
 | |
| 
 | |
| class AttachmentSerializer(serializers.Serializer):
 | |
|     uuid = serializers.UUIDField(read_only=True)
 | |
|     size = serializers.IntegerField(read_only=True)
 | |
|     mimetype = serializers.CharField(read_only=True)
 | |
|     creation_date = serializers.DateTimeField(read_only=True)
 | |
|     file = StripExifImageField(write_only=True)
 | |
|     urls = serializers.SerializerMethodField()
 | |
| 
 | |
|     @extend_schema_field(OpenApiTypes.OBJECT)
 | |
|     def get_urls(self, o):
 | |
|         urls = {}
 | |
|         urls["source"] = o.url
 | |
|         urls["original"] = o.download_url_original
 | |
|         urls["medium_square_crop"] = o.download_url_medium_square_crop
 | |
|         urls["large_square_crop"] = o.download_url_large_square_crop
 | |
|         return urls
 | |
| 
 | |
|     def create(self, validated_data):
 | |
|         return models.Attachment.objects.create(
 | |
|             file=validated_data["file"], actor=validated_data["actor"]
 | |
|         )
 | |
| 
 | |
| 
 | |
| class ContentSerializer(serializers.Serializer):
 | |
|     text = serializers.CharField(
 | |
|         max_length=models.CONTENT_TEXT_MAX_LENGTH, allow_null=True
 | |
|     )
 | |
|     content_type = serializers.ChoiceField(
 | |
|         choices=models.CONTENT_TEXT_SUPPORTED_TYPES,
 | |
|     )
 | |
|     html = serializers.SerializerMethodField()
 | |
| 
 | |
|     def get_html(self, o) -> str:
 | |
|         return utils.render_html(o.text, o.content_type)
 | |
| 
 | |
| 
 | |
| class NullToEmptDict:
 | |
|     def get_attribute(self, o):
 | |
|         attr = super().get_attribute(o)
 | |
|         if attr is None:
 | |
|             return {}
 | |
|         return attr
 | |
| 
 | |
|     def to_representation(self, v):
 | |
|         if not v:
 | |
|             return v
 | |
|         return super().to_representation(v)
 | |
| 
 | |
| 
 | |
| class ScopesSerializer(serializers.Serializer):
 | |
|     id = serializers.CharField()
 | |
|     rate = serializers.CharField()
 | |
|     description = serializers.CharField()
 | |
|     limit = serializers.IntegerField()
 | |
|     duration = serializers.IntegerField()
 | |
|     remaining = serializers.IntegerField()
 | |
|     available = serializers.IntegerField()
 | |
|     available_seconds = serializers.IntegerField()
 | |
|     reset = serializers.IntegerField()
 | |
|     reset_seconds = serializers.IntegerField()
 | |
| 
 | |
| 
 | |
| class IdentSerializer(serializers.Serializer):
 | |
|     type = serializers.CharField()
 | |
|     id = serializers.IntegerField()
 | |
| 
 | |
| 
 | |
| class RateLimitSerializer(serializers.Serializer):
 | |
|     enabled = serializers.BooleanField()
 | |
|     ident = IdentSerializer()
 | |
|     scopes = serializers.ListField(child=ScopesSerializer())
 | |
| 
 | |
| 
 | |
| class ErrorDetailSerializer(serializers.Serializer):
 | |
|     detail = serializers.CharField(source="*")
 | |
| 
 | |
| 
 | |
| class TextPreviewSerializer(serializers.Serializer):
 | |
|     rendered = serializers.CharField(read_only=True, source="*")
 | |
|     text = serializers.CharField(write_only=True)
 |