pulseapi/entries/serializers/base.py (139 lines of code) (raw):

"""Serialize the models""" from rest_framework import serializers from django.utils.encoding import smart_text from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.db.utils import IntegrityError from pulseapi.entries.models import Entry, ModerationState from pulseapi.tags.models import Tag from pulseapi.issues.models import Issue from pulseapi.helptypes.models import HelpType from pulseapi.creators.models import EntryCreator from pulseapi.creators.serializers import ( RelatedEntryCreatorV1Field, RelatedEntryCreatorField, ) def associate_entry_with_creator_data(entry, creator_data=[], user=None): if user and entry.published_by_creator: creator_data.append({'profile': user.profile}) for data in creator_data: if not data.pop('profile_committed', True): data.profile.save() EntryCreator.objects.create(entry=entry, **data) class CreatableSlugRelatedField(serializers.SlugRelatedField): """ Override SlugRelatedField to create or update instead of getting upset that a tag doesn't exist """ def to_internal_value(self, data): try: qs = self.get_queryset() return qs.get_or_create(**{self.slug_field: data})[0] except ObjectDoesNotExist: self.fail( 'does_not_exist', slug_name=self.slug_field, value=smart_text(data) ) except (TypeError, ValueError): self.fail('invalid') class ModerationStateSerializer(serializers.ModelSerializer): class Meta: """ Meta class. Because """ model = ModerationState exclude = () class EntryBaseSerializer(serializers.ModelSerializer): """ Serializes an entry with minimal information """ # As an optional-for-some-types-of-entries, this # field is required "by default" in this serializer, # and marked as "not required" for the various entry # types that don't actually need it. content_url = serializers.URLField(required=True) is_bookmarked = serializers.SerializerMethodField() def get_is_bookmarked(self, instance): """ Check whether the current user has bookmarked this Entry. Anonymous users always see False """ user = self.context.get('user') if user and user.is_authenticated: # instance.bookmarked_by.all() is already prefetched and cached in the QuerySet return any(bookmark_by.profile.user == user for bookmark_by in instance.bookmarked_by.all()) return False class Meta: model = Entry read_only_fields = fields = ( 'id', 'title', 'content_url', 'thumbnail', 'is_bookmarked', ) class EntryWithV1CreatorsBaseSerializer(EntryBaseSerializer): related_creators = RelatedEntryCreatorV1Field( queryset=EntryCreator.objects.all(), source='related_entry_creators', required=False, many=True, ) class Meta(EntryBaseSerializer.Meta): read_only_fields = fields = EntryBaseSerializer.Meta.fields + ('related_creators',) class EntryWithCreatorsBaseSerializer(EntryBaseSerializer): related_creators = RelatedEntryCreatorField( queryset=EntryCreator.objects.all(), source='related_entry_creators', required=False, many=True, ) class Meta(EntryBaseSerializer.Meta): read_only_fields = fields = EntryBaseSerializer.Meta.fields + ('related_creators',) class EntrySerializer(EntryBaseSerializer): """ Serializes an entry with embeded information including list of tags, issues and help types associated with that entry as simple strings. """ tags = CreatableSlugRelatedField( many=True, slug_field='name', queryset=Tag.objects, required=False ) issues = serializers.SlugRelatedField( many=True, slug_field='name', queryset=Issue.objects, required=False ) help_types = serializers.SlugRelatedField( many=True, slug_field='name', queryset=HelpType.objects, required=False ) # overrides 'published_by' for REST purposes # as we don't want to expose any user's email address published_by = serializers.SlugRelatedField( source='published_by.profile', slug_field='name', read_only=True, ) # "virtual" property so that we can link to the correct profile submitter_profile_id = serializers.SlugRelatedField( source='published_by.profile', slug_field='id', read_only=True, ) bookmark_count = serializers.SerializerMethodField() def get_bookmark_count(self, instance): """ Get the total number of bookmarks this entry received """ return instance.bookmarked_by.count() class Meta: """ Meta class. Because """ model = Entry exclude = ( 'internal_notes', ) class EntrySerializerWithCreators(EntrySerializer): related_creators = RelatedEntryCreatorField( queryset=EntryCreator.objects.all(), source='related_entry_creators', required=False, many=True, ) def create(self, validated_data): """ We override the create method to make sure we save related creators as well and setup the relationship with the created entry. """ user = self.context.get('user') creator_data = validated_data.pop('related_entry_creators', []) entry = super().create(validated_data) if user and entry.published_by_creator: creator_data.append({'profile': user.profile}) for data in creator_data: if not data.pop('profile_committed', True): data['profile'].save() try: with transaction.atomic(): EntryCreator.objects.create(entry=entry, **data) except IntegrityError: # We ignore duplicate key violations so that only single # relations exist between entries and profiles. # This exception might be thrown if the published_by_creator # field is True and the provided user's profile is also included # in the `related_creators` pass return entry class EntrySerializerWithV1Creators(EntrySerializerWithCreators): related_creators = RelatedEntryCreatorV1Field( queryset=EntryCreator.objects.all(), source='related_entry_creators', required=False, many=True, )