add gettext lookups for addons api ValidationErrors (#19115)
This commit is contained in:
Родитель
ee02eed9c8
Коммит
9a11501da4
|
@ -7,6 +7,7 @@ from urllib.parse import urlsplit, urlunsplit
|
|||
|
||||
from django.conf import settings
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.translation import gettext
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import fields, exceptions, serializers
|
||||
|
@ -39,12 +40,17 @@ class CategoriesSerializerField(serializers.Field):
|
|||
for app_name, category_names in data.items():
|
||||
if len(category_names) > amo.MAX_CATEGORIES:
|
||||
raise exceptions.ValidationError(
|
||||
'Maximum number of categories per application '
|
||||
f'({amo.MAX_CATEGORIES}) exceeded'
|
||||
gettext(
|
||||
'Maximum number of categories per application '
|
||||
'({MAX_CATEGORIES}) exceeded'
|
||||
).format(MAX_CATEGORIES=amo.MAX_CATEGORIES)
|
||||
)
|
||||
if len(category_names) > 1 and 'other' in category_names:
|
||||
raise exceptions.ValidationError(
|
||||
'The "other" category cannot be combined with another category'
|
||||
gettext(
|
||||
'The "other" category cannot be combined with another '
|
||||
'category'
|
||||
)
|
||||
)
|
||||
app_cats = CATEGORIES[APPS[app_name].id]
|
||||
# We don't know the addon_type at this point, so try them all and we'll
|
||||
|
@ -57,10 +63,10 @@ class CategoriesSerializerField(serializers.Field):
|
|||
all_cat_slugs.update(type_cats.keys())
|
||||
# Now double-check all the category names were found
|
||||
if not all_cat_slugs.issuperset(category_names):
|
||||
raise exceptions.ValidationError('Invalid category name.')
|
||||
raise exceptions.ValidationError(gettext('Invalid category name.'))
|
||||
return categories
|
||||
except KeyError:
|
||||
raise exceptions.ValidationError('Invalid app name.')
|
||||
raise exceptions.ValidationError(gettext('Invalid app name.'))
|
||||
|
||||
def to_representation(self, value):
|
||||
grouped = sorted_groupby(
|
||||
|
@ -100,16 +106,19 @@ class ContributionSerializerField(OutgoingURLField):
|
|||
|
||||
if parsed_url.hostname not in amo.VALID_CONTRIBUTION_DOMAINS:
|
||||
errors.append(
|
||||
'URL domain must be one of '
|
||||
f'[{", ".join(amo.VALID_CONTRIBUTION_DOMAINS)}].'
|
||||
gettext('URL domain must be one of [{domains}].').format(
|
||||
domains=', '.join(amo.VALID_CONTRIBUTION_DOMAINS)
|
||||
)
|
||||
)
|
||||
elif parsed_url.hostname == 'github.com' and not parsed_url.path.startswith(
|
||||
'/sponsors/'
|
||||
):
|
||||
# Issue 15497, validate path for GitHub Sponsors
|
||||
errors.append('URL path for GitHub Sponsors must contain /sponsors/.')
|
||||
errors.append(
|
||||
gettext('URL path for GitHub Sponsors must contain /sponsors/.')
|
||||
)
|
||||
if parsed_url.scheme != 'https':
|
||||
errors.append('URLs must start with https://.')
|
||||
errors.append(gettext('URLs must start with https://.'))
|
||||
|
||||
if errors:
|
||||
raise exceptions.ValidationError(errors)
|
||||
|
@ -195,7 +204,7 @@ class SourceFileField(serializers.FileField):
|
|||
|
||||
# Ensure the file type is one we support.
|
||||
if not data.name.endswith(VALID_SOURCE_EXTENSIONS):
|
||||
error_msg = (
|
||||
error_msg = gettext(
|
||||
'Unsupported file type, please upload an archive file ({extensions}).'
|
||||
)
|
||||
raise exceptions.ValidationError(
|
||||
|
@ -216,7 +225,7 @@ class SourceFileField(serializers.FileField):
|
|||
for member in archive.getmembers():
|
||||
archive_member_validator(archive, member)
|
||||
except (zipfile.BadZipFile, tarfile.ReadError, OSError, EOFError):
|
||||
raise exceptions.ValidationError('Invalid or broken archive.')
|
||||
raise exceptions.ValidationError(gettext('Invalid or broken archive.'))
|
||||
|
||||
return data
|
||||
|
||||
|
@ -240,7 +249,7 @@ class VersionCompatabilityField(serializers.Field):
|
|||
data = {key: {} for key in data}
|
||||
if not isinstance(data, dict) or data == {}:
|
||||
# if it's neither it's not a valid input
|
||||
raise exceptions.ValidationError('Invalid value')
|
||||
raise exceptions.ValidationError(gettext('Invalid value'))
|
||||
|
||||
version = self.parent.instance
|
||||
existing = version.compatible_apps if version else {}
|
||||
|
@ -270,9 +279,9 @@ class VersionCompatabilityField(serializers.Field):
|
|||
|
||||
internal[app] = apps_versions
|
||||
except KeyError:
|
||||
raise exceptions.ValidationError('Invalid app specified')
|
||||
raise exceptions.ValidationError(gettext('Invalid app specified'))
|
||||
except AppVersion.DoesNotExist:
|
||||
raise exceptions.ValidationError('Unknown app version specified')
|
||||
raise exceptions.ValidationError(gettext('Unknown app version specified'))
|
||||
|
||||
return internal
|
||||
|
||||
|
@ -306,15 +315,18 @@ class ImageField(serializers.ImageField):
|
|||
image_check = ImageCheck(data)
|
||||
|
||||
if data.content_type not in amo.IMG_TYPES or not image_check.is_image():
|
||||
raise serializers.ValidationError('Images must be either PNG or JPG.')
|
||||
raise exceptions.ValidationError(
|
||||
gettext('Images must be either PNG or JPG.')
|
||||
)
|
||||
errors = []
|
||||
|
||||
if image_check.is_animated():
|
||||
errors.append('Images cannot be animated.')
|
||||
errors.append(gettext('Images cannot be animated.'))
|
||||
|
||||
if data.size > self.max_size:
|
||||
errors.append(
|
||||
'Images must be smaller than %dMB' % (self.max_size / 1024 / 1024)
|
||||
gettext('Images must be smaller than %dMB')
|
||||
% (self.max_size / 1024 / 1024)
|
||||
)
|
||||
|
||||
icon_size = image_check.size
|
||||
|
@ -322,6 +334,6 @@ class ImageField(serializers.ImageField):
|
|||
errors.append('Images must be square (same width and height).')
|
||||
|
||||
if errors:
|
||||
raise serializers.ValidationError(errors)
|
||||
raise exceptions.ValidationError(errors)
|
||||
|
||||
return data
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from django.utils.translation import gettext
|
||||
from django.urls import reverse
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
|
@ -254,7 +255,9 @@ class LicenseSerializer(serializers.ModelSerializer):
|
|||
|
||||
def validate(self, data):
|
||||
if self.instance and not self.get_is_custom(self.instance):
|
||||
raise exceptions.ValidationError('Built in licenses can not be updated.')
|
||||
raise exceptions.ValidationError(
|
||||
gettext('Built in licenses can not be updated.')
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
|
@ -410,7 +413,7 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
request = self.context.get('request')
|
||||
own_upload = request and request.user == value.user
|
||||
if not own_upload or not value.valid or value.validation_timeout:
|
||||
raise exceptions.ValidationError('Upload is not valid.')
|
||||
raise exceptions.ValidationError(gettext('Upload is not valid.'))
|
||||
# Parse the file to get and validate package data with the addon.
|
||||
self.parsed_data = parse_addon(value, addon=self.addon, user=request.user)
|
||||
return value
|
||||
|
@ -419,7 +422,7 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
# check the guid/version isn't in the addon blocklist
|
||||
block_qs = Block.objects.filter(guid=guid) if guid else ()
|
||||
if block_qs and block_qs.first().is_version_blocked(version_string):
|
||||
msg = (
|
||||
msg = gettext(
|
||||
'Version {version} matches {block_link} for this add-on. '
|
||||
'You can contact {amo_admins} for additional information.'
|
||||
)
|
||||
|
@ -438,10 +441,14 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
)
|
||||
if existing_versions.exists():
|
||||
if existing_versions[0].deleted:
|
||||
msg = f'Version {version_string} was uploaded before and deleted.'
|
||||
msg = gettext(
|
||||
'Version {version_string} was uploaded before and deleted.'
|
||||
)
|
||||
else:
|
||||
msg = f'Version {version_string} already exists.'
|
||||
raise exceptions.ValidationError({'version': msg})
|
||||
msg = gettext('Version {version_string} already exists.')
|
||||
raise exceptions.ValidationError(
|
||||
{'version': msg.format(version_string=version_string)}
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
if not self.instance:
|
||||
|
@ -457,7 +464,10 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
if channel == amo.RELEASE_CHANNEL_LISTED and self.addon:
|
||||
if self.addon.disabled_by_user:
|
||||
raise exceptions.ValidationError(
|
||||
'Listed versions cannot be submitted while add-on is disabled.'
|
||||
gettext(
|
||||
'Listed versions cannot be submitted while add-on is '
|
||||
'disabled.'
|
||||
)
|
||||
)
|
||||
# This is replicating what Addon.get_required_metadata does
|
||||
missing_addon_metadata = [
|
||||
|
@ -471,8 +481,10 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
]
|
||||
if missing_addon_metadata:
|
||||
raise exceptions.ValidationError(
|
||||
'Addon metadata is required to be set to create a listed '
|
||||
f'version: {missing_addon_metadata}.',
|
||||
gettext(
|
||||
'Addon metadata is required to be set to create a listed '
|
||||
'version: {missing_addon_metadata}.'
|
||||
).format(missing_addon_metadata=missing_addon_metadata),
|
||||
code='required',
|
||||
)
|
||||
else:
|
||||
|
@ -893,7 +905,7 @@ class AddonSerializer(serializers.ModelSerializer):
|
|||
# DeniedSlug.blocked checks for all numeric slugs as well as being denied.
|
||||
if DeniedSlug.blocked(value):
|
||||
raise exceptions.ValidationError(
|
||||
'This slug cannot be used. Please choose another.'
|
||||
gettext('This slug cannot be used. Please choose another.')
|
||||
)
|
||||
|
||||
return value
|
||||
|
@ -908,7 +920,9 @@ class AddonSerializer(serializers.ModelSerializer):
|
|||
# We test for new versions in VersionSerailizer.validate instead
|
||||
if channel == amo.RELEASE_CHANNEL_LISTED:
|
||||
# This is replicating what Addon.get_required_metadata does
|
||||
required_msg = 'This field is required for addons with listed versions.'
|
||||
required_msg = gettext(
|
||||
'This field is required for addons with listed versions.'
|
||||
)
|
||||
missing_metadata = {
|
||||
field: required_msg
|
||||
for field, value in (
|
||||
|
@ -932,7 +946,7 @@ class AddonSerializer(serializers.ModelSerializer):
|
|||
# double check we didn't lose any
|
||||
if slugs != {cat.slug for cat in data['all_categories']}:
|
||||
raise exceptions.ValidationError(
|
||||
{'categories': 'Invalid category name.'}
|
||||
{'categories': gettext('Invalid category name.')}
|
||||
)
|
||||
return data
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django import forms
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from rest_framework import exceptions
|
||||
|
||||
|
@ -33,7 +34,9 @@ class ValidateVersionLicense:
|
|||
and 'custom_license' in request.data
|
||||
):
|
||||
raise exceptions.ValidationError(
|
||||
'Both `license` and `custom_license` cannot be provided together.'
|
||||
gettext(
|
||||
'Both `license` and `custom_license` cannot be provided together.'
|
||||
)
|
||||
)
|
||||
|
||||
license_ = data.get('license')
|
||||
|
@ -54,8 +57,10 @@ class ValidateVersionLicense:
|
|||
raise exceptions.ValidationError(
|
||||
{
|
||||
'license': (
|
||||
'This field, or custom_license, is required for listed '
|
||||
'versions.'
|
||||
gettext(
|
||||
'This field, or custom_license, is required for '
|
||||
'listed versions.'
|
||||
)
|
||||
)
|
||||
},
|
||||
code='required',
|
||||
|
@ -82,10 +87,14 @@ class ValidateVersionLicense:
|
|||
is_theme = addon_type == amo.ADDON_STATICTHEME
|
||||
if isinstance(license_, License) and license_.creative_commons != is_theme:
|
||||
raise exceptions.ValidationError(
|
||||
{'license': 'Wrong addon type for this license.'},
|
||||
{'license': gettext('Wrong addon type for this license.')},
|
||||
code='required',
|
||||
)
|
||||
if is_custom and is_theme:
|
||||
raise exceptions.ValidationError(
|
||||
{'custom_license': 'Custom licenses are not supported for themes.'},
|
||||
{
|
||||
'custom_license': gettext(
|
||||
'Custom licenses are not supported for themes.'
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django import http, shortcuts
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from rest_framework import exceptions, status
|
||||
from rest_framework.mixins import CreateModelMixin
|
||||
|
@ -77,13 +78,13 @@ class FileUploadViewSet(CreateModelMixin, ReadOnlyModelViewSet):
|
|||
filedata = request.FILES['upload']
|
||||
else:
|
||||
raise exceptions.ValidationError(
|
||||
'Missing "upload" key in multipart file data.',
|
||||
gettext('Missing "upload" key in multipart file data.'),
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
channel = amo.CHANNEL_CHOICES_LOOKUP.get(request.POST.get('channel'))
|
||||
if not channel:
|
||||
raise exceptions.ValidationError(
|
||||
'Missing "channel" arg.',
|
||||
gettext('Missing "channel" arg.'),
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче