addons-server/apps/api/handlers.py

359 строки
11 KiB
Python

import functools
import json
from django.conf import settings
from django.db import transaction
import commonware.log
import happyforms
from piston.handler import AnonymousBaseHandler, BaseHandler
from piston.utils import rc
from tower import ugettext as _
import waffle
import amo
from access import acl
from addons.forms import AddonForm
from addons.models import Addon, AddonUser
from amo.utils import paginate
from devhub.forms import LicenseForm, NewManifestForm
from devhub import tasks
from files.models import FileUpload, Platform
from users.models import UserProfile
from versions.forms import XPIForm
from versions.models import Version, ApplicationsVersions
from webapps.models import Webapp
log = commonware.log.getLogger('z.api')
def check_addon_and_version(f):
"""
Decorator that checks that an addon, and version exist and belong to the
request user.
"""
@functools.wraps(f)
def wrapper(*args, **kw):
request = args[1]
addon_id = kw['addon_id']
try:
addon = Addon.objects.id_or_slug(addon_id).get()
except:
return rc.NOT_HERE
if not acl.check_addon_ownership(request, addon, viewer=True):
return rc.FORBIDDEN
if 'version_id' in kw:
try:
version = Version.objects.get(addon=addon, pk=kw['version_id'])
except Version.DoesNotExist:
return rc.NOT_HERE
return f(*args, addon=addon, version=version)
else:
return f(*args, addon=addon)
return wrapper
def _form_error(f):
resp = rc.BAD_REQUEST
error = ','.join(['%s (%s)' % (v[0], k) for k, v in f.errors.iteritems()])
resp.write(': ' +
# L10n: {0} is comma separated data errors.
_(u'Invalid data provided: {0}').format(error))
log.debug(error)
return resp
def _xpi_form_error(f, request):
resp = rc.BAD_REQUEST
error = ','.join([e[0] for e in f.errors.values()])
resp.write(': ' + _('Add-on did not validate: %s') % error)
log.debug('Add-on did not validate (%s) for %s'
% (error, request.amo_user))
return resp
class UserHandler(BaseHandler):
allowed_methods = ('GET',)
model = UserProfile
fields = ('email',)
def read(self, request):
return request.amo_user
class AddonsHandler(BaseHandler):
allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
model = Addon
fields = ('id', 'name', 'eula', 'guid', 'status', 'slug')
exclude = ('highest_status', 'icon_type')
# Custom handler so translated text doesn't look weird
@classmethod
def name(cls, addon):
return addon.name.localized_string if addon.name else ''
# We need multiple validation, so don't use @validate decorators.
@transaction.commit_on_success
def create(self, request):
new_file_form = XPIForm(request, request.POST, request.FILES)
if not new_file_form.is_valid():
return _xpi_form_error(new_file_form, request)
# License can be optional.
license = None
if 'builtin' in request.POST:
license_form = LicenseForm(request.POST)
if not license_form.is_valid():
return _form_error(license_form)
license = license_form.save()
addon = new_file_form.create_addon(license=license)
if not license:
# If there is no license, we push you to step
# 5 so that you can pick one.
addon.submitstep_set.create(step=5)
return addon
@check_addon_and_version
def update(self, request, addon):
form = AddonForm(request.PUT, instance=addon)
if not form.is_valid():
return _form_error(form)
a = form.save()
return a
@check_addon_and_version
def delete(self, request, addon):
addon.delete(msg='Deleted via API')
return rc.DELETED
def read(self, request, addon_id=None):
"""
Returns authors who can update an addon (not Viewer role) for addons
that have not been admin disabled. Optionally provide an addon id.
"""
if not request.user.is_authenticated():
return rc.BAD_REQUEST
ids = (AddonUser.objects.values_list('addon_id', flat=True)
.filter(user=request.amo_user,
role__in=[amo.AUTHOR_ROLE_DEV,
amo.AUTHOR_ROLE_OWNER]))
qs = (Addon.objects.filter(id__in=ids)
.exclude(status=amo.STATUS_DISABLED)
.no_transforms())
if addon_id:
try:
return qs.get(id=addon_id)
except Addon.DoesNotExist:
rc.NOT_HERE
paginator = paginate(request, qs)
return {'objects': paginator.object_list,
'num_pages': paginator.paginator.num_pages,
'count': paginator.paginator.count}
class AppsHandler(AddonsHandler):
allowed_methods = ('GET', 'POST')
model = Webapp
fields = ('id', 'name', 'manifest_url', 'status', 'app_slug')
exclude = ('highest_status', 'icon_type')
@transaction.commit_on_success
def create(self, request):
if not waffle.flag_is_active(request, 'accept-webapps'):
return rc.BAD_REQUEST
form = NewManifestForm(request.POST)
if form.is_valid():
# This feels like an awful lot of work.
# But first upload the file and do the validation.
upload = FileUpload.objects.create()
tasks.fetch_manifest(form.cleaned_data['manifest'], upload.pk)
# We must reget the object here since the above has
# saved changes to the object.
upload = FileUpload.uncached.get(pk=upload.pk)
# Check it validated correctly.
if settings.VALIDATE_ADDONS:
validation = json.loads(upload.validation)
if validation['errors']:
response = rc.BAD_REQUEST
response.write(validation)
return response
# Fetch the addon, the icon and set the user.
addon = Addon.from_upload(upload,
[Platform.objects.get(id=amo.PLATFORM_ALL.id)])
tasks.fetch_icon(addon)
AddonUser(addon=addon, user=request.amo_user).save()
addon.update(status=amo.STATUS_PENDING if
settings.WEBAPPS_RESTRICTED else amo.STATUS_PUBLIC)
else:
return _form_error(form)
return addon
class ApplicationsVersionsHandler(AnonymousBaseHandler):
model = ApplicationsVersions
allowed_methods = ('GET', )
fields = ('application', 'max', 'min')
@classmethod
def application(cls, av):
return unicode(av.application)
@classmethod
def max(cls, av):
return av.max.version
@classmethod
def min(cls, av):
return av.min.version
class BaseVersionHandler(object):
# Custom handler so translated text doesn't look weird
@classmethod
def release_notes(cls, version):
if version.releasenotes:
return version.releasenotes.localized_string
@classmethod
def license(cls, version):
if version.license:
return unicode(version.license)
@classmethod
def current(cls, version):
return (version.id == version.addon._current_version_id)
class AnonymousVersionsHandler(AnonymousBaseHandler, BaseVersionHandler):
model = Version
allowed_methods = ('GET',)
fields = ('id', 'addon_id', 'created', 'release_notes', 'version',
'license', 'current', 'apps')
def read(self, request, addon_id, version_id=None):
if version_id:
try:
return Version.objects.get(pk=version_id)
except:
return rc.NOT_HERE
try:
addon = Addon.objects.id_or_slug(addon_id).get()
except:
return rc.NOT_HERE
return addon.versions.all()
class VersionsHandler(BaseHandler, BaseVersionHandler):
allowed_methods = ('POST', 'PUT', 'DELETE', 'GET')
model = Version
fields = AnonymousVersionsHandler.fields + ('statuses',)
exclude = ('approvalnotes', )
anonymous = AnonymousVersionsHandler
@check_addon_and_version
def create(self, request, addon):
new_file_form = XPIForm(request, request.POST, request.FILES,
addon=addon)
if not new_file_form.is_valid():
return _xpi_form_error(new_file_form, request)
license = None
if 'builtin' in request.POST:
license_form = LicenseForm(request.POST)
if not license_form.is_valid():
return _form_error(license_form)
license = license_form.save()
v = new_file_form.create_version(license=license)
return v
@check_addon_and_version
def update(self, request, addon, version):
new_file_form = XPIForm(request, request.PUT, request.FILES,
version=version)
if not new_file_form.is_valid():
return _xpi_form_error(new_file_form, request)
license = None
if 'builtin' in request.POST:
license_form = LicenseForm(request.POST)
if not license_form.is_valid():
return _form_error(license_form)
license = license_form.save()
v = new_file_form.update_version(license)
return v
@check_addon_and_version
def delete(self, request, addon, version):
version.delete()
return rc.DELETED
@check_addon_and_version
def read(self, request, addon, version=None):
return version if version else addon.versions.all()
class AMOBaseHandler(BaseHandler):
"""
A generic Base Handler that automates create, delete, read and update.
For list, we use a pagination handler rather than just returning all.
For list, if an id is given, only one object is returned.
For delete and update the id of the record is required.
"""
def get_form(self, *args, **kw):
class Form(happyforms.ModelForm):
class Meta:
model = self.model
return Form(*args, **kw)
def delete(self, request, id):
try:
return self.model.objects.get(pk=id).delete()
except self.model.DoesNotExist:
return rc.NOT_HERE
def create(self, request):
form = self.get_form(request.POST)
if form.is_valid():
return form.save()
return _form_error(form)
def read(self, request, id=None):
if id:
try:
return self.model.objects.get(pk=id)
except self.model.DoesNotExist:
return rc.NOT_HERE
else:
paginator = paginate(request, self.model.objects.all())
return {'objects': paginator.object_list,
'num_pages': paginator.paginator.num_pages,
'count': paginator.paginator.count}
def update(self, request, id):
try:
obj = self.model.objects.get(pk=id)
except self.model.DoesNotExist:
return rc.NOT_HERE
form = self.get_form(request.POST, instance=obj)
if form.is_valid():
form.save()
return rc.ALL_OK
return _form_error(form)