screenshot and video handling api (bug 755218)
This commit is contained in:
Родитель
3c41c549a6
Коммит
730937b3ac
|
@ -300,6 +300,10 @@ class AMOPaths(object):
|
|||
path = 'mkt/webapps/tests/sample.key'
|
||||
return os.path.join(settings.ROOT, path)
|
||||
|
||||
def mozball_image(self):
|
||||
return os.path.join(settings.ROOT,
|
||||
'mkt/developers/tests/addons/mozball-128.png')
|
||||
|
||||
|
||||
def close_to_now(dt):
|
||||
"""
|
||||
|
|
|
@ -96,25 +96,31 @@ def initialize_oauth_server_request(request):
|
|||
"""
|
||||
|
||||
# Since 'Authorization' header comes through as 'HTTP_AUTHORIZATION',
|
||||
# convert it back
|
||||
# convert it back.
|
||||
auth_header = {}
|
||||
if 'HTTP_AUTHORIZATION' in request.META:
|
||||
auth_header = {'Authorization': request.META.get('HTTP_AUTHORIZATION')}
|
||||
|
||||
url = urljoin(settings.SITE_URL, request.path)
|
||||
|
||||
# Note: we are only signing using the QUERY STRING. We are not signing the
|
||||
# body yet. According to the spec we should be including an oauth_body_hash
|
||||
# as per:
|
||||
#
|
||||
# http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/1/spec.html
|
||||
#
|
||||
# There is no support in python-oauth2 for this yet. There is an
|
||||
# outstanding pull request for this:
|
||||
#
|
||||
# https://github.com/simplegeo/python-oauth2/pull/110
|
||||
#
|
||||
# Or time to move to a better OAuth implementation.
|
||||
oauth_request = oauth2.Request.from_request(
|
||||
request.method, url, headers=auth_header)
|
||||
|
||||
if oauth_request:
|
||||
oauth_server = oauth2.Server(signature_methods={
|
||||
# Supported signature methods
|
||||
'HMAC-SHA1': oauth2.SignatureMethod_HMAC_SHA1()
|
||||
})
|
||||
|
||||
else:
|
||||
oauth_server = None
|
||||
|
||||
request.method, url, headers=auth_header,
|
||||
query_string=request.META['QUERY_STRING'])
|
||||
oauth_server = oauth2.Server(signature_methods={
|
||||
'HMAC-SHA1': oauth2.SignatureMethod_HMAC_SHA1()
|
||||
})
|
||||
return oauth_server, oauth_request
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import json
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from tastypie import http
|
||||
from tastypie.bundle import Bundle
|
||||
from tastypie.exceptions import ImmediateHttpResponse
|
||||
from tastypie.exceptions import ImmediateHttpResponse, NotFound
|
||||
from tastypie.resources import ModelResource
|
||||
|
||||
from translations.fields import PurifiedField, TranslatedField
|
||||
|
||||
|
||||
class MarketplaceResource(ModelResource):
|
||||
|
||||
def get_resource_uri(self, bundle_or_obj):
|
||||
|
@ -49,3 +50,25 @@ class MarketplaceResource(ModelResource):
|
|||
response = http.HttpBadRequest(json.dumps({'error_message': errors}),
|
||||
content_type='application/json')
|
||||
return ImmediateHttpResponse(response=response)
|
||||
|
||||
def get_object_or_404(self, cls, **filters):
|
||||
"""
|
||||
A wrapper around our more familiar get_object_or_404, for when we need
|
||||
to get access to an object that isn't covered by get_obj.
|
||||
"""
|
||||
if not filters:
|
||||
raise ImmediateHttpResponse(response=http.HttpNotFound())
|
||||
try:
|
||||
return cls.objects.get(**filters)
|
||||
except (cls.DoesNotExist, cls.MultipleObjectsReturned):
|
||||
raise ImmediateHttpResponse(response=http.HttpNotFound())
|
||||
|
||||
def get_by_resource_or_404(self, request, **kwargs):
|
||||
"""
|
||||
A wrapper around the obj_get to just get the object.
|
||||
"""
|
||||
try:
|
||||
obj = self.obj_get(request, **kwargs)
|
||||
except ObjectDoesNotExist:
|
||||
raise ImmediateHttpResponse(response=http.HttpNotFound())
|
||||
return obj
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import base64
|
||||
import json
|
||||
import StringIO
|
||||
|
||||
from django import forms
|
||||
|
||||
import happyforms
|
||||
|
||||
from files.models import FileUpload
|
||||
from mkt.developers.utils import check_upload
|
||||
|
||||
|
||||
class UploadForm(happyforms.Form):
|
||||
|
@ -19,3 +24,41 @@ class UploadForm(happyforms.Form):
|
|||
|
||||
self.obj = upload
|
||||
return uuid
|
||||
|
||||
|
||||
class JSONField(forms.Field):
|
||||
def to_python(self, value):
|
||||
if value == '':
|
||||
return None
|
||||
|
||||
try:
|
||||
if isinstance(value, basestring):
|
||||
return json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
class PreviewJSONForm(happyforms.Form):
|
||||
file = JSONField(required=True)
|
||||
position = forms.IntegerField(required=True)
|
||||
|
||||
def clean_file(self):
|
||||
file_ = self.cleaned_data.get('file', {})
|
||||
try:
|
||||
if not set(['data', 'type']).issubset(set(file_.keys())):
|
||||
raise forms.ValidationError('Type and data are required.')
|
||||
except AttributeError:
|
||||
raise forms.ValidationError('File must be a dictionary.')
|
||||
|
||||
file_obj = StringIO.StringIO(base64.b64decode(file_['data']))
|
||||
errors, hash_ = check_upload(file_obj, 'image', file_['type'])
|
||||
if errors:
|
||||
raise forms.ValidationError(errors)
|
||||
|
||||
self.hash_ = hash_
|
||||
return file_
|
||||
|
||||
def clean(self):
|
||||
self.cleaned_data['upload_hash'] = getattr(self, 'hash_', None)
|
||||
return self.cleaned_data
|
||||
|
|
|
@ -5,10 +5,12 @@ from django.db import transaction
|
|||
import commonware.log
|
||||
from tastypie import http
|
||||
from tastypie.exceptions import ImmediateHttpResponse
|
||||
from tastypie import fields
|
||||
from tastypie.serializers import Serializer
|
||||
from tastypie.resources import ALL_WITH_RELATIONS
|
||||
|
||||
from addons.forms import CategoryFormSet, DeviceTypeForm
|
||||
from addons.models import AddonUser, Category, DeviceType
|
||||
from addons.models import AddonUser, Category, DeviceType, Preview
|
||||
import amo
|
||||
from amo.decorators import write
|
||||
from amo.utils import no_translation
|
||||
|
@ -19,10 +21,11 @@ from mkt.api.base import MarketplaceResource
|
|||
from mkt.api.forms import UploadForm
|
||||
from mkt.developers import tasks
|
||||
from mkt.developers.forms import NewManifestForm
|
||||
from mkt.developers.forms import PreviewForm
|
||||
from mkt.api.forms import PreviewJSONForm
|
||||
from mkt.webapps.models import Webapp
|
||||
from mkt.submit.forms import AppDetailsBasicForm
|
||||
|
||||
|
||||
log = commonware.log.getLogger('z.api')
|
||||
|
||||
|
||||
|
@ -64,7 +67,7 @@ class ValidationResource(MarketplaceResource):
|
|||
raise ImmediateHttpResponse(response=http.HttpNotFound())
|
||||
|
||||
if not OwnerAuthorization().is_authorized(request, object=obj):
|
||||
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
log.info('Validation retreived: %s' % obj.pk)
|
||||
return obj
|
||||
|
@ -105,7 +108,7 @@ class AppResource(MarketplaceResource):
|
|||
|
||||
if not (OwnerAuthorization()
|
||||
.is_authorized(request, object=form.obj)):
|
||||
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
plats = [Platform.objects.get(id=amo.PLATFORM_ALL.id)]
|
||||
|
||||
|
@ -119,7 +122,7 @@ class AppResource(MarketplaceResource):
|
|||
def obj_get(self, request=None, **kwargs):
|
||||
obj = super(AppResource, self).obj_get(request=request, **kwargs)
|
||||
if not AppOwnerAuthorization().is_authorized(request, object=obj):
|
||||
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
log.info('App retreived: %s' % obj.pk)
|
||||
return obj
|
||||
|
@ -148,7 +151,7 @@ class AppResource(MarketplaceResource):
|
|||
raise ImmediateHttpResponse(response=http.HttpNotFound())
|
||||
|
||||
if not AppOwnerAuthorization().is_authorized(request, object=obj):
|
||||
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
data['slug'] = data.get('slug', obj.app_slug)
|
||||
data.update(self.formset(data))
|
||||
|
@ -191,3 +194,62 @@ class CategoryResource(MarketplaceResource):
|
|||
always_return_data = True
|
||||
resource_name = 'category'
|
||||
serializer = Serializer(formats=['json'])
|
||||
|
||||
|
||||
class PreviewResource(MarketplaceResource):
|
||||
addon = fields.ForeignKey(AppResource, 'addon')
|
||||
|
||||
class Meta:
|
||||
queryset = Preview.objects.all()
|
||||
list_allowed_methods = ['post']
|
||||
allowed_methods = ['get', 'delete']
|
||||
always_return_data = True
|
||||
fields = ['id']
|
||||
authentication = MarketplaceAuthentication()
|
||||
authorization = OwnerAuthorization()
|
||||
resource_name = 'preview'
|
||||
filtering = {'addon': ALL_WITH_RELATIONS}
|
||||
|
||||
def obj_create(self, bundle, request, **kwargs):
|
||||
filters = self.build_filters(filters=request.GET.copy())
|
||||
addon = self.get_object_or_404(Webapp,
|
||||
pk=filters.get('addon__exact'))
|
||||
if not AppOwnerAuthorization().is_authorized(request, object=addon):
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
data_form = PreviewJSONForm(bundle.data)
|
||||
if not data_form.is_valid():
|
||||
raise self.form_errors(data_form)
|
||||
|
||||
form = PreviewForm(data_form.cleaned_data)
|
||||
if not form.is_valid():
|
||||
raise self.form_errors(form)
|
||||
|
||||
form.save(addon)
|
||||
bundle.obj = form.instance
|
||||
log.info('Preview created: %s' % bundle.obj.pk)
|
||||
return bundle
|
||||
|
||||
def obj_delete(self, request, **kwargs):
|
||||
obj = self.get_by_resource_or_404(request, **kwargs)
|
||||
if not AppOwnerAuthorization().is_authorized(request,
|
||||
object=obj.addon):
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
log.info('Preview deleted: %s' % obj.pk)
|
||||
return super(PreviewResource, self).obj_delete(request, **kwargs)
|
||||
|
||||
def obj_get(self, request=None, **kwargs):
|
||||
obj = super(PreviewResource, self).obj_get(request=request, **kwargs)
|
||||
if not AppOwnerAuthorization().is_authorized(request,
|
||||
object=obj.addon):
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
log.info('Preview retreived: %s' % obj.pk)
|
||||
return obj
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
# Returning an image back to the user isn't useful, let's stop that.
|
||||
if 'file' in bundle.data:
|
||||
del bundle.data['file']
|
||||
return bundle
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import base64
|
||||
|
||||
from nose.tools import eq_
|
||||
|
||||
import amo
|
||||
import amo.tests
|
||||
from mkt.api.forms import PreviewJSONForm
|
||||
|
||||
|
||||
class TestPreviewForm(amo.tests.TestCase, amo.tests.AMOPaths):
|
||||
|
||||
def setUp(self):
|
||||
self.file = base64.b64encode(open(self.mozball_image(), 'r').read())
|
||||
|
||||
def test_bad_type(self):
|
||||
form = PreviewJSONForm({'file': {'data': self.file, 'type': 'wtf?'},
|
||||
'position': 1})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['file'], ['Images must be either PNG or JPG.'])
|
||||
|
||||
def test_bad_file(self):
|
||||
file_ = base64.b64encode(open(self.xpi_path('langpack'), 'r').read())
|
||||
form = PreviewJSONForm({'file': {'data': file_, 'type': 'image/png'},
|
||||
'position': 1})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['file'], ['Images must be either PNG or JPG.'])
|
||||
|
||||
def test_position_missing(self):
|
||||
form = PreviewJSONForm({'file': {'data': self.file,
|
||||
'type': 'image/jpg'}})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['position'], ['This field is required.'])
|
||||
|
||||
def test_preview(self):
|
||||
form = PreviewJSONForm({'file': {'type': '', 'data': ''},
|
||||
'position': 1})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['file'], ['Images must be either PNG or JPG.'])
|
||||
|
||||
def test_not_json(self):
|
||||
form = PreviewJSONForm({'file': 1, 'position': 1})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['file'], ['File must be a dictionary.'])
|
||||
|
||||
def test_not_file(self):
|
||||
form = PreviewJSONForm({'position': 1})
|
||||
assert not form.is_valid()
|
||||
eq_(form.errors['file'], ['This field is required.'])
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
|
@ -6,7 +7,7 @@ from django.conf import settings
|
|||
from mock import patch
|
||||
from nose.tools import eq_
|
||||
|
||||
from addons.models import Addon, Category, DeviceType
|
||||
from addons.models import Addon, AddonUser, Category, DeviceType
|
||||
import amo
|
||||
from amo.tests import AMOPaths
|
||||
from files.models import FileUpload
|
||||
|
@ -92,7 +93,7 @@ class TestGetValidationHandler(ValidationHandler):
|
|||
obj = self.create()
|
||||
obj.update(user=UserProfile.objects.get(email='admin@mozilla.com'))
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 401)
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def test_not_found(self):
|
||||
url = ('api_dispatch_detail',
|
||||
|
@ -150,9 +151,6 @@ class CreateHandler(BaseOAuth):
|
|||
return FileUpload.objects.create(user=self.user, path=self.file,
|
||||
name=self.file, valid=True)
|
||||
|
||||
def get_error(self, response):
|
||||
return json.loads(response.content)['error_message']
|
||||
|
||||
|
||||
@patch.object(settings, 'SITE_URL', 'http://api/')
|
||||
class TestAppCreateHandler(CreateHandler, AMOPaths):
|
||||
|
@ -188,7 +186,7 @@ class TestAppCreateHandler(CreateHandler, AMOPaths):
|
|||
obj.update(user=UserProfile.objects.get(email='admin@mozilla.com'))
|
||||
res = self.client.post(self.list_url,
|
||||
data=json.dumps({'manifest': obj.uuid}))
|
||||
eq_(res.status_code, 401)
|
||||
eq_(res.status_code, 403)
|
||||
eq_(self.count(), 0)
|
||||
|
||||
def test_create(self):
|
||||
|
@ -225,7 +223,7 @@ class TestAppCreateHandler(CreateHandler, AMOPaths):
|
|||
obj = self.create_app()
|
||||
obj.authors.clear()
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 401)
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def base_data(self):
|
||||
return {'support_email': 'a@a.com',
|
||||
|
@ -299,7 +297,7 @@ class TestAppCreateHandler(CreateHandler, AMOPaths):
|
|||
obj = self.create_app()
|
||||
obj.authors.clear()
|
||||
res = self.client.put(self.get_url, data='{}')
|
||||
eq_(res.status_code, 401)
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def test_put_not_there(self):
|
||||
url = ('api_dispatch_detail', {'resource_name': 'app', 'pk': 123})
|
||||
|
@ -356,3 +354,88 @@ class TestCategoryHandler(BaseOAuth):
|
|||
{'resource_name': 'category',
|
||||
'pk': self.other.pk}))
|
||||
eq_(res.status_code, 404)
|
||||
|
||||
|
||||
@patch.object(settings, 'SITE_URL', 'http://api/')
|
||||
class TestPreviewHandler(BaseOAuth, AMOPaths):
|
||||
fixtures = ['base/users', 'base/user_2519', 'webapps/337141-steamcube']
|
||||
|
||||
def setUp(self):
|
||||
super(TestPreviewHandler, self).setUp()
|
||||
self.app = Webapp.objects.get(pk=337141)
|
||||
self.user = UserProfile.objects.get(pk=2519)
|
||||
AddonUser.objects.create(user=self.user, addon=self.app)
|
||||
self.file = base64.b64encode(open(self.mozball_image(), 'r').read())
|
||||
self.list_url = ('api_dispatch_list', {'resource_name': 'preview'},
|
||||
{'addon__exact': self.app.pk})
|
||||
self.good = {'file': {'data': self.file, 'type': 'image/jpg'},
|
||||
'position': 1}
|
||||
|
||||
def test_no_addon(self):
|
||||
list_url = ('api_dispatch_list', {'resource_name': 'preview'})
|
||||
res = self.client.post(list_url, data=json.dumps(self.good))
|
||||
eq_(res.status_code, 404)
|
||||
|
||||
def test_post_preview(self):
|
||||
res = self.client.post(self.list_url, data=json.dumps(self.good))
|
||||
eq_(res.status_code, 201)
|
||||
previews = self.app.previews
|
||||
eq_(previews.count(), 1)
|
||||
eq_(previews.all()[0].position, 1)
|
||||
|
||||
def test_not_mine(self):
|
||||
self.app.authors.clear()
|
||||
res = self.client.post(self.list_url, data=json.dumps(self.good))
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def test_position_missing(self):
|
||||
data = {'file': {'data': self.file, 'type': 'image/jpg'}}
|
||||
res = self.client.post(self.list_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 400)
|
||||
eq_(self.get_error(res)['position'], ['This field is required.'])
|
||||
|
||||
def test_preview_missing(self):
|
||||
res = self.client.post(self.list_url, data=json.dumps({}))
|
||||
eq_(res.status_code, 400)
|
||||
eq_(self.get_error(res)['position'], ['This field is required.'])
|
||||
|
||||
def create(self):
|
||||
self.client.post(self.list_url, data=json.dumps(self.good))
|
||||
self.preview = self.app.previews.all()[0]
|
||||
self.get_url = ('api_dispatch_detail',
|
||||
{'resource_name': 'preview', 'pk': self.preview.pk})
|
||||
|
||||
def test_delete(self):
|
||||
self.create()
|
||||
res = self.client.delete(self.get_url)
|
||||
eq_(res.status_code, 204)
|
||||
eq_(self.app.previews.count(), 0)
|
||||
|
||||
def test_delete_not_mine(self):
|
||||
self.create()
|
||||
self.app.authors.clear()
|
||||
res = self.client.delete(self.get_url)
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def test_delete_not_there(self):
|
||||
self.get_url = ('api_dispatch_detail',
|
||||
{'resource_name': 'preview', 'pk': 123})
|
||||
res = self.client.delete(self.get_url)
|
||||
eq_(res.status_code, 404)
|
||||
|
||||
def test_get(self):
|
||||
self.create()
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 200)
|
||||
|
||||
def test_get_not_mine(self):
|
||||
self.create()
|
||||
self.app.authors.clear()
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 403)
|
||||
|
||||
def test_get_not_there(self):
|
||||
self.get_url = ('api_dispatch_detail',
|
||||
{'resource_name': 'preview', 'pk': 123})
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 404)
|
||||
|
|
|
@ -19,7 +19,7 @@ With headers:
|
|||
oauth_nonce="1008F707-37E6-4ABF-8322-C6B658771D88",
|
||||
oauth_version="1.0"
|
||||
"""
|
||||
import time
|
||||
import json
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
|
@ -38,16 +38,6 @@ from amo.urlresolvers import reverse
|
|||
from files.models import FileUpload
|
||||
|
||||
|
||||
def _get_args(consumer):
|
||||
return dict(
|
||||
oauth_consumer_key=consumer.key,
|
||||
oauth_nonce=oauth.generate_nonce(),
|
||||
oauth_signature_method='HMAC-SHA1',
|
||||
oauth_timestamp=int(time.time()),
|
||||
oauth_version='1.0',
|
||||
)
|
||||
|
||||
|
||||
def get_absolute_url(url):
|
||||
# TODO (andym): make this more standard.
|
||||
url[1]['api_name'] = 'apps'
|
||||
|
@ -58,13 +48,6 @@ def get_absolute_url(url):
|
|||
return res
|
||||
|
||||
|
||||
def data_keys(d):
|
||||
# Form keys and values MUST be part of the signature.
|
||||
# File keys MUST be part of the signature.
|
||||
# But file values MUST NOT be included as part of the signature.
|
||||
return dict([k, '' if isinstance(v, file) else v] for k, v in d.items())
|
||||
|
||||
|
||||
class OAuthClient(Client):
|
||||
"""
|
||||
OAuthClient can do all the requests the Django test client,
|
||||
|
@ -80,8 +63,15 @@ class OAuthClient(Client):
|
|||
def header(self, method, url):
|
||||
if not self.consumer:
|
||||
return None
|
||||
req = oauth.Request(method=method, url=url,
|
||||
parameters=_get_args(self.consumer))
|
||||
|
||||
parsed = urlparse.urlparse(url)
|
||||
args = dict(urlparse.parse_qs(parsed.query))
|
||||
|
||||
req = oauth.Request.from_consumer_and_token(self.consumer,
|
||||
token=None, http_method=method,
|
||||
http_url=urlparse.urlunparse(parsed._replace(query='')),
|
||||
parameters=args)
|
||||
|
||||
req.sign_request(self.signature_method, self.consumer, None)
|
||||
return req.to_header()['Authorization']
|
||||
|
||||
|
@ -152,8 +142,11 @@ class BaseOAuth(TestCase):
|
|||
if verb in allowed:
|
||||
continue
|
||||
res = getattr(self.client, verb)(url)
|
||||
assert (res.status_code in (401, 405),
|
||||
'%s: %s not 401 or 405' % (verb.upper(), res.status_code))
|
||||
assert res.status_code in (401, 405), (
|
||||
'%s: %s not 401 or 405' % (verb.upper(), res.status_code))
|
||||
|
||||
def get_error(self, response):
|
||||
return json.loads(response.content)['error_message']
|
||||
|
||||
|
||||
@patch.object(settings, 'SITE_URL', 'http://api/')
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
from django.conf.urls.defaults import include, patterns, url
|
||||
|
||||
from tastypie.api import Api
|
||||
from mkt.api.resources import AppResource, CategoryResource, ValidationResource
|
||||
from mkt.search.api import SearchResource
|
||||
from mkt.api.resources import (AppResource, CategoryResource,
|
||||
PreviewResource, ValidationResource)
|
||||
|
||||
api = Api(api_name='apps')
|
||||
api.register(ValidationResource())
|
||||
api.register(AppResource())
|
||||
api.register(CategoryResource())
|
||||
api.register(SearchResource())
|
||||
api.register(PreviewResource())
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^', include(api.urls)),
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage as storage
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
|
||||
from tower import ugettext as _
|
||||
import waffle
|
||||
|
||||
import amo
|
||||
from lib.video import library as video_library
|
||||
|
||||
|
||||
def check_upload(file_obj, upload_type, content_type):
|
||||
errors = []
|
||||
upload_hash = ''
|
||||
is_icon = upload_type == 'icon'
|
||||
is_video = (content_type in amo.VIDEO_TYPES and
|
||||
waffle.switch_is_active('video-upload'))
|
||||
|
||||
# By pushing the type onto the instance hash, we can easily see what
|
||||
# to do with the file later.
|
||||
ext = content_type.replace('/', '-')
|
||||
upload_hash = '%s.%s' % (uuid.uuid4().hex, ext)
|
||||
loc = os.path.join(settings.TMP_PATH, upload_type, upload_hash)
|
||||
|
||||
with storage.open(loc, 'wb') as fd:
|
||||
for chunk in file_obj:
|
||||
fd.write(chunk)
|
||||
|
||||
if is_video:
|
||||
if not video_library:
|
||||
errors.append(_('Video support not enabled.'))
|
||||
else:
|
||||
video = video_library(loc)
|
||||
video.get_meta()
|
||||
if not video.is_valid():
|
||||
errors.extend(video.errors)
|
||||
|
||||
else:
|
||||
check = amo.utils.ImageCheck(file_obj)
|
||||
if (not check.is_image() or
|
||||
content_type not in amo.IMG_TYPES):
|
||||
if is_icon:
|
||||
errors.append(_('Icons must be either PNG or JPG.'))
|
||||
else:
|
||||
errors.append(_('Images must be either PNG or JPG.'))
|
||||
|
||||
if check.is_animated():
|
||||
if is_icon:
|
||||
errors.append(_('Icons cannot be animated.'))
|
||||
else:
|
||||
errors.append(_('Images cannot be animated.'))
|
||||
|
||||
max_size = (settings.MAX_ICON_UPLOAD_SIZE if is_icon else
|
||||
settings.MAX_VIDEO_UPLOAD_SIZE if is_video else None)
|
||||
|
||||
if max_size and file_obj.size > max_size:
|
||||
if is_icon or is_video:
|
||||
errors.append(_('Please use files smaller than %dMB.') %
|
||||
filesizeformat(max_size))
|
||||
|
||||
return errors, upload_hash
|
||||
|
|
@ -2,10 +2,8 @@ import json
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
from django import http
|
||||
from django.core.files.storage import default_storage as storage
|
||||
from django.conf import settings
|
||||
from django import forms as django_forms
|
||||
from django.db import models, transaction
|
||||
|
@ -26,7 +24,6 @@ import amo
|
|||
import amo.utils
|
||||
from amo import messages
|
||||
from amo.decorators import json_view, login_required, post_required, write
|
||||
from amo.helpers import loc
|
||||
from amo.utils import escape_all
|
||||
from amo.urlresolvers import reverse
|
||||
from addons import forms as addon_forms
|
||||
|
@ -34,7 +31,6 @@ from addons.decorators import can_become_premium
|
|||
from addons.models import Addon, AddonUser
|
||||
from addons.views import BaseFilter
|
||||
from devhub.models import AppLog
|
||||
from lib.video import library as video_library
|
||||
from files.models import File, FileUpload
|
||||
from files.utils import parse_addon
|
||||
from market.models import AddonPaymentData, AddonPremium, Refund
|
||||
|
@ -51,7 +47,8 @@ from mkt.developers.decorators import dev_required
|
|||
from mkt.developers.forms import (AppFormBasic, AppFormDetails, AppFormMedia,
|
||||
AppFormSupport, CurrencyForm,
|
||||
InappConfigForm, PaypalSetupForm,
|
||||
PreviewForm, PreviewFormSet, trap_duplicate)
|
||||
PreviewFormSet, trap_duplicate)
|
||||
from mkt.developers.utils import check_upload
|
||||
from mkt.inapp_pay.models import InappConfig
|
||||
from mkt.webapps.tasks import update_manifests
|
||||
from mkt.webapps.models import Webapp
|
||||
|
@ -943,52 +940,10 @@ def ajax_upload_media(request, upload_type):
|
|||
if 'upload_image' in request.FILES:
|
||||
upload_preview = request.FILES['upload_image']
|
||||
upload_preview.seek(0)
|
||||
content_type = upload_preview.content_type
|
||||
errors, upload_hash = check_upload(upload_preview, upload_type,
|
||||
content_type)
|
||||
|
||||
is_icon = upload_type == 'icon'
|
||||
is_video = (upload_preview.content_type in amo.VIDEO_TYPES and
|
||||
waffle.switch_is_active('video-upload'))
|
||||
|
||||
# By pushing the type onto the instance hash, we can easily see what
|
||||
# to do with the file later.
|
||||
ext = upload_preview.content_type.replace('/', '-')
|
||||
upload_hash = '%s.%s' % (uuid.uuid4().hex, ext)
|
||||
loc = os.path.join(settings.TMP_PATH, upload_type, upload_hash)
|
||||
|
||||
with storage.open(loc, 'wb') as fd:
|
||||
for chunk in upload_preview:
|
||||
fd.write(chunk)
|
||||
|
||||
if is_video:
|
||||
if not video_library:
|
||||
errors.append(_('Video support not enabled.'))
|
||||
else:
|
||||
video = video_library(loc)
|
||||
video.get_meta()
|
||||
if not video.is_valid():
|
||||
errors.extend(video.errors)
|
||||
|
||||
else:
|
||||
check = amo.utils.ImageCheck(upload_preview)
|
||||
if (not check.is_image() or
|
||||
upload_preview.content_type not in amo.IMG_TYPES):
|
||||
if is_icon:
|
||||
errors.append(_('Icons must be either PNG or JPG.'))
|
||||
else:
|
||||
errors.append(_('Images must be either PNG or JPG.'))
|
||||
|
||||
if check.is_animated():
|
||||
if is_icon:
|
||||
errors.append(_('Icons cannot be animated.'))
|
||||
else:
|
||||
errors.append(_('Images cannot be animated.'))
|
||||
|
||||
max_size = (settings.MAX_ICON_UPLOAD_SIZE if is_icon else
|
||||
settings.MAX_VIDEO_UPLOAD_SIZE if is_video else None)
|
||||
|
||||
if max_size and upload_preview.size > max_size:
|
||||
if is_icon or is_video:
|
||||
errors.append(_('Please use files smaller than %dMB.') % (
|
||||
max_size / 1024 / 1024 - 1))
|
||||
else:
|
||||
errors.append(_('There was an error uploading your preview.'))
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче