class UserAbuseReportSerializer(serializers.ModelSerializer): reporter = BaseUserSerializer(read_only=True) user = BaseUserSerializer() class Meta: model = AbuseReport fields = ('reporter', 'user', 'message')
class ActivityLogSerializer(serializers.ModelSerializer): action = serializers.SerializerMethodField() action_label = serializers.SerializerMethodField() comments = serializers.SerializerMethodField() date = serializers.DateTimeField(source='created') user = BaseUserSerializer() highlight = serializers.SerializerMethodField() class Meta: model = ActivityLog fields = ('id', 'action', 'action_label', 'comments', 'user', 'date', 'highlight') def __init__(self, *args, **kwargs): super(ActivityLogSerializer, self).__init__(*args, **kwargs) self.to_highlight = kwargs.get('context', []).get('to_highlight', []) def get_comments(self, obj): return getattr(obj.log(), 'sanitize', obj.details['comments']) def get_action_label(self, obj): log = obj.log() return _(u'Review note') if not hasattr(log, 'short') else log.short def get_action(self, obj): return self.get_action_label(obj).replace(' ', '-').lower() def get_highlight(self, obj): return obj in self.to_highlight
class ActivityLogSerializer(serializers.ModelSerializer): action = serializers.SerializerMethodField() action_label = serializers.SerializerMethodField() comments = serializers.SerializerMethodField() date = serializers.DateTimeField(source='created') user = BaseUserSerializer() highlight = serializers.SerializerMethodField() class Meta: model = ActivityLog fields = ('id', 'action', 'action_label', 'comments', 'user', 'date', 'highlight') def __init__(self, *args, **kwargs): super(ActivityLogSerializer, self).__init__(*args, **kwargs) self.to_highlight = kwargs.get('context', []).get('to_highlight', []) def get_comments(self, obj): # We have to do .unescape on the output of obj.to_string because the # 'string' it returns is supposed to be used in markup and doesn't get # serialized correctly unless we unescape it. comments = (obj.details['comments'] if obj.details else obj.to_string().unescape()) return getattr(obj.log(), 'sanitize', comments) def get_action_label(self, obj): log = obj.log() return _(u'Review note') if not hasattr(log, 'short') else log.short def get_action(self, obj): return self.get_action_label(obj).replace(' ', '-').lower() def get_highlight(self, obj): return obj in self.to_highlight
class BaseReviewSerializer(serializers.ModelSerializer): # title and body are TranslatedFields, but there is never more than one # translation for each review - it's essentially useless. Because of that # we use a simple CharField in the API, hiding the fact that it's a # TranslatedField underneath. addon = serializers.SerializerMethodField() body = serializers.CharField(allow_null=True, required=False) is_latest = serializers.BooleanField(read_only=True) previous_count = serializers.IntegerField(read_only=True) title = serializers.CharField(allow_null=True, required=False) user = BaseUserSerializer(read_only=True) class Meta: model = Review fields = ('id', 'addon', 'body', 'created', 'is_latest', 'previous_count', 'title', 'user') def get_addon(self, obj): # We only return the addon id and slug for convenience, so just return # them directly to avoid instantiating a full serializer. Also avoid # database queries if possible by re-using the addon object from the # view if there is one. addon = self.context['view'].get_addon_object() or obj.addon return {'id': addon.id, 'slug': addon.slug} def validate(self, data): data = super(BaseReviewSerializer, self).validate(data) request = self.context['request'] data['user_responsible'] = request.user if not self.partial: # Get the add-on pk from the URL, no need to pass it as POST data # since the URL is always going to have it. data['addon'] = self.context['view'].get_addon_object() # Get the user from the request, don't allow clients to pick one # themselves. data['user'] = request.user # Also include the user ip adress. data['ip_address'] = request.META.get('REMOTE_ADDR', '') # Clean up body and automatically flag the review if an URL was in it. body = data.get('body', '') if body: if '<br>' in body: data['body'] = re.sub('<br>', '\n', body) # Unquote the body when searching for links, in case someone tries # 'example%2ecom'. if ReviewForm.link_pattern.search(unquote(body)) is not None: data['flag'] = True data['editorreview'] = True return data
def test_user_report(self): user = user_factory() report = AbuseReport(user=user, message='bad stuff') serial = self.serialize(report) user_serial = BaseUserSerializer(user).data assert serial == { 'reporter': None, 'user': user_serial, 'message': 'bad stuff' }
class CollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField(required=False) url = serializers.SerializerMethodField() author = BaseUserSerializer(default=serializers.CurrentUserDefault()) public = serializers.BooleanField(source='listed', default=True) class Meta: model = Collection fields = ('id', 'uuid', 'url', 'addon_count', 'author', 'description', 'modified', 'name', 'slug', 'public', 'default_locale') writeable_fields = ('description', 'name', 'slug', 'public', 'default_locale') read_only_fields = tuple(set(fields) - set(writeable_fields)) validators = [ UniqueTogetherValidator( queryset=Collection.objects.all(), message=_(u'This slug is already in use by another one ' u'of your collections.'), fields=('slug', 'author')) ] def get_url(self, obj): return obj.get_abs_url() def validate_name(self, value): # if we have a localised dict of values validate them all. if isinstance(value, dict): return { locale: self.validate_name(sub_value) for locale, sub_value in value.iteritems() } if DeniedName.blocked(value): raise serializers.ValidationError( ugettext(u'This name cannot be used.')) return value def validate_description(self, value): if has_links(clean_nl(unicode(value))): # There's some links, we don't want them. raise serializers.ValidationError( ugettext(u'No links are allowed.')) return value def validate_slug(self, value): slug_validator(value, lower=False, message=ugettext( u'Enter a valid slug consisting of letters, ' u'numbers, underscores or hyphens.')) if DeniedName.blocked(value): raise serializers.ValidationError( ugettext(u'This slug cannot be used.')) return value
class SimpleCollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField() url = serializers.SerializerMethodField() author = BaseUserSerializer() class Meta: model = Collection fields = ('id', 'url', 'addon_count', 'author', 'description', 'modified', 'name') def get_url(self, obj): return obj.get_abs_url()
class AddonAbuseReportSerializer(serializers.ModelSerializer): addon = serializers.SerializerMethodField() reporter = BaseUserSerializer(read_only=True) class Meta: model = AbuseReport fields = ('reporter', 'addon', 'message') def get_addon(self, obj): addon = obj.addon if not addon and not obj.guid: return None return { 'guid': addon.guid if addon else obj.guid, 'id': addon.id if addon else None, 'slug': addon.slug if addon else None, }
class ActivityLogSerializer(serializers.ModelSerializer): action = serializers.SerializerMethodField() action_label = serializers.SerializerMethodField() comments = serializers.SerializerMethodField() date = serializers.DateTimeField(source='created') user = BaseUserSerializer() class Meta: model = ActivityLog fields = ('id', 'action', 'action_label', 'comments', 'user', 'date') def get_comments(self, obj): return obj.details['comments'] def get_action_label(self, obj): log = obj.log() return _(u'Review note') if not hasattr(log, 'short') else log.short def get_action(self, obj): return self.get_action_label(obj).replace(' ', '-').lower()
class BaseReviewSerializer(serializers.ModelSerializer): # title and body are TranslatedFields, but there is never more than one # translation for each review - it's essentially useless. Because of that # we use a simple CharField in the API, hiding the fact that it's a # TranslatedField underneath. body = serializers.CharField(allow_null=True, required=False) title = serializers.CharField(allow_null=True, required=False) user = BaseUserSerializer(read_only=True) class Meta: model = Review fields = ('id', 'body', 'created', 'title', 'user') def validate(self, data): data = super(BaseReviewSerializer, self).validate(data) # Get the add-on pk from the URL, no need to pass it as POST data since # the URL is always going to have it. data['addon'] = self.context['view'].get_addon_object() # Get the user from the request, don't allow clients to pick one # themselves. data['user'] = self.context['request'].user return data
class AddonSerializer(serializers.ModelSerializer): authors = BaseUserSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() current_beta_version = SimpleVersionSerializer() current_version = SimpleVersionSerializer() description = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() is_source_public = serializers.BooleanField(source='view_source') name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='all_previews') ratings = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() theme_data = serializers.SerializerMethodField() type = ReverseChoiceField(choices=amo.ADDON_TYPE_CHOICES_API.items()) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ('id', 'authors', 'average_daily_users', 'categories', 'current_beta_version', 'current_version', 'default_locale', 'description', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'is_disabled', 'is_experimental', 'is_source_public', 'last_updated', 'name', 'previews', 'public_stats', 'ratings', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'theme_data', 'type', 'url', 'weekly_downloads') def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) if 'theme_data' in data and data['theme_data'] is None: data.pop('theme_data') if 'homepage' in data: data['homepage'] = self.outgoingify(data['homepage']) if 'support_url' in data: data['support_url'] = self.outgoingify(data['support_url']) return data def outgoingify(self, data): if isinstance(data, basestring): return get_outgoing_url(data) elif isinstance(data, dict): return { key: get_outgoing_url(value) if value else None for key, value in data.items() } # Probably None... don't bother. return data def get_categories(self, obj): # Return a dict of lists like obj.app_categories does, but exposing # slugs for keys and values instead of objects. return { app.short: [cat.slug for cat in obj.app_categories[app]] for app in obj.app_categories.keys() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): return absolutify(obj.get_url_path()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_review_url(self, obj): return absolutify(reverse('editors.review', args=[obj.pk])) def get_icon_url(self, obj): if self.is_broken_persona(obj): return absolutify(obj.get_default_icon_url(64)) return absolutify(obj.get_icon_url(64)) def get_ratings(self, obj): return { 'average': obj.average_rating, 'count': obj.total_reviews, } def get_theme_data(self, obj): theme_data = None if obj.type == amo.ADDON_PERSONA and not self.is_broken_persona(obj): theme_data = obj.persona.theme_data return theme_data def is_broken_persona(self, obj): """Find out if the object is a Persona and either is missing its Persona instance or has a broken one. Call this everytime something in the serializer is suceptible to call something on the Persona instance, explicitly or not, to avoid 500 errors and/or SQL queries in ESAddonSerializer.""" try: # Sadly, https://code.djangoproject.com/ticket/14368 prevents us # from setting obj.persona = None in ESAddonSerializer.fake_object # below. This is fixed in Django 1.9, but in the meantime we work # around it by creating a Persona instance with a custom '_broken' # attribute indicating that it should not be used. if obj.type == amo.ADDON_PERSONA and ( obj.persona is None or hasattr(obj.persona, '_broken')): raise Persona.DoesNotExist except Persona.DoesNotExist: # We got a DoesNotExist exception, therefore the Persona does not # exist or is broken. return True # Everything is fine, move on. return False
def serialize(self): # Manually reload the user first to clear any cached properties. self.user = UserProfile.objects.get(pk=self.user.pk) serializer = BaseUserSerializer(context={'request': self.request}) return serializer.to_representation(self.user)
class ESAddonSerializer(BaseESSerializer, AddonSerializer): # Override various fields for related objects which we don't want to expose # data the same way than the regular serializer does (usually because we # some of the data is not indexed in ES). authors = BaseUserSerializer(many=True, source='listed_authors') current_beta_version = SimpleESVersionSerializer() current_version = SimpleESVersionSerializer() previews = ESPreviewSerializer(many=True, source='all_previews') datetime_fields = ('created', 'last_updated', 'modified') translated_fields = ('name', 'description', 'developer_comments', 'homepage', 'summary', 'support_email', 'support_url') def fake_file_object(self, obj, data): file_ = File(id=data['id'], created=self.handle_date(data['created']), hash=data['hash'], filename=data['filename'], is_webextension=data.get('is_webextension'), is_restart_required=data.get('is_restart_required', False), platform=data['platform'], size=data['size'], status=data['status'], strict_compatibility=data.get('strict_compatibility', False), version=obj) file_.webext_permissions_list = data.get('webext_permissions_list', []) return file_ def fake_version_object(self, obj, data, channel): if data: version = Version(addon=obj, id=data['id'], reviewed=self.handle_date(data['reviewed']), version=data['version'], channel=channel) version.all_files = [ self.fake_file_object(version, file_data) for file_data in data.get('files', []) ] # In ES we store integers for the appversion info, we need to # convert it back to strings. compatible_apps = {} for app_id, compat_dict in data.get('compatible_apps', {}).items(): app_name = APPS_ALL[int(app_id)] compatible_apps[app_name] = ApplicationsVersions( min=AppVersion(version=compat_dict.get('min_human', '')), max=AppVersion(version=compat_dict.get('max_human', ''))) version.compatible_apps = compatible_apps else: version = None return version def fake_object(self, data): """Create a fake instance of Addon and related models from ES data.""" obj = Addon(id=data['id'], slug=data['slug']) # Attach base attributes that have the same name/format in ES and in # the model. self._attach_fields( obj, data, ('average_daily_users', 'bayesian_rating', 'created', 'default_locale', 'guid', 'has_eula', 'has_privacy_policy', 'hotness', 'icon_type', 'is_experimental', 'last_updated', 'modified', 'public_stats', 'requires_payment', 'slug', 'status', 'type', 'view_source', 'weekly_downloads')) # Attach attributes that do not have the same name/format in ES. obj.tag_list = data.get('tags', []) obj.all_categories = [ CATEGORIES_BY_ID[cat_id] for cat_id in data.get('category', []) ] # Not entirely accurate, but enough in the context of the search API. obj.disabled_by_user = data.get('is_disabled', False) # Attach translations (they require special treatment). self._attach_translations(obj, data, self.translated_fields) # Attach related models (also faking them). `current_version` is a # property we can't write to, so we use the underlying field which # begins with an underscore. `current_beta_version` and # `latest_unlisted_version` are writeable cached_property so we can # directly write to them. obj.current_beta_version = self.fake_version_object( obj, data.get('current_beta_version'), amo.RELEASE_CHANNEL_LISTED) obj._current_version = self.fake_version_object( obj, data.get('current_version'), amo.RELEASE_CHANNEL_LISTED) obj.latest_unlisted_version = self.fake_version_object( obj, data.get('latest_unlisted_version'), amo.RELEASE_CHANNEL_UNLISTED) data_authors = data.get('listed_authors', []) obj.listed_authors = [ UserProfile(id=data_author['id'], display_name=data_author['name'], username=data_author['username'], is_public=data_author.get('is_public', False)) for data_author in data_authors ] # We set obj.all_previews to the raw preview data because # ESPreviewSerializer will handle creating the fake Preview object # for us when its to_representation() method is called. obj.all_previews = data.get('previews', []) ratings = data.get('ratings', {}) obj.average_rating = ratings.get('average') obj.total_reviews = ratings.get('count') obj.text_reviews_count = ratings.get('text_count') obj._is_featured = data.get('is_featured', False) if data['type'] == amo.ADDON_PERSONA: persona_data = data.get('persona') if persona_data: obj.persona = Persona( addon=obj, accentcolor=persona_data['accentcolor'], display_username=persona_data['author'], header=persona_data['header'], footer=persona_data['footer'], # "New" Persona do not have a persona_id, it's a relic from # old ones. persona_id=0 if persona_data['is_new'] else 42, textcolor=persona_data['textcolor']) else: # Sadly, https://code.djangoproject.com/ticket/14368 prevents # us from setting obj.persona = None. This is fixed in # Django 1.9, but in the meantime, work around it by creating # a Persona instance with a custom attribute indicating that # it should not be used. obj.persona = Persona() obj.persona._broken = True return obj
class ESAddonSerializerWithUnlistedData(ESBaseAddonSerializer, AddonSerializerWithUnlistedData): # Override authors because we don't want picture_url in serializer. authors = BaseUserSerializer(many=True, source='listed_authors') previews = ESPreviewSerializer(many=True, source='all_previews')
class BaseReviewSerializer(serializers.ModelSerializer): # title and body are TranslatedFields, but there is never more than one # translation for each review - it's essentially useless. Because of that # we use a simple CharField in the API, hiding the fact that it's a # TranslatedField underneath. addon = serializers.SerializerMethodField() body = serializers.CharField(allow_null=True, required=False) is_latest = serializers.BooleanField(read_only=True) previous_count = serializers.IntegerField(read_only=True) title = serializers.CharField(allow_null=True, required=False) user = BaseUserSerializer(read_only=True) class Meta: model = Review fields = ('id', 'addon', 'body', 'created', 'is_latest', 'previous_count', 'title', 'user') def get_addon(self, obj): # We only return the addon id and slug for convenience, so just return # them directly to avoid instantiating a full serializer. Also avoid # database queries if possible by re-using the addon object from the # view if there is one. addon = self.context['view'].get_addon_object() or obj.addon return {'id': addon.id, 'slug': addon.slug} def validate(self, data): data = super(BaseReviewSerializer, self).validate(data) request = self.context['request'] data['user_responsible'] = request.user # There are a few fields that need to be set at creation time and never # modified afterwards: if not self.partial: # Because we want to avoid extra queries, addon is a # SerializerMethodField, which means it needs to be validated # manually. Fortunately the view does most of the work for us. data['addon'] = self.context['view'].get_addon_object() if data['addon'] is None: raise serializers.ValidationError( {'addon': ugettext('This field is required.')}) # Get the user from the request, don't allow clients to pick one # themselves. data['user'] = request.user # Also include the user ip adress. data['ip_address'] = request.META.get('REMOTE_ADDR', '') else: # When editing, you can't change the add-on. if self.context['request'].data.get('addon'): raise serializers.ValidationError({ 'addon': ugettext(u'You can\'t change the add-on of a review once' u' it has been created.') }) # Clean up body and automatically flag the review if an URL was in it. body = data.get('body', '') if body: if '<br>' in body: data['body'] = re.sub('<br>', '\n', body) # Unquote the body when searching for links, in case someone tries # 'example%2ecom'. if ReviewForm.link_pattern.search(unquote(body)) is not None: data['flag'] = True data['editorreview'] = True return data