addons-server/mkt/developers/views.py

901 строка
32 KiB
Python

import json
import os
import sys
import traceback
from django import http
from django import forms as django_forms
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_view_exempt
import commonware.log
import jingo
from session_csrf import anonymous_csrf, anonymous_csrf_exempt
from tower import ugettext as _, ugettext_lazy as _lazy
import waffle
from waffle.decorators import waffle_switch
import amo
import amo.utils
from access import acl
from addons import forms as addon_forms
from addons.decorators import addon_view
from addons.models import Addon, AddonUser
from addons.views import BaseFilter
from amo import messages
from amo.decorators import (any_permission_required, json_view, login_required,
post_required)
from amo.urlresolvers import reverse
from amo.utils import escape_all
from devhub.forms import VersionForm
from devhub.models import AppLog
from files.models import File, FileUpload
from files.utils import parse_addon
from market.models import Refund
from stats.models import Contribution
from translations.models import delete_translation
from users.models import UserProfile
from users.views import _login
from versions.models import Version
from mkt.api.models import Access, generate
from mkt.constants import APP_IMAGE_SIZES
from mkt.developers.decorators import dev_required
from mkt.developers.forms import (APIConsumerForm, AppFormBasic,
AppFormDetails, AppFormMedia,
AppFormSupport, AppFormTechnical,
CategoryForm, ImageAssetFormSet,
NewPackagedAppForm, PreviewFormSet,
TransactionFilterForm, trap_duplicate)
from mkt.developers.utils import check_upload
from mkt.submit.forms import AppFeaturesForm, NewWebappVersionForm
from mkt.webapps.tasks import update_manifests, _update_manifest
from mkt.webapps.models import Webapp
from . import forms, tasks
log = commonware.log.getLogger('z.devhub')
# We use a session cookie to make sure people see the dev agreement.
DEV_AGREEMENT_COOKIE = 'yes-I-read-the-dev-agreement'
class AddonFilter(BaseFilter):
opts = (('name', _lazy(u'Name')),
('updated', _lazy(u'Updated')),
('created', _lazy(u'Created')),
('popular', _lazy(u'Downloads')),
('rating', _lazy(u'Rating')))
class AppFilter(BaseFilter):
opts = (('name', _lazy(u'Name')),
('created', _lazy(u'Created')))
def addon_listing(request, default='name', webapp=False):
"""Set up the queryset and filtering for addon listing for Dashboard."""
Filter = AppFilter if webapp else AddonFilter
addons = UserProfile.objects.get(pk=request.user.id).addons
if webapp:
qs = Webapp.objects.filter(id__in=addons.filter(type=amo.ADDON_WEBAPP))
model = Webapp
else:
qs = addons.exclude(type=amo.ADDON_WEBAPP)
model = Addon
filter = Filter(request, qs, 'sort', default, model=model)
return filter.qs, filter
@anonymous_csrf
def login(request, template=None):
return _login(request, template='developers/login.html')
def home(request):
return index(request)
@login_required
def index(request):
# This is a temporary redirect.
return redirect('mkt.developers.apps')
@login_required
def dashboard(request, webapp=False):
addons, filter = addon_listing(request, webapp=webapp)
addons = amo.utils.paginate(request, addons, per_page=10)
data = dict(addons=addons, sorting=filter.field, filter=filter,
sort_opts=filter.opts, webapp=webapp)
return jingo.render(request, 'developers/apps/dashboard.html', data)
@dev_required(webapp=True, staff=True)
def edit(request, addon_id, addon, webapp=False):
data = {
'page': 'edit',
'addon': addon,
'webapp': webapp,
'valid_slug': addon.app_slug,
'image_sizes': APP_IMAGE_SIZES,
'tags': addon.tags.not_blacklisted().values_list('tag_text',
flat=True),
'previews': addon.get_previews(),
'version': addon.current_version or addon.latest_version
}
if (waffle.switch_is_active('buchets') and not addon.is_packaged and
addon.current_version):
data['feature_list'] = [unicode(f) for f in
addon.current_version.features.to_list()]
if acl.action_allowed(request, 'Apps', 'Configure'):
data['admin_settings_form'] = forms.AdminSettingsForm(instance=addon)
return jingo.render(request, 'developers/apps/edit.html', data)
@dev_required(owner_for_post=True, webapp=True)
@post_required
def delete(request, addon_id, addon, webapp=False):
# Database deletes only allowed for free or incomplete addons.
if not addon.can_be_deleted():
msg = _('Paid apps cannot be deleted. Disable this app instead.')
messages.error(request, msg)
return redirect(addon.get_dev_url('versions'))
# TODO: Force the user to re-auth with BrowserID (this DeleteForm doesn't
# ask the user for his password)
form = forms.DeleteForm(request)
if form.is_valid():
reason = form.cleaned_data.get('reason', '')
addon.delete(msg='Removed via devhub', reason=reason)
messages.success(request, _('App deleted.'))
# Preserve query-string parameters if we were directed from Dashboard.
return redirect(request.GET.get('to') or
reverse('mkt.developers.apps'))
else:
msg = _('Password was incorrect. App was not deleted.')
messages.error(request, msg)
return redirect(addon.get_dev_url('versions'))
@dev_required
@post_required
def enable(request, addon_id, addon):
addon.update(disabled_by_user=False)
amo.log(amo.LOG.USER_ENABLE, addon)
return redirect(addon.get_dev_url('versions'))
@dev_required
@post_required
def disable(request, addon_id, addon):
addon.update(disabled_by_user=True)
amo.log(amo.LOG.USER_DISABLE, addon)
return redirect(addon.get_dev_url('versions'))
@dev_required
@post_required
def publicise(request, addon_id, addon):
if addon.status == amo.STATUS_PUBLIC_WAITING:
addon.update(status=amo.STATUS_PUBLIC)
File.objects.filter(
version__addon=addon, status=amo.STATUS_PUBLIC_WAITING).update(
status=amo.STATUS_PUBLIC)
amo.log(amo.LOG.CHANGE_STATUS, addon.get_status_display(), addon)
# Call update_version, so various other bits of data update.
addon.update_version()
# Call to update names and locales if changed.
addon.update_name_from_package_manifest()
addon.update_supported_locales()
return redirect(addon.get_dev_url('versions'))
@dev_required(webapp=True)
def status(request, addon_id, addon, webapp=False):
form = forms.AppAppealForm(request.POST, product=addon)
upload_form = NewWebappVersionForm(request.POST or None, is_packaged=True,
addon=addon, request=request)
if request.method == 'POST':
if 'resubmit-app' in request.POST and form.is_valid():
form.save()
messages.success(request, _('App successfully resubmitted.'))
return redirect(addon.get_dev_url('versions'))
elif 'upload-version' in request.POST and upload_form.is_valid():
ver = Version.from_upload(upload_form.cleaned_data['upload'],
addon, [amo.PLATFORM_ALL])
messages.success(request, _('New version successfully added.'))
log.info('[Webapp:%s] New version created id=%s from upload: %s'
% (addon, ver.pk, upload_form.cleaned_data['upload']))
return redirect(addon.get_dev_url('versions.edit', args=[ver.pk]))
ctx = {'addon': addon, 'webapp': webapp, 'form': form,
'upload_form': upload_form}
# Used in the delete version modal.
if addon.is_packaged:
versions = addon.versions.values('id', 'version')
version_strings = dict((v['id'], v) for v in versions)
version_strings['num'] = len(versions)
ctx['version_strings'] = json.dumps(version_strings)
if addon.status == amo.STATUS_REJECTED:
try:
entry = (AppLog.objects
.filter(addon=addon,
activity_log__action=amo.LOG.REJECT_VERSION.id)
.order_by('-created'))[0]
except IndexError:
entry = None
# This contains the rejection reason and timestamp.
ctx['rejection'] = entry and entry.activity_log
return jingo.render(request, 'developers/apps/status.html', ctx)
@dev_required
def version_edit(request, addon_id, addon, version_id):
show_features = waffle.switch_is_active('buchets') and addon.is_packaged
version = get_object_or_404(Version, pk=version_id, addon=addon)
form = VersionForm(request.POST or None, instance=version)
all_forms = [form]
if show_features:
appfeatures = version.features
appfeatures_form = AppFeaturesForm(request.POST, instance=appfeatures)
all_forms.append(appfeatures_form)
if request.method == 'POST' and all(f.is_valid() for f in all_forms):
[f.save() for f in all_forms]
messages.success(request, _('Version successfully edited.'))
return redirect(addon.get_dev_url('versions'))
context = {
'addon': addon,
'version': version,
'form': form
}
if show_features:
context.update({
'appfeatures_form': appfeatures_form,
'appfeatures': appfeatures,
'feature_list': [unicode(f) for f in appfeatures.to_list()]
})
return jingo.render(request, 'developers/apps/version_edit.html', context)
@dev_required
@post_required
@transaction.commit_on_success
def version_delete(request, addon_id, addon):
version_id = request.POST.get('version_id')
version = get_object_or_404(Version, pk=version_id, addon=addon)
if version.all_files[0].status == amo.STATUS_BLOCKED:
raise PermissionDenied
version.delete()
messages.success(request,
_('Version "{0}" deleted.').format(version.version))
return redirect(addon.get_dev_url('versions'))
@dev_required(owner_for_post=True, webapp=True)
def ownership(request, addon_id, addon, webapp=False):
# Authors.
qs = AddonUser.objects.filter(addon=addon).order_by('position')
user_form = forms.AuthorFormSet(request.POST or None, queryset=qs)
if request.method == 'POST' and user_form.is_valid():
# Authors.
authors = user_form.save(commit=False)
for author in authors:
action = None
if not author.id or author.user_id != author._original_user_id:
action = amo.LOG.ADD_USER_WITH_ROLE
author.addon = addon
elif author.role != author._original_role:
action = amo.LOG.CHANGE_USER_WITH_ROLE
author.save()
if action:
amo.log(action, author.user, author.get_role_display(), addon)
if (author._original_user_id and
author.user_id != author._original_user_id):
amo.log(amo.LOG.REMOVE_USER_WITH_ROLE,
(UserProfile, author._original_user_id),
author.get_role_display(), addon)
for author in user_form.deleted_objects:
amo.log(amo.LOG.REMOVE_USER_WITH_ROLE, author.user,
author.get_role_display(), addon)
messages.success(request, _('Changes successfully saved.'))
return redirect(addon.get_dev_url('owner'))
ctx = dict(addon=addon, webapp=webapp, user_form=user_form)
return jingo.render(request, 'developers/apps/owner.html', ctx)
@waffle_switch('allow-refund')
@dev_required(support=True, webapp=True)
def refunds(request, addon_id, addon, webapp=False):
ctx = {'addon': addon, 'webapp': webapp}
queues = {
'pending': Refund.objects.pending(addon).order_by('requested'),
'approved': Refund.objects.approved(addon).order_by('-requested'),
'instant': Refund.objects.instant(addon).order_by('-requested'),
'declined': Refund.objects.declined(addon).order_by('-requested'),
'failed': Refund.objects.failed(addon).order_by('-requested'),
}
for status, refunds in queues.iteritems():
ctx[status] = amo.utils.paginate(request, refunds, per_page=50)
return jingo.render(request, 'developers/payments/refunds.html', ctx)
@dev_required(webapp=True)
@post_required
def remove_profile(request, addon_id, addon, webapp=False):
delete_translation(addon, 'the_reason')
delete_translation(addon, 'the_future')
if addon.wants_contributions:
addon.update(wants_contributions=False)
return redirect(addon.get_dev_url('profile'))
@dev_required(webapp=True)
def profile(request, addon_id, addon, webapp=False):
profile_form = forms.ProfileForm(request.POST or None, instance=addon)
if request.method == 'POST' and profile_form.is_valid():
profile_form.save()
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
messages.success(request, _('Changes successfully saved.'))
return redirect(addon.get_dev_url('profile'))
return jingo.render(request, 'developers/apps/profile.html',
dict(addon=addon, webapp=webapp,
profile_form=profile_form))
@anonymous_csrf
def validate_addon(request):
return jingo.render(request, 'developers/validate_addon.html', {
'upload_hosted_url':
reverse('mkt.developers.standalone_hosted_upload'),
'upload_packaged_url':
reverse('mkt.developers.standalone_packaged_upload'),
})
@post_required
def _upload(request, addon_slug=None, is_standalone=False):
# If there is no user, default to None (saves the file upload as anon).
form = NewPackagedAppForm(request.POST, request.FILES,
user=getattr(request, 'amo_user', None))
if form.is_valid():
tasks.validator.delay(form.file_upload.pk)
if addon_slug:
return redirect('mkt.developers.upload_detail_for_addon',
addon_slug, form.file_upload.pk)
elif is_standalone:
return redirect('mkt.developers.standalone_upload_detail',
'packaged', form.file_upload.pk)
else:
return redirect('mkt.developers.upload_detail',
form.file_upload.pk, 'json')
@login_required
def upload_new(*args, **kwargs):
return _upload(*args, **kwargs)
@anonymous_csrf
def standalone_packaged_upload(request):
return _upload(request, is_standalone=True)
@dev_required
def upload_for_addon(request, addon_id, addon):
return _upload(request, addon_slug=addon.slug)
@dev_required
def refresh_manifest(request, addon_id, addon, webapp=False):
log.info('Manifest %s refreshed for %s' % (addon.manifest_url, addon))
_update_manifest(addon_id, True, ())
return http.HttpResponse(status=204)
@post_required
@json_view
def _upload_manifest(request, is_standalone=False):
form = forms.NewManifestForm(request.POST, is_standalone=is_standalone)
if (not is_standalone and
waffle.switch_is_active('webapps-unique-by-domain')):
# Helpful error if user already submitted the same manifest.
dup_msg = trap_duplicate(request, request.POST.get('manifest'))
if dup_msg:
return {'validation': {'errors': 1, 'success': False,
'messages': [{'type': 'error', 'message': dup_msg,
'tier': 1}]}}
if form.is_valid():
upload = FileUpload.objects.create()
tasks.fetch_manifest.delay(form.cleaned_data['manifest'], upload.pk)
if is_standalone:
return redirect('mkt.developers.standalone_upload_detail',
'hosted', upload.pk)
else:
return redirect('mkt.developers.upload_detail', upload.pk, 'json')
else:
error_text = _('There was an error with the submission.')
if 'manifest' in form.errors:
error_text = ' '.join(form.errors['manifest'])
error_message = {'type': 'error', 'message': error_text, 'tier': 1}
v = {'errors': 1, 'success': False, 'messages': [error_message]}
return make_validation_result(dict(validation=v, error=error_text))
@login_required
def upload_manifest(*args, **kwargs):
"""Wrapper function for `_upload_manifest` so we can keep the
standalone validator separate from the manifest upload stuff.
"""
return _upload_manifest(*args, **kwargs)
def standalone_hosted_upload(request):
return _upload_manifest(request, is_standalone=True)
@json_view
@anonymous_csrf_exempt
def standalone_upload_detail(request, type_, uuid):
upload = get_object_or_404(FileUpload.uncached, uuid=uuid)
url = reverse('mkt.developers.standalone_upload_detail',
args=[type_, uuid])
return upload_validation_context(request, upload, url=url)
@dev_required
@json_view
def upload_detail_for_addon(request, addon_id, addon, uuid):
upload = get_object_or_404(FileUpload.uncached, uuid=uuid)
return json_upload_detail(request, upload, addon_slug=addon.slug)
def make_validation_result(data):
"""Safe wrapper around JSON dict containing a validation result."""
if not settings.EXPOSE_VALIDATOR_TRACEBACKS:
if data['error']:
# Just expose the message, not the traceback.
data['error'] = data['error'].strip().split('\n')[-1].strip()
if data['validation']:
for msg in data['validation']['messages']:
for k, v in msg.items():
msg[k] = escape_all(v)
return data
@dev_required(allow_editors=True)
def file_validation(request, addon_id, addon, file_id):
file = get_object_or_404(File, id=file_id)
v = addon.get_dev_url('json_file_validation', args=[file.id])
return jingo.render(request, 'developers/validation.html',
dict(validate_url=v, filename=file.filename,
timestamp=file.created,
addon=addon))
@json_view
@csrf_view_exempt
@dev_required(allow_editors=True)
def json_file_validation(request, addon_id, addon, file_id):
file = get_object_or_404(File, id=file_id)
if not file.has_been_validated:
if request.method != 'POST':
return http.HttpResponseNotAllowed(['POST'])
try:
v_result = tasks.file_validator(file.id)
except Exception, exc:
log.error('file_validator(%s): %s' % (file.id, exc))
error = "\n".join(traceback.format_exception(*sys.exc_info()))
return make_validation_result({'validation': '',
'error': error})
else:
v_result = file.validation
validation = json.loads(v_result.validation)
return make_validation_result(dict(validation=validation,
error=None))
@json_view
def json_upload_detail(request, upload, addon_slug=None):
addon = None
if addon_slug:
addon = get_object_or_404(Addon, slug=addon_slug)
result = upload_validation_context(request, upload, addon=addon)
if result['validation']:
if result['validation']['errors'] == 0:
try:
parse_addon(upload, addon=addon)
except django_forms.ValidationError, exc:
m = []
for msg in exc.messages:
# Simulate a validation error so the UI displays it.
m.append({'type': 'error', 'message': msg, 'tier': 1})
v = make_validation_result(dict(error='',
validation=dict(messages=m)))
return json_view.error(v)
return result
def upload_validation_context(request, upload, addon_slug=None, addon=None,
url=None):
if addon_slug and not addon:
addon = get_object_or_404(Addon, slug=addon_slug)
if not settings.VALIDATE_ADDONS:
upload.task_error = ''
upload.is_webapp = True
upload.validation = json.dumps({'errors': 0, 'messages': [],
'metadata': {}, 'notices': 0,
'warnings': 0})
upload.save()
validation = json.loads(upload.validation) if upload.validation else ''
if not url:
if addon:
url = reverse('mkt.developers.upload_detail_for_addon',
args=[addon.slug, upload.uuid])
else:
url = reverse('mkt.developers.upload_detail',
args=[upload.uuid, 'json'])
report_url = reverse('mkt.developers.upload_detail', args=[upload.uuid])
return make_validation_result(dict(upload=upload.uuid,
validation=validation,
error=upload.task_error, url=url,
full_report_url=report_url))
def upload_detail(request, uuid, format='html'):
upload = get_object_or_404(FileUpload.uncached, uuid=uuid)
if format == 'json' or request.is_ajax():
return json_upload_detail(request, upload)
validate_url = reverse('mkt.developers.standalone_upload_detail',
args=['hosted', upload.uuid])
return jingo.render(request, 'developers/validation.html',
dict(validate_url=validate_url, filename=upload.name,
timestamp=upload.created))
@dev_required(webapp=True, staff=True)
def addons_section(request, addon_id, addon, section, editable=False,
webapp=False):
basic = AppFormBasic if webapp else addon_forms.AddonFormBasic
models = {'basic': basic,
'media': AppFormMedia,
'details': AppFormDetails,
'support': AppFormSupport,
'technical': AppFormTechnical,
'admin': forms.AdminSettingsForm}
is_dev = acl.check_addon_ownership(request, addon, dev=True)
if section not in models:
raise http.Http404()
# Only show the list of features if app isn't packaged.
show_features = waffle.switch_is_active('buchets') and not addon.is_packaged
appfeatures = appfeatures_form = None
if show_features:
appfeatures = addon.current_version.features
appfeatures_form = AppFeaturesForm(instance=appfeatures)
tags = image_assets = previews = restricted_tags = []
cat_form = None
# Permissions checks.
# Only app owners can edit any of the details of their apps.
# Users with 'Apps:Configure' can edit the admin settings.
if (section != 'admin' and not is_dev) or (section == 'admin' and
not acl.action_allowed(request, 'Apps', 'Configure') and
not acl.action_allowed(request, 'Apps', 'ViewConfiguration')):
raise PermissionDenied
if section == 'basic':
tags = addon.tags.not_blacklisted().values_list('tag_text', flat=True)
cat_form = CategoryForm(request.POST or None, product=addon,
request=request)
restricted_tags = addon.tags.filter(restricted=True)
elif section == 'media':
image_assets = ImageAssetFormSet(
request.POST or None, prefix='images', app=addon)
previews = PreviewFormSet(
request.POST or None, prefix='files',
queryset=addon.get_previews())
# Get the slug before the form alters it to the form data.
valid_slug = addon.app_slug
if editable:
if request.method == 'POST':
if show_features:
appfeatures_form = AppFeaturesForm(request.POST,
instance=appfeatures)
if (section == 'admin' and
not acl.action_allowed(request, 'Apps', 'Configure')):
raise PermissionDenied
form = models[section](request.POST, request.FILES,
instance=addon, request=request)
all_forms = [form, previews, image_assets]
if show_features:
all_forms.append(appfeatures_form)
if all(not f or f.is_valid() for f in all_forms):
addon = form.save(addon)
if show_features:
appfeatures_form.save()
if 'manifest_url' in form.changed_data:
addon.update(
app_domain=addon.domain_from_url(addon.manifest_url))
update_manifests([addon.pk])
if previews:
for preview in previews.forms:
preview.save(addon)
if image_assets:
image_assets.save()
editable = False
if section == 'media':
amo.log(amo.LOG.CHANGE_ICON, addon)
else:
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
valid_slug = addon.app_slug
if cat_form:
if cat_form.is_valid():
cat_form.save()
addon.save()
else:
editable = True
else:
form = models[section](instance=addon, request=request)
else:
form = False
data = {'addon': addon,
'webapp': webapp,
'version': addon.current_version or addon.latest_version,
'form': form,
'editable': editable,
'tags': tags,
'restricted_tags': restricted_tags,
'image_sizes': APP_IMAGE_SIZES,
'cat_form': cat_form,
'preview_form': previews,
'image_asset_form': image_assets,
'valid_slug': valid_slug, }
if show_features:
data.update({
'appfeatures': appfeatures,
'feature_list': [unicode(f) for f in appfeatures.to_list()],
'appfeatures_form': appfeatures_form
})
return jingo.render(request,
'developers/apps/edit/%s.html' % section, data)
@never_cache
@dev_required(skip_submit_check=True)
@json_view
def image_status(request, addon_id, addon, icon_size=64):
# Default icon needs no checking.
if not addon.icon_type or addon.icon_type.split('/')[0] == 'icon':
icons = True
# Persona icon is handled differently.
elif addon.type == amo.ADDON_PERSONA:
icons = True
else:
icons = os.path.exists(os.path.join(addon.get_icon_dir(),
'%s-%s.png' %
(addon.id, icon_size)))
previews = all(os.path.exists(p.thumbnail_path)
for p in addon.get_previews())
return {'overall': icons and previews,
'icons': icons,
'previews': previews}
@json_view
def ajax_upload_media(request, upload_type):
errors = []
upload_hash = ''
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)
else:
errors.append(_('There was an error uploading your preview.'))
if errors:
upload_hash = ''
return {'upload_hash': upload_hash, 'errors': errors}
@dev_required
def upload_media(request, addon_id, addon, upload_type):
return ajax_upload_media(request, upload_type)
@dev_required
@post_required
def remove_locale(request, addon_id, addon):
locale = request.POST.get('locale')
if locale and locale != addon.default_locale:
addon.remove_locale(locale)
return http.HttpResponse()
return http.HttpResponseBadRequest()
def docs(request, doc_name=None, doc_page=None):
filename = ''
all_docs = {'policies': ['agreement']}
if doc_name and doc_name in all_docs:
filename = '%s.html' % doc_name
if doc_page and doc_page in all_docs[doc_name]:
filename = '%s-%s.html' % (doc_name, doc_page)
else:
# TODO: Temporary until we have a `policies` docs index.
filename = None
if not filename:
return redirect('ecosystem.landing')
return jingo.render(request, 'developers/docs/%s' % filename)
@login_required
def terms(request):
form = forms.DevAgreementForm({'read_dev_agreement': True},
instance=request.amo_user)
if request.POST and form.is_valid():
form.save()
log.info('Dev agreement agreed for user: %s' % request.amo_user.pk)
messages.success(request, _('Terms of service accepted.'))
return jingo.render(request, 'developers/terms.html',
{'accepted': request.amo_user.read_dev_agreement,
'agreement_form': form})
@waffle_switch('create-api-tokens')
@login_required
def api(request):
roles = request.amo_user.groups.filter(name='Admins').exists()
if roles:
messages.error(request,
_('Users with the admin role cannot use the API.'))
elif request.method == 'POST':
if 'delete' in request.POST:
try:
consumer = Access.objects.get(pk=request.POST.get('consumer'))
consumer.delete()
except Access.DoesNotExist:
messages.error(request, _('No such API key.'))
else:
key = 'mkt:%s:%s:%s' % (
request.amo_user.pk,
request.amo_user.email,
Access.objects.filter(user=request.user).count())
access = Access.objects.create(key=key,
user=request.user,
secret=generate())
f = APIConsumerForm(request.POST, instance=access)
if f.is_valid():
f.save()
messages.success(request, _('New API key generated.'))
else:
access.delete()
messages.error(
request,
_('Both application name and redirect URI are required.'))
return redirect(reverse('mkt.developers.apps.api'))
consumers = list(Access.objects.filter(user=request.user))
return jingo.render(request, 'developers/api.html',
{'consumers': consumers, 'profile': profile,
'roles': roles})
@addon_view
@post_required
@any_permission_required([('Admin', '%'),
('Apps', 'Configure')])
def blocklist(request, addon):
"""
Blocklists the app by creating a new version/file.
"""
if addon.status != amo.STATUS_BLOCKED:
addon.create_blocklisted_version()
messages.success(request, _('Created blocklisted version.'))
else:
messages.info(request, _('App already blocklisted.'))
return redirect(addon.get_dev_url('versions'))
@waffle_switch('view-transactions')
@login_required
def transactions(request):
form, transactions = _get_transactions(request)
return jingo.render(
request, 'developers/transactions.html',
{'form': form,
'CONTRIB_TYPES': amo.CONTRIB_TYPES,
'count': transactions.count(),
'transactions': amo.utils.paginate(request,
transactions, per_page=50)})
def _get_transactions(request):
apps = addon_listing(request, webapp=True)[0]
transactions = Contribution.objects.filter(addon__in=list(apps),
type__in=amo.CONTRIB_TYPES)
form = TransactionFilterForm(request.GET, apps=apps)
if form.is_valid():
transactions = _filter_transactions(transactions, form.cleaned_data)
return form, transactions
def _filter_transactions(qs, data):
"""Handle search filters and queries for transactions."""
filter_mapping = {'app': 'addon_id',
'transaction_type': 'type',
'transaction_id': 'uuid',
'date_from': 'created__gte',
'date_to': 'created__lte'}
for form_field, db_field in filter_mapping.iteritems():
if data.get(form_field):
try:
qs = qs.filter(**{db_field: data[form_field]})
except ValueError:
continue
return qs