From 2cf0fd1dad850458c60480d91f3f04dbf472ee7e Mon Sep 17 00:00:00 2001 From: Mathieu Pillard Date: Mon, 9 Jun 2014 16:16:32 +0200 Subject: [PATCH] Moved apps/zadmin/ to mkt/zadmin/ (bug 1021207) --- .../management/commands/install_landfill.py | 2 +- apps/amo/tests/test_middleware.py | 2 +- apps/zadmin/__init__.py | 48 -- apps/zadmin/models.py | 109 ----- .../zadmin/templates/zadmin/addon-search.html | 25 - .../zadmin/templates/zadmin/addon_manage.html | 66 --- apps/zadmin/tests/__init__.py | 0 apps/zadmin/tests/test_views.py | 438 ------------------ apps/zadmin/urls.py | 57 --- apps/zadmin/views.py | 305 ------------ mkt/reviewers/tests/test_views.py | 2 +- mkt/reviewers/views.py | 2 +- mkt/settings.py | 2 +- mkt/site/context_processors.py | 2 +- mkt/urls.py | 3 - mkt/zadmin/__init__.py | 48 ++ {apps => mkt}/zadmin/admin.py | 0 {apps => mkt}/zadmin/decorators.py | 2 +- .../zadmin/fixtures/zadmin/users.json | 0 {apps => mkt}/zadmin/forms.py | 28 +- mkt/zadmin/models.py | 109 +++++ {apps => mkt}/zadmin/tasks.py | 2 +- .../zadmin/templates/admin/base.html | 0 .../zadmin/templates/admin/base_site.html | 0 .../zadmin/templates/zadmin/email-devs.html | 0 .../templates/zadmin/export_button.html | 0 .../zadmin/templates/zadmin/fix-disabled.html | 0 .../templates/zadmin/generate-error.html | 0 .../zadmin/templates/zadmin/index.html | 0 .../zadmin/templates/zadmin/mail.html | 0 .../zadmin/templates/zadmin/manifest.html | 0 .../zadmin/templates/zadmin/memcache.html | 0 .../zadmin/oauth-consumer-create.html | 0 .../zadmin/templates/zadmin/settings.html | 0 .../templates/zadmin/update-prices.html | 0 {apps => mkt}/zadmin/tests/test_models.py | 2 +- mkt/zadmin/tests/test_views.py | 268 ++++++++++- mkt/zadmin/urls.py | 48 +- mkt/zadmin/views.py | 225 ++++++++- 39 files changed, 683 insertions(+), 1112 deletions(-) delete mode 100644 apps/zadmin/__init__.py delete mode 100644 apps/zadmin/models.py delete mode 100644 apps/zadmin/templates/zadmin/addon-search.html delete mode 100644 apps/zadmin/templates/zadmin/addon_manage.html delete mode 100644 apps/zadmin/tests/__init__.py delete mode 100644 apps/zadmin/tests/test_views.py delete mode 100644 apps/zadmin/urls.py delete mode 100644 apps/zadmin/views.py rename {apps => mkt}/zadmin/admin.py (100%) rename {apps => mkt}/zadmin/decorators.py (96%) rename {apps => mkt}/zadmin/fixtures/zadmin/users.json (100%) rename {apps => mkt}/zadmin/forms.py (84%) rename {apps => mkt}/zadmin/tasks.py (93%) rename {apps => mkt}/zadmin/templates/admin/base.html (100%) rename {apps => mkt}/zadmin/templates/admin/base_site.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/email-devs.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/export_button.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/fix-disabled.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/generate-error.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/index.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/mail.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/manifest.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/memcache.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/oauth-consumer-create.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/settings.html (100%) rename {apps => mkt}/zadmin/templates/zadmin/update-prices.html (100%) rename {apps => mkt}/zadmin/tests/test_models.py (89%) diff --git a/apps/amo/management/commands/install_landfill.py b/apps/amo/management/commands/install_landfill.py index 526963d57f2..be06286615b 100644 --- a/apps/amo/management/commands/install_landfill.py +++ b/apps/amo/management/commands/install_landfill.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.management.base import BaseCommand -from zadmin.models import Config +from mkt.zadmin.models import Config class Command(BaseCommand): diff --git a/apps/amo/tests/test_middleware.py b/apps/amo/tests/test_middleware.py index c6d07b3b408..11b9b15e769 100644 --- a/apps/amo/tests/test_middleware.py +++ b/apps/amo/tests/test_middleware.py @@ -10,7 +10,7 @@ import amo.tests from amo.middleware import NoVarySessionMiddleware from amo.urlresolvers import reverse -from zadmin.models import Config, _config_cache +from mkt.zadmin.models import Config, _config_cache class TestMiddleware(amo.tests.TestCase): diff --git a/apps/zadmin/__init__.py b/apps/zadmin/__init__.py deleted file mode 100644 index 219cd149359..00000000000 --- a/apps/zadmin/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.shortcuts import render -from django.template import loader -from django.template.response import SimpleTemplateResponse - -import jingo - - -def jinja_for_django(template_name, context=None, **kw): - """ - If you want to use some built in logic (or a contrib app) but need to - override the templates to work with Jinja, replace the object's - render_to_response function with this one. That will render a Jinja - template through Django's functions. An example can be found in the users - app. - """ - if context is None: - context = {} - context_instance = kw.pop('context_instance') - request = context_instance['request'] - for d in context_instance.dicts: - context.update(d) - return render(request, template_name, context, **kw) - - -# We monkeypatch SimpleTemplateResponse.rendered_content to use our jinja -# rendering pipeline (most of the time). The exception is the admin app, where -# we render their Django templates and pipe the result through jinja to render -# our page skeleton. -def rendered_content(self): - template = self.template_name - context_instance = self.resolve_context(self.context_data) - request = context_instance['request'] - - # Gross, let's figure out if we're in the admin. - if self._current_app == 'admin': - source = loader.render_to_string(template, context_instance) - template = jingo.env.from_string(source) - # This interferes with our media() helper. - if 'media' in self.context_data: - del self.context_data['media'] - - # ``render_to_string`` only accepts a Template instance or a template name, - # not a list. - if isinstance(template, (list, tuple)): - template = loader.select_template(template) - return jingo.render_to_string(request, template, self.context_data) - -SimpleTemplateResponse.rendered_content = property(rendered_content) diff --git a/apps/zadmin/models.py b/apps/zadmin/models.py deleted file mode 100644 index 64ba5c674b8..00000000000 --- a/apps/zadmin/models.py +++ /dev/null @@ -1,109 +0,0 @@ -import json - -from django.conf import settings -from django.db import models -from django.utils.functional import memoize - -import amo -import amo.models - - -_config_cache = {} - - -class Config(models.Model): - """Sitewide settings.""" - key = models.CharField(max_length=255, primary_key=True) - value = models.TextField() - - class Meta: - db_table = u'config' - - @property - def json(self): - try: - return json.loads(self.value) - except (TypeError, ValueError): - return {} - - -def unmemoized_get_config(conf): - try: - c = Config.objects.get(key=conf) - return c.value - except Config.DoesNotExist: - return - -get_config = memoize(unmemoized_get_config, _config_cache, 1) - - -def set_config(conf, value): - cf, created = Config.objects.get_or_create(key=conf) - cf.value = value - cf.save() - _config_cache.clear() - - -class EmailPreviewTopic(object): - """Store emails in a given topic so an admin can preview before - re-sending. - - A topic is a unique string identifier that groups together preview emails. - If you pass in an object (a Model instance) you will get a poor man's - foreign key as your topic. - - For example, EmailPreviewTopic(addon) will link all preview emails to - the ID of that addon object. - """ - - def __init__(self, object=None, suffix='', topic=None): - if not topic: - assert object, 'object keyword is required when topic is empty' - topic = '%s-%s-%s' % (object.__class__._meta.db_table, object.pk, - suffix) - self.topic = topic - - def filter(self, *args, **kw): - kw['topic'] = self.topic - return EmailPreview.objects.filter(**kw) - - def send_mail(self, subject, body, - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=tuple([])): - return EmailPreview.objects.create( - topic=self.topic, - subject=subject, body=body, - recipient_list=u','.join(recipient_list), - from_email=from_email) - - -class EmailPreview(amo.models.ModelBase): - """A log of emails for previewing purposes. - - This is only for development and the data might get deleted at any time. - """ - topic = models.CharField(max_length=255, db_index=True) - recipient_list = models.TextField() # comma separated list of emails - from_email = models.EmailField() - subject = models.CharField(max_length=255) - body = models.TextField() - - class Meta: - db_table = 'email_preview' - - -class DownloadSource(models.Model): - # e.g., `mkt-search` or `mkt-detail-`. - name = models.CharField(max_length=255) - - # e.g., `full` or `prefix`. - type = models.CharField(max_length=255) - - description = models.TextField() - created = models.DateTimeField(auto_now_add=True) - - class Meta: - db_table = 'download_sources' - - def __unicode__(self): - return u'%s (%s)' % (self.name, self.type) diff --git a/apps/zadmin/templates/zadmin/addon-search.html b/apps/zadmin/templates/zadmin/addon-search.html deleted file mode 100644 index 2106a6c2a2e..00000000000 --- a/apps/zadmin/templates/zadmin/addon-search.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "admin/base.html" %} - -{% block title %}{{ page_title('Add-on Search') }}{% endblock %} - -{% block content %} -

Search Add-ons

-{% include "zadmin/includes/search_addons.html" %} - -{% if q %} -

Results for {{ q }} (1–{{ addons|length }} of {{ addons.count() }}):

- {% if addons %} - - {% else %} -

Nothing found!

- {% endif %} -{% endif %} - -{% endblock %} diff --git a/apps/zadmin/templates/zadmin/addon_manage.html b/apps/zadmin/templates/zadmin/addon_manage.html deleted file mode 100644 index 93884eae6f6..00000000000 --- a/apps/zadmin/templates/zadmin/addon_manage.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "admin/base.html" %} - -{% block title %}{{ page_title('Manage {0}')|f(addon.name) }}{% endblock %} - -{% block content %} -

Manage {{ addon.name }}

-
-

- Public Listing | - Edit Add-on -

-

- {{ form.status }}
- {{ form.highest_status }}
-

- {% if versions %} -

Versions & Files

- - - - - - - - - - - - - {% for v in versions %} - - - - {% with files = file_map.get(v.id, []) %} - {% if files %} - {% for file in files %} - {% if not loop.first %} - - - - {% endfor %} - {% else %} - - {% endif %} - {% endwith %} - - {% endfor %} -
DateVersionFile IDPlatformStatusHash
{{ v.created|datetime }}>{{ v.version }}
{% endif %} - {{ file.id }}{{ file.platform }} - {{ form_map[file.id].status }} - {% for hidden in form_map[file.id].hidden_fields() %} - {{ hidden }} - {% endfor %} - Recalc Hash
- {% endif %} -

- {{ formset.management_form }} - {{ csrf() }} - {% if pager.paginator.num_pages > 1 %} - - {% endif %} -
- -{% endblock %} diff --git a/apps/zadmin/tests/__init__.py b/apps/zadmin/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/apps/zadmin/tests/test_views.py b/apps/zadmin/tests/test_views.py deleted file mode 100644 index 47f1185e698..00000000000 --- a/apps/zadmin/tests/test_views.py +++ /dev/null @@ -1,438 +0,0 @@ -# -*- coding: utf-8 -*- -import csv -import json -from cStringIO import StringIO - -from django.core import mail, management -from django.core.cache import cache - -import mock -from nose.tools import eq_ -from pyquery import PyQuery as pq - -import amo -import amo.tests -from addons.models import Addon -from amo.urlresolvers import reverse -from amo.utils import urlparams -from files.models import File -from mkt.access.models import Group, GroupUser -from mkt.versions.models import Version -from users.models import UserProfile -from zadmin.forms import DevMailerForm -from zadmin.models import EmailPreviewTopic - - -class TestEmailPreview(amo.tests.TestCase): - fixtures = ['base/addon_3615', 'base/users'] - - def setUp(self): - assert self.client.login(username='admin@mozilla.com', - password='password') - addon = Addon.objects.get(pk=3615) - self.topic = EmailPreviewTopic(addon) - - def test_csv(self): - self.topic.send_mail('the subject', u'Hello Ivan Krsti\u0107', - from_email='admin@mozilla.org', - recipient_list=['funnyguy@mozilla.org']) - r = self.client.get(reverse('zadmin.email_preview_csv', - args=[self.topic.topic])) - eq_(r.status_code, 200) - rdr = csv.reader(StringIO(r.content)) - eq_(rdr.next(), ['from_email', 'recipient_list', 'subject', 'body']) - eq_(rdr.next(), ['admin@mozilla.org', 'funnyguy@mozilla.org', - 'the subject', 'Hello Ivan Krsti\xc4\x87']) - - -class TestLookup(amo.tests.TestCase): - fixtures = ['base/users'] - - def setUp(self): - assert self.client.login(username='admin@mozilla.com', - password='password') - self.user = UserProfile.objects.get(pk=999) - self.url = reverse('zadmin.search', args=['users', 'userprofile']) - - def test_logged_out(self): - self.client.logout() - eq_(self.client.get('%s?q=admin' % self.url).status_code, 403) - - def check_results(self, q, expected): - res = self.client.get(urlparams(self.url, q=q)) - eq_(res.status_code, 200) - content = json.loads(res.content) - eq_(len(content), len(expected)) - ids = [int(c['value']) for c in content] - emails = [u'%s' % c['label'] for c in content] - for d in expected: - id = d['value'] - email = u'%s' % d['label'] - assert id in ids, ( - 'Expected user ID "%s" not found' % id) - assert email in emails, ( - 'Expected username "%s" not found' % email) - - def test_lookup_wrong_model(self): - self.url = reverse('zadmin.search', args=['doesnt', 'exist']) - res = self.client.get(urlparams(self.url, q='')) - eq_(res.status_code, 404) - - def test_lookup_empty(self): - users = UserProfile.objects.values('id', 'email') - self.check_results('', [dict( - value=u['id'], label=u['email']) for u in users]) - - def test_lookup_by_id(self): - self.check_results(self.user.id, [dict(value=self.user.id, - label=self.user.email)]) - - def test_lookup_by_email(self): - self.check_results(self.user.email, [dict(value=self.user.id, - label=self.user.email)]) - - def test_lookup_by_username(self): - self.check_results(self.user.username, [dict(value=self.user.id, - label=self.user.email)]) - - -class TestAddonSearch(amo.tests.ESTestCase): - fixtures = ['base/users', 'base/addon_3615'] - - def setUp(self): - self.reindex(Addon) - assert self.client.login(username='admin@mozilla.com', - password='password') - self.url = reverse('zadmin.addon-search') - - @mock.patch('mkt.webapps.tasks.index_webapps') - def test_lookup_app(self, index_webapps_mock): - # Load the Webapp fixture here, as loading it in the - # TestAddonSearch.fixtures would trigger the reindex, and fail, as - # this is an AMO test. - management.call_command('loaddata', 'base/337141-steamcube') - index_webapps_mock.assert_called() - - res = self.client.get(urlparams(self.url, q='steamcube')) - eq_(res.status_code, 200) - links = pq(res.content)('form + h3 + ul li a') - eq_(len(links), 0) - if any(li.text().contains('Steamcube') for li in links): - raise AssertionError('Did not expect webapp in results.') - - def test_lookup_addon(self): - res = self.client.get(urlparams(self.url, q='delicious')) - # There's only one result, so it should just forward us to that page. - eq_(res.status_code, 302) - - -class TestAddonAdmin(amo.tests.TestCase): - fixtures = ['base/users', 'base/337141-steamcube', 'base/addon_3615'] - - def setUp(self): - assert self.client.login(username='admin@mozilla.com', - password='password') - self.url = reverse('admin:addons_addon_changelist') - - def test_no_webapps(self): - res = self.client.get(self.url) - doc = pq(res.content) - rows = doc('#result_list tbody tr') - eq_(rows.length, 1) - eq_(rows.find('a').attr('href'), - '/en-US/admin/models/addons/addon/3615/') - - -class TestAddonManagement(amo.tests.TestCase): - fixtures = ['base/addon_3615', 'base/users'] - - def setUp(self): - self.addon = Addon.objects.get(pk=3615) - self.url = reverse('zadmin.addon_manage', args=[self.addon.slug]) - self.client.login(username='admin@mozilla.com', password='password') - - def _form_data(self, data=None): - initial_data = { - 'status': '4', - 'highest_status': '4', - 'form-0-status': '4', - 'form-0-id': '67442', - 'form-TOTAL_FORMS': '1', - 'form-INITIAL_FORMS': '1', - } - if data: - initial_data.update(data) - return initial_data - - def test_addon_status_change(self): - data = self._form_data({'status': '2'}) - r = self.client.post(self.url, data, follow=True) - eq_(r.status_code, 200) - addon = Addon.objects.get(pk=3615) - eq_(addon.status, 2) - - def test_addon_file_status_change(self): - data = self._form_data({'form-0-status': '2'}) - r = self.client.post(self.url, data, follow=True) - eq_(r.status_code, 200) - file = File.objects.get(pk=67442) - eq_(file.status, 2) - - @mock.patch.object(File, 'file_path', - amo.tests.AMOPaths().file_fixture_path( - 'delicious_bookmarks-2.1.106-fx.xpi')) - def test_regenerate_hash(self): - version = Version.objects.create(addon_id=3615) - file = File.objects.create( - filename='delicious_bookmarks-2.1.106-fx.xpi', version=version) - - r = self.client.post(reverse('zadmin.recalc_hash', args=[file.id])) - eq_(json.loads(r.content)[u'success'], 1) - - file = File.objects.get(pk=file.id) - - assert file.size, 'File size should not be zero' - assert file.hash, 'File hash should not be empty' - - @mock.patch.object(File, 'file_path', - amo.tests.AMOPaths().file_fixture_path( - 'delicious_bookmarks-2.1.106-fx.xpi')) - def test_regenerate_hash_get(self): - """ Don't allow GET """ - version = Version.objects.create(addon_id=3615) - file = File.objects.create( - filename='delicious_bookmarks-2.1.106-fx.xpi', version=version) - - r = self.client.get(reverse('zadmin.recalc_hash', args=[file.id])) - eq_(r.status_code, 405) # GET out of here - - -class TestMemcache(amo.tests.TestCase): - fixtures = ['base/addon_3615', 'base/users'] - - def setUp(self): - self.url = reverse('zadmin.memcache') - cache.set('foo', 'bar') - self.client.login(username='admin@mozilla.com', password='password') - - def test_login(self): - self.client.logout() - eq_(self.client.get(self.url).status_code, 302) - - def test_can_clear(self): - self.client.post(self.url, {'yes': 'True'}) - eq_(cache.get('foo'), None) - - def test_cant_clear(self): - self.client.post(self.url, {'yes': 'False'}) - eq_(cache.get('foo'), 'bar') - - -class TestElastic(amo.tests.ESTestCase): - fixtures = ['base/addon_3615', 'base/users'] - - def setUp(self): - self.url = reverse('zadmin.elastic') - self.client.login(username='admin@mozilla.com', password='password') - - def test_login(self): - self.client.logout() - self.assertRedirects(self.client.get(self.url), - reverse('users.login') + '?to=/en-US/admin/elastic') - - -class TestEmailDevs(amo.tests.TestCase): - fixtures = ['base/addon_3615', 'base/users'] - - def setUp(self): - self.login('admin') - self.addon = Addon.objects.get(pk=3615) - - def post(self, recipients='payments', subject='subject', message='msg', - preview_only=False): - return self.client.post(reverse('zadmin.email_devs'), - dict(recipients=recipients, subject=subject, - message=message, - preview_only=preview_only)) - - def test_preview(self): - res = self.post(preview_only=True) - self.assertNoFormErrors(res) - preview = EmailPreviewTopic(topic='email-devs') - eq_([e.recipient_list for e in preview.filter()], ['del@icio.us']) - eq_(len(mail.outbox), 0) - - def test_only_apps_with_payments(self): - self.addon.update(type=amo.ADDON_WEBAPP, - premium_type=amo.ADDON_PREMIUM) - res = self.post(recipients='payments') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(status=amo.STATUS_PENDING) - res = self.post(recipients='payments') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(status=amo.STATUS_DELETED) - res = self.post(recipients='payments') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - def test_only_free_apps_with_new_regions(self): - self.addon.update(type=amo.ADDON_WEBAPP) - res = self.post(recipients='free_apps_region_enabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - mail.outbox = [] - res = self.post(recipients='free_apps_region_disabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(enable_new_regions=True) - res = self.post(recipients='free_apps_region_enabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - mail.outbox = [] - res = self.post(recipients='free_apps_region_disabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - def test_only_apps_with_payments_and_new_regions(self): - self.addon.update(type=amo.ADDON_WEBAPP, - premium_type=amo.ADDON_PREMIUM) - res = self.post(recipients='payments_region_enabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - mail.outbox = [] - res = self.post(recipients='payments_region_disabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(enable_new_regions=True) - res = self.post(recipients='payments_region_enabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - mail.outbox = [] - res = self.post(recipients='payments_region_disabled') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - def test_only_desktop_apps(self): - from addons.models import AddonDeviceType - self.addon.update(type=amo.ADDON_WEBAPP) - AddonDeviceType.objects.create(addon=self.addon, - device_type=amo.DEVICE_MOBILE.id) - res = self.post(recipients='desktop_apps') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - mail.outbox = [] - AddonDeviceType.objects.create(addon=self.addon, - device_type=amo.DEVICE_DESKTOP.id) - res = self.post(recipients='desktop_apps') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(status=amo.STATUS_PENDING) - res = self.post(recipients='desktop_apps') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - mail.outbox = [] - self.addon.update(status=amo.STATUS_DELETED) - res = self.post(recipients='desktop_apps') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - def test_only_apps(self): - self.addon.update(type=amo.ADDON_WEBAPP) - res = self.post(recipients='apps') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - def test_only_extensions(self): - self.addon.update(type=amo.ADDON_EXTENSION) - res = self.post(recipients='all_extensions') - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 1) - - def test_ignore_deleted_always(self): - self.addon.update(status=amo.STATUS_DELETED) - for name, label in DevMailerForm._choices: - res = self.post(recipients=name) - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - def test_exclude_pending_for_addons(self): - self.addon.update(status=amo.STATUS_PENDING) - for name, label in DevMailerForm._choices: - if name in ('payments', 'desktop_apps'): - continue - res = self.post(recipients=name) - self.assertNoFormErrors(res) - eq_(len(mail.outbox), 0) - - -class TestPerms(amo.tests.TestCase): - fixtures = ['base/users', 'base/apps'] - - def test_admin_user(self): - # Admin should see views with Django's perm decorator and our own. - assert self.client.login(username='admin@mozilla.com', - password='password') - eq_(self.client.get(reverse('zadmin.index')).status_code, 200) - eq_(self.client.get(reverse('zadmin.settings')).status_code, 200) - eq_(self.client.get(reverse('zadmin.addon-search')).status_code, 200) - - def test_staff_user(self): - # Staff users have some privileges. - user = UserProfile.objects.get(email='regular@mozilla.com') - group = Group.objects.create(name='Staff', rules='AdminTools:View') - GroupUser.objects.create(group=group, user=user) - assert self.client.login(username='regular@mozilla.com', - password='password') - eq_(self.client.get(reverse('zadmin.index')).status_code, 200) - eq_(self.client.get(reverse('zadmin.settings')).status_code, 200) - eq_(self.client.get(reverse('zadmin.addon-search')).status_code, 200) - - def test_sr_reviewers_user(self): - # Sr Reviewers users have only a few privileges. - user = UserProfile.objects.get(email='regular@mozilla.com') - group = Group.objects.create(name='Sr Reviewer', - rules='ReviewerAdminTools:View') - GroupUser.objects.create(group=group, user=user) - assert self.client.login(username='regular@mozilla.com', - password='password') - eq_(self.client.get(reverse('zadmin.index')).status_code, 200) - eq_(self.client.get(reverse('zadmin.addon-search')).status_code, 200) - eq_(self.client.get(reverse('zadmin.settings')).status_code, 403) - - def test_bulk_compat_user(self): - # Bulk Compatibility Updaters only have access to /admin/validation/*. - user = UserProfile.objects.get(email='regular@mozilla.com') - group = Group.objects.create(name='Bulk Compatibility Updaters', - rules='BulkValidationAdminTools:View') - GroupUser.objects.create(group=group, user=user) - assert self.client.login(username='regular@mozilla.com', - password='password') - eq_(self.client.get(reverse('zadmin.index')).status_code, 200) - eq_(self.client.get(reverse('zadmin.addon-search')).status_code, 403) - eq_(self.client.get(reverse('zadmin.settings')).status_code, 403) - - def test_unprivileged_user(self): - # Unprivileged user. - assert self.client.login(username='regular@mozilla.com', - password='password') - eq_(self.client.get(reverse('zadmin.index')).status_code, 403) - eq_(self.client.get(reverse('zadmin.settings')).status_code, 403) - eq_(self.client.get(reverse('zadmin.addon-search')).status_code, 403) - # Anonymous users should also get a 403. - self.client.logout() - self.assertRedirects(self.client.get(reverse('zadmin.index')), - reverse('users.login') + '?to=/en-US/admin/') diff --git a/apps/zadmin/urls.py b/apps/zadmin/urls.py deleted file mode 100644 index 0a9cb5fadd7..00000000000 --- a/apps/zadmin/urls.py +++ /dev/null @@ -1,57 +0,0 @@ -from django.conf.urls import include, patterns, url -from django.contrib import admin -from django.core.exceptions import PermissionDenied -from django.shortcuts import redirect - -from amo.urlresolvers import reverse -from . import views - - -ADDON_ID = r"""(?P[^/<>"']+)""" - - -urlpatterns = patterns('', - # AMO stuff. - url('^$', views.index, name='zadmin.index'), - url('^models$', lambda r: redirect('admin:index'), name='zadmin.home'), - url('^addon/manage/%s/$' % ADDON_ID, - views.addon_manage, name='zadmin.addon_manage'), - url('^addon/recalc-hash/(?P\d+)/', views.recalc_hash, - name='zadmin.recalc_hash'), - url('^env$', views.env, name='amo.env'), - url('^memcache$', views.memcache, name='zadmin.memcache'), - url('^settings', views.show_settings, name='zadmin.settings'), - url('^fix-disabled', views.fix_disabled_file, name='zadmin.fix-disabled'), - url(r'^email_preview/(?P.*)\.csv$', - views.email_preview_csv, name='zadmin.email_preview_csv'), - - url('^mail$', views.mail, name='zadmin.mail'), - url('^email-devs$', views.email_devs, name='zadmin.email_devs'), - url('^generate-error$', views.generate_error, - name='zadmin.generate-error'), - - url('^export_email_addresses$', views.export_email_addresses, - name='zadmin.export_email_addresses'), - url('^email_addresses_file$', views.email_addresses_file, - name='zadmin.email_addresses_file'), - - url('^price-tiers$', views.price_tiers, name='zadmin.price_tiers'), - - # The Django admin. - url('^models/', include(admin.site.urls)), - url('^models/(?P.+)/(?P.+)/search.json$', - views.general_search, name='zadmin.search'), -) - - -# Hijack the admin's login to use our pages. -def login(request): - # If someone is already auth'd then they're getting directed to login() - # because they don't have sufficient permissions. - if request.user.is_authenticated(): - raise PermissionDenied - else: - return redirect('%s?to=%s' % (reverse('users.login'), request.path)) - - -admin.site.login = login diff --git a/apps/zadmin/views.py b/apps/zadmin/views.py deleted file mode 100644 index 67ca5de0f2c..00000000000 --- a/apps/zadmin/views.py +++ /dev/null @@ -1,305 +0,0 @@ -import csv - -from django import http -from django.conf import settings -from django.contrib import admin -from django.core.cache import cache -from django.core.exceptions import PermissionDenied -from django.core.files.storage import default_storage as storage -from django.db.models.loading import cache as app_cache -from django.shortcuts import get_object_or_404, redirect, render -from django.views import debug -from django.views.decorators.cache import never_cache - -import commonware.log -import jinja2 - -import amo -from addons.decorators import addon_view -from addons.models import AddonUser -from amo import messages -from amo.decorators import any_permission_required, json_view, post_required -from amo.mail import FakeEmailBackend -from amo.urlresolvers import reverse -from amo.utils import chunked -from files.models import File -from mkt.developers.models import ActivityLog -from mkt.prices.utils import update_from_csv -from users.models import UserProfile -from zadmin.forms import GenerateErrorForm, PriceTiersForm - -from . import tasks -from .decorators import admin_required -from .forms import AddonStatusForm, DevMailerForm, FileFormSet, YesImSure -from .models import EmailPreviewTopic - - -log = commonware.log.getLogger('z.zadmin') - - -@admin_required -def show_settings(request): - settings_dict = debug.get_safe_settings() - - for i in ['GOOGLE_ANALYTICS_CREDENTIALS']: - settings_dict[i] = debug.cleanse_setting(i, - getattr(settings, i, {})) - - settings_dict['WEBAPPS_RECEIPT_KEY'] = '********************' - - return render(request, 'zadmin/settings.html', - {'settings_dict': settings_dict}) - - -@admin_required -def env(request): - return http.HttpResponse(u'
%s
' % (jinja2.escape(request))) - - -@admin.site.admin_view -def fix_disabled_file(request): - file_ = None - if request.method == 'POST' and 'file' in request.POST: - file_ = get_object_or_404(File, id=request.POST['file']) - if 'confirm' in request.POST: - file_.unhide_disabled_file() - messages.success(request, 'We have done a great thing.') - return redirect('zadmin.fix-disabled') - return render(request, 'zadmin/fix-disabled.html', - {'file': file_, 'file_id': request.POST.get('file', '')}) - - -@any_permission_required([('Admin', '%'), - ('BulkValidationAdminTools', 'View')]) -def email_preview_csv(request, topic): - resp = http.HttpResponse() - resp['Content-Type'] = 'text/csv; charset=utf-8' - resp['Content-Disposition'] = "attachment; filename=%s.csv" % (topic) - writer = csv.writer(resp) - fields = ['from_email', 'recipient_list', 'subject', 'body'] - writer.writerow(fields) - rs = EmailPreviewTopic(topic=topic).filter().values_list(*fields) - for row in rs: - writer.writerow([r.encode('utf8') for r in row]) - return resp - - -@admin.site.admin_view -def mail(request): - backend = FakeEmailBackend() - if request.method == 'POST': - backend.clear() - return redirect('zadmin.mail') - return render(request, 'zadmin/mail.html', dict(mail=backend.view_all())) - - -@admin.site.admin_view -def email_devs(request): - form = DevMailerForm(request.POST or None) - preview = EmailPreviewTopic(topic='email-devs') - if preview.filter().count(): - preview_csv = reverse('zadmin.email_preview_csv', - args=[preview.topic]) - else: - preview_csv = None - if request.method == 'POST' and form.is_valid(): - data = form.cleaned_data - qs = (AddonUser.objects.filter(role__in=(amo.AUTHOR_ROLE_DEV, - amo.AUTHOR_ROLE_OWNER)) - .exclude(user__email=None)) - - if data['recipients'] in ('payments', 'desktop_apps'): - qs = qs.exclude(addon__status=amo.STATUS_DELETED) - else: - qs = qs.filter(addon__status__in=amo.LISTED_STATUSES) - - if data['recipients'] in ('payments', 'payments_region_enabled', - 'payments_region_disabled'): - qs = qs.filter(addon__type=amo.ADDON_WEBAPP) - qs = qs.exclude(addon__premium_type__in=(amo.ADDON_FREE, - amo.ADDON_OTHER_INAPP)) - if data['recipients'] == 'payments_region_enabled': - qs = qs.filter(addon__enable_new_regions=True) - elif data['recipients'] == 'payments_region_disabled': - qs = qs.filter(addon__enable_new_regions=False) - elif data['recipients'] in ('apps', 'free_apps_region_enabled', - 'free_apps_region_disabled'): - qs = qs.filter(addon__type=amo.ADDON_WEBAPP) - if data['recipients'] == 'free_apps_region_enabled': - qs = qs.filter(addon__enable_new_regions=True) - elif data['recipients'] == 'free_apps_region_disabled': - qs = qs.filter(addon__enable_new_regions=False) - elif data['recipients'] == 'desktop_apps': - qs = (qs.filter(addon__type=amo.ADDON_WEBAPP, - addon__addondevicetype__device_type=amo.DEVICE_DESKTOP.id)) - elif data['recipients'] == 'all_extensions': - qs = qs.filter(addon__type=amo.ADDON_EXTENSION) - else: - raise NotImplementedError('If you want to support emailing other ' - 'types of developers, do it here!') - if data['preview_only']: - # Clear out the last batch of previewed emails. - preview.filter().delete() - total = 0 - for emails in chunked(set(qs.values_list('user__email', flat=True)), - 100): - total += len(emails) - tasks.admin_email.delay(emails, data['subject'], data['message'], - preview_only=data['preview_only'], - preview_topic=preview.topic) - msg = 'Emails queued for delivery: %s' % total - if data['preview_only']: - msg = '%s (for preview only, emails not sent!)' % msg - messages.success(request, msg) - return redirect('zadmin.email_devs') - return render(request, 'zadmin/email-devs.html', - dict(form=form, preview_csv=preview_csv)) - - -@any_permission_required([('Admin', '%'), - ('AdminTools', 'View'), - ('ReviewerAdminTools', 'View'), - ('BulkValidationAdminTools', 'View')]) -def index(request): - log = ActivityLog.objects.admin_events()[:5] - return render(request, 'zadmin/index.html', {'log': log}) - - -@never_cache -@json_view -def general_search(request, app_id, model_id): - if not admin.site.has_permission(request): - raise PermissionDenied - - model = app_cache.get_model(app_id, model_id) - if not model: - raise http.Http404 - - limit = 10 - obj = admin.site._registry[model] - ChangeList = obj.get_changelist(request) - # This is a hideous api, but uses the builtin admin search_fields API. - # Expecting this to get replaced by ES so soon, that I'm not going to lose - # too much sleep about it. - cl = ChangeList(request, obj.model, [], [], [], [], obj.search_fields, [], - obj.list_max_show_all, limit, [], obj) - qs = cl.get_query_set(request) - # Override search_fields_response on the ModelAdmin object - # if you'd like to pass something else back to the front end. - lookup = getattr(obj, 'search_fields_response', None) - return [{'value': o.pk, 'label': getattr(o, lookup) if lookup else str(o)} - for o in qs[:limit]] - - -@admin_required(reviewers=True) -@addon_view -def addon_manage(request, addon): - - form = AddonStatusForm(request.POST or None, instance=addon) - pager = amo.utils.paginate(request, addon.versions.all(), 30) - # A list coercion so this doesn't result in a subquery with a LIMIT which - # MySQL doesn't support (at this time). - versions = list(pager.object_list) - files = File.objects.filter(version__in=versions).select_related('version') - formset = FileFormSet(request.POST or None, queryset=files) - - if form.is_valid() and formset.is_valid(): - if 'status' in form.changed_data: - amo.log(amo.LOG.CHANGE_STATUS, addon, form.cleaned_data['status']) - log.info('Addon "%s" status changed to: %s' % ( - addon.slug, form.cleaned_data['status'])) - form.save() - if 'highest_status' in form.changed_data: - log.info('Addon "%s" highest status changed to: %s' % ( - addon.slug, form.cleaned_data['highest_status'])) - form.save() - - for form in formset: - if 'status' in form.changed_data: - log.info('Addon "%s" file (ID:%d) status changed to: %s' % ( - addon.slug, form.instance.id, form.cleaned_data['status'])) - form.save() - return redirect('zadmin.addon_manage', addon.slug) - - # Build a map from file.id to form in formset for precise form display - form_map = dict((form.instance.id, form) for form in formset.forms) - # A version to file map to avoid an extra query in the template - file_map = {} - for file in files: - file_map.setdefault(file.version_id, []).append(file) - - return render(request, 'zadmin/addon_manage.html', { - 'addon': addon, 'pager': pager, 'versions': versions, 'form': form, - 'formset': formset, 'form_map': form_map, 'file_map': file_map}) - - -@admin.site.admin_view -@post_required -@json_view -def recalc_hash(request, file_id): - - file = get_object_or_404(File, pk=file_id) - file.size = storage.size(file.file_path) - file.hash = file.generate_hash() - file.save() - - log.info('Recalculated hash for file ID %d' % file.id) - messages.success(request, - 'File hash and size recalculated for file %d.' % file.id) - return {'success': 1} - - -@admin.site.admin_view -def memcache(request): - form = YesImSure(request.POST or None) - if form.is_valid() and form.cleaned_data['yes']: - cache.clear() - form = YesImSure() - messages.success(request, 'Cache cleared') - if cache._cache and hasattr(cache._cache, 'get_stats'): - stats = cache._cache.get_stats() - else: - stats = [] - return render(request, 'zadmin/memcache.html', - {'form': form, 'stats': stats}) - - -@admin_required -def generate_error(request): - form = GenerateErrorForm(request.POST or None) - if request.method == 'POST' and form.is_valid(): - form.explode() - return render(request, 'zadmin/generate-error.html', {'form': form}) - - -@any_permission_required([('Admin', '%'), - ('MailingLists', 'View')]) -def export_email_addresses(request): - return render(request, 'zadmin/export_button.html', {}) - - -@any_permission_required([('Admin', '%'), - ('MailingLists', 'View')]) -def email_addresses_file(request): - resp = http.HttpResponse() - resp['Content-Type'] = 'text/plain; charset=utf-8' - resp['Content-Disposition'] = ('attachment; ' - 'filename=amo_optin_emails.txt') - emails = (UserProfile.objects.filter(notifications__notification_id=13, - notifications__enabled=1) - .values_list('email', flat=True)) - for e in emails: - if e is not None: - resp.write(e + '\n') - return resp - - -@admin_required -def price_tiers(request): - output = [] - form = PriceTiersForm(request.POST or None, request.FILES) - if request.method == 'POST' and form.is_valid(): - output = update_from_csv(form.cleaned_data['prices']) - - return render(request, 'zadmin/update-prices.html', - {'result': output, 'form': form}) diff --git a/mkt/reviewers/tests/test_views.py b/mkt/reviewers/tests/test_views.py index 29fdcdf7c18..869d8563d78 100644 --- a/mkt/reviewers/tests/test_views.py +++ b/mkt/reviewers/tests/test_views.py @@ -51,8 +51,8 @@ from mkt.versions.models import Version from mkt.webapps.models import Webapp from mkt.webapps.tests.test_models import PackagedFilesMixin +from mkt.zadmin.models import get_config, set_config from users.models import UserProfile -from zadmin.models import get_config, set_config TEST_PATH = path.dirname(path.abspath(__file__)) diff --git a/mkt/reviewers/views.py b/mkt/reviewers/views.py index 8d332f4c98b..0a61d55669e 100644 --- a/mkt/reviewers/views.py +++ b/mkt/reviewers/views.py @@ -69,9 +69,9 @@ from mkt.submit.forms import AppFeaturesForm from mkt.tags.models import Tag from mkt.webapps.models import Webapp, WebappIndexer +from mkt.zadmin.models import set_config, unmemoized_get_config from translations.query import order_by_translation from users.models import UserProfile -from zadmin.models import set_config, unmemoized_get_config from . import forms from .models import AppCannedResponse diff --git a/mkt/settings.py b/mkt/settings.py index 9c6e86445a5..5a8303e32f2 100644 --- a/mkt/settings.py +++ b/mkt/settings.py @@ -94,7 +94,6 @@ 'tower', # for ./manage.py extract 'translations', 'users', - 'zadmin', # Third party apps 'djcelery', @@ -146,6 +145,7 @@ 'mkt.zadmin', 'mkt.webapps', 'mkt.webpay', + 'mkt.zadmin', ) MIDDLEWARE_CLASSES = ( diff --git a/mkt/site/context_processors.py b/mkt/site/context_processors.py index 722797976c4..61411b9c603 100644 --- a/mkt/site/context_processors.py +++ b/mkt/site/context_processors.py @@ -8,7 +8,7 @@ import mkt from amo.context_processors import get_collect_timings from mkt.access import acl -from zadmin.models import get_config +from mkt.zadmin.models import get_config def global_settings(request): diff --git a/mkt/urls.py b/mkt/urls.py index 1c0aa40200e..caffffd25c6 100644 --- a/mkt/urls.py +++ b/mkt/urls.py @@ -62,9 +62,6 @@ # webpay / nav.pay() services. ('^services/webpay/', include(webpay_services_patterns)), - # AMO admin (not django admin). - ('^admin/', include('zadmin.urls')), - # AMO Marketplace admin (not django admin). ('^admin/', include('mkt.zadmin.urls')), diff --git a/mkt/zadmin/__init__.py b/mkt/zadmin/__init__.py index e69de29bb2d..219cd149359 100644 --- a/mkt/zadmin/__init__.py +++ b/mkt/zadmin/__init__.py @@ -0,0 +1,48 @@ +from django.shortcuts import render +from django.template import loader +from django.template.response import SimpleTemplateResponse + +import jingo + + +def jinja_for_django(template_name, context=None, **kw): + """ + If you want to use some built in logic (or a contrib app) but need to + override the templates to work with Jinja, replace the object's + render_to_response function with this one. That will render a Jinja + template through Django's functions. An example can be found in the users + app. + """ + if context is None: + context = {} + context_instance = kw.pop('context_instance') + request = context_instance['request'] + for d in context_instance.dicts: + context.update(d) + return render(request, template_name, context, **kw) + + +# We monkeypatch SimpleTemplateResponse.rendered_content to use our jinja +# rendering pipeline (most of the time). The exception is the admin app, where +# we render their Django templates and pipe the result through jinja to render +# our page skeleton. +def rendered_content(self): + template = self.template_name + context_instance = self.resolve_context(self.context_data) + request = context_instance['request'] + + # Gross, let's figure out if we're in the admin. + if self._current_app == 'admin': + source = loader.render_to_string(template, context_instance) + template = jingo.env.from_string(source) + # This interferes with our media() helper. + if 'media' in self.context_data: + del self.context_data['media'] + + # ``render_to_string`` only accepts a Template instance or a template name, + # not a list. + if isinstance(template, (list, tuple)): + template = loader.select_template(template) + return jingo.render_to_string(request, template, self.context_data) + +SimpleTemplateResponse.rendered_content = property(rendered_content) diff --git a/apps/zadmin/admin.py b/mkt/zadmin/admin.py similarity index 100% rename from apps/zadmin/admin.py rename to mkt/zadmin/admin.py diff --git a/apps/zadmin/decorators.py b/mkt/zadmin/decorators.py similarity index 96% rename from apps/zadmin/decorators.py rename to mkt/zadmin/decorators.py index 75f9e8c7b81..1ef36075391 100644 --- a/apps/zadmin/decorators.py +++ b/mkt/zadmin/decorators.py @@ -18,7 +18,7 @@ def decorator(f): def wrapper(request, *args, **kw): admin = (action_allowed(request, 'Admin', '%') or action_allowed(request, 'AdminTools', 'View')) - if reviewers: + if reviewers is True: admin = ( admin or action_allowed(request, 'ReviewerAdminTools', 'View')) diff --git a/apps/zadmin/fixtures/zadmin/users.json b/mkt/zadmin/fixtures/zadmin/users.json similarity index 100% rename from apps/zadmin/fixtures/zadmin/users.json rename to mkt/zadmin/fixtures/zadmin/users.json diff --git a/apps/zadmin/forms.py b/mkt/zadmin/forms.py similarity index 84% rename from apps/zadmin/forms.py rename to mkt/zadmin/forms.py index 868312b3389..ce86efa362c 100644 --- a/apps/zadmin/forms.py +++ b/mkt/zadmin/forms.py @@ -1,14 +1,8 @@ from django import forms from django.conf import settings -from django.forms import ModelForm -from django.forms.models import modelformset_factory import commonware.log import happyforms -from quieter_formset.formset import BaseModelFormSet - -from addons.models import Addon -from files.models import File LOGGER_NAME = 'z.zadmin' @@ -16,8 +10,7 @@ class DevMailerForm(happyforms.Form): - _choices = [('sdk', 'Developers of active SDK add-ons'), - ('apps', 'Developers of active apps (not add-ons)'), + _choices = [('apps', 'Developers of active apps (not add-ons)'), ('free_apps_region_enabled', 'Developers of free apps and new region enabled'), ('free_apps_region_disabled', @@ -29,8 +22,7 @@ class DevMailerForm(happyforms.Form): ('payments_region_disabled', 'Developers of apps with payments and new regions disabled'), ('desktop_apps', - 'Developers of non-deleted apps supported on desktop'), - ('all_extensions', 'All extension developers')] + 'Developers of non-deleted apps supported on desktop')] recipients = forms.ChoiceField(choices=_choices, required=True) subject = forms.CharField(widget=forms.TextInput(attrs=dict(size='100')), required=True) @@ -39,22 +31,6 @@ class DevMailerForm(happyforms.Form): message = forms.CharField(widget=forms.Textarea, required=True) -class AddonStatusForm(ModelForm): - class Meta: - model = Addon - fields = ('status', 'highest_status') - - -class FileStatusForm(ModelForm): - class Meta: - model = File - fields = ('status',) - - -FileFormSet = modelformset_factory(File, form=FileStatusForm, - formset=BaseModelFormSet, extra=0) - - class YesImSure(happyforms.Form): yes = forms.BooleanField(required=True, label="Yes, I'm sure") diff --git a/mkt/zadmin/models.py b/mkt/zadmin/models.py index e69de29bb2d..64ba5c674b8 100644 --- a/mkt/zadmin/models.py +++ b/mkt/zadmin/models.py @@ -0,0 +1,109 @@ +import json + +from django.conf import settings +from django.db import models +from django.utils.functional import memoize + +import amo +import amo.models + + +_config_cache = {} + + +class Config(models.Model): + """Sitewide settings.""" + key = models.CharField(max_length=255, primary_key=True) + value = models.TextField() + + class Meta: + db_table = u'config' + + @property + def json(self): + try: + return json.loads(self.value) + except (TypeError, ValueError): + return {} + + +def unmemoized_get_config(conf): + try: + c = Config.objects.get(key=conf) + return c.value + except Config.DoesNotExist: + return + +get_config = memoize(unmemoized_get_config, _config_cache, 1) + + +def set_config(conf, value): + cf, created = Config.objects.get_or_create(key=conf) + cf.value = value + cf.save() + _config_cache.clear() + + +class EmailPreviewTopic(object): + """Store emails in a given topic so an admin can preview before + re-sending. + + A topic is a unique string identifier that groups together preview emails. + If you pass in an object (a Model instance) you will get a poor man's + foreign key as your topic. + + For example, EmailPreviewTopic(addon) will link all preview emails to + the ID of that addon object. + """ + + def __init__(self, object=None, suffix='', topic=None): + if not topic: + assert object, 'object keyword is required when topic is empty' + topic = '%s-%s-%s' % (object.__class__._meta.db_table, object.pk, + suffix) + self.topic = topic + + def filter(self, *args, **kw): + kw['topic'] = self.topic + return EmailPreview.objects.filter(**kw) + + def send_mail(self, subject, body, + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=tuple([])): + return EmailPreview.objects.create( + topic=self.topic, + subject=subject, body=body, + recipient_list=u','.join(recipient_list), + from_email=from_email) + + +class EmailPreview(amo.models.ModelBase): + """A log of emails for previewing purposes. + + This is only for development and the data might get deleted at any time. + """ + topic = models.CharField(max_length=255, db_index=True) + recipient_list = models.TextField() # comma separated list of emails + from_email = models.EmailField() + subject = models.CharField(max_length=255) + body = models.TextField() + + class Meta: + db_table = 'email_preview' + + +class DownloadSource(models.Model): + # e.g., `mkt-search` or `mkt-detail-`. + name = models.CharField(max_length=255) + + # e.g., `full` or `prefix`. + type = models.CharField(max_length=255) + + description = models.TextField() + created = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'download_sources' + + def __unicode__(self): + return u'%s (%s)' % (self.name, self.type) diff --git a/apps/zadmin/tasks.py b/mkt/zadmin/tasks.py similarity index 93% rename from apps/zadmin/tasks.py rename to mkt/zadmin/tasks.py index 7b854f8f334..8eadbb76b7c 100644 --- a/apps/zadmin/tasks.py +++ b/mkt/zadmin/tasks.py @@ -5,7 +5,7 @@ from celeryutils import task from amo.utils import send_mail -from zadmin.models import EmailPreviewTopic +from mkt.zadmin.models import EmailPreviewTopic log = logging.getLogger('z.task') diff --git a/apps/zadmin/templates/admin/base.html b/mkt/zadmin/templates/admin/base.html similarity index 100% rename from apps/zadmin/templates/admin/base.html rename to mkt/zadmin/templates/admin/base.html diff --git a/apps/zadmin/templates/admin/base_site.html b/mkt/zadmin/templates/admin/base_site.html similarity index 100% rename from apps/zadmin/templates/admin/base_site.html rename to mkt/zadmin/templates/admin/base_site.html diff --git a/apps/zadmin/templates/zadmin/email-devs.html b/mkt/zadmin/templates/zadmin/email-devs.html similarity index 100% rename from apps/zadmin/templates/zadmin/email-devs.html rename to mkt/zadmin/templates/zadmin/email-devs.html diff --git a/apps/zadmin/templates/zadmin/export_button.html b/mkt/zadmin/templates/zadmin/export_button.html similarity index 100% rename from apps/zadmin/templates/zadmin/export_button.html rename to mkt/zadmin/templates/zadmin/export_button.html diff --git a/apps/zadmin/templates/zadmin/fix-disabled.html b/mkt/zadmin/templates/zadmin/fix-disabled.html similarity index 100% rename from apps/zadmin/templates/zadmin/fix-disabled.html rename to mkt/zadmin/templates/zadmin/fix-disabled.html diff --git a/apps/zadmin/templates/zadmin/generate-error.html b/mkt/zadmin/templates/zadmin/generate-error.html similarity index 100% rename from apps/zadmin/templates/zadmin/generate-error.html rename to mkt/zadmin/templates/zadmin/generate-error.html diff --git a/apps/zadmin/templates/zadmin/index.html b/mkt/zadmin/templates/zadmin/index.html similarity index 100% rename from apps/zadmin/templates/zadmin/index.html rename to mkt/zadmin/templates/zadmin/index.html diff --git a/apps/zadmin/templates/zadmin/mail.html b/mkt/zadmin/templates/zadmin/mail.html similarity index 100% rename from apps/zadmin/templates/zadmin/mail.html rename to mkt/zadmin/templates/zadmin/mail.html diff --git a/apps/zadmin/templates/zadmin/manifest.html b/mkt/zadmin/templates/zadmin/manifest.html similarity index 100% rename from apps/zadmin/templates/zadmin/manifest.html rename to mkt/zadmin/templates/zadmin/manifest.html diff --git a/apps/zadmin/templates/zadmin/memcache.html b/mkt/zadmin/templates/zadmin/memcache.html similarity index 100% rename from apps/zadmin/templates/zadmin/memcache.html rename to mkt/zadmin/templates/zadmin/memcache.html diff --git a/apps/zadmin/templates/zadmin/oauth-consumer-create.html b/mkt/zadmin/templates/zadmin/oauth-consumer-create.html similarity index 100% rename from apps/zadmin/templates/zadmin/oauth-consumer-create.html rename to mkt/zadmin/templates/zadmin/oauth-consumer-create.html diff --git a/apps/zadmin/templates/zadmin/settings.html b/mkt/zadmin/templates/zadmin/settings.html similarity index 100% rename from apps/zadmin/templates/zadmin/settings.html rename to mkt/zadmin/templates/zadmin/settings.html diff --git a/apps/zadmin/templates/zadmin/update-prices.html b/mkt/zadmin/templates/zadmin/update-prices.html similarity index 100% rename from apps/zadmin/templates/zadmin/update-prices.html rename to mkt/zadmin/templates/zadmin/update-prices.html diff --git a/apps/zadmin/tests/test_models.py b/mkt/zadmin/tests/test_models.py similarity index 89% rename from apps/zadmin/tests/test_models.py rename to mkt/zadmin/tests/test_models.py index ddc42d4dbf5..ee8f30d9aad 100644 --- a/apps/zadmin/tests/test_models.py +++ b/mkt/zadmin/tests/test_models.py @@ -1,7 +1,7 @@ from nose.tools import eq_ from amo.tests import TestCase -from zadmin.models import DownloadSource +from mkt.zadmin.models import DownloadSource class TestDownloadSource(TestCase): diff --git a/mkt/zadmin/tests/test_views.py b/mkt/zadmin/tests/test_views.py index 835a70f41e6..5d9a460ba41 100644 --- a/mkt/zadmin/tests/test_views.py +++ b/mkt/zadmin/tests/test_views.py @@ -1,18 +1,259 @@ +# -*- coding: utf-8 -*- +import csv +from cStringIO import StringIO + from django.conf import settings -from django.core.urlresolvers import reverse +from django.core import mail +from django.core.cache import cache from nose.tools import eq_ -from pyquery import PyQuery as pq import amo import amo.tests +from addons.models import Addon +from amo.urlresolvers import reverse +from mkt.access.models import Group, GroupUser from mkt.reviewers.models import RereviewQueue from mkt.site.fixtures import fixture from users.models import UserProfile +from ..forms import DevMailerForm +from ..models import EmailPreviewTopic + + +class TestEmailPreview(amo.tests.TestCase): + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group', + 'webapp_337141') + + def setUp(self): + assert self.client.login(username='admin@mozilla.com', + password='password') + addon = Addon.objects.get(pk=337141) + self.topic = EmailPreviewTopic(addon) + + def test_csv(self): + self.topic.send_mail('the subject', u'Hello Ivan Krsti\u0107', + from_email='admin@mozilla.org', + recipient_list=['funnyguy@mozilla.org']) + r = self.client.get(reverse('zadmin.email_preview_csv', + args=[self.topic.topic])) + eq_(r.status_code, 200) + rdr = csv.reader(StringIO(r.content)) + eq_(rdr.next(), ['from_email', 'recipient_list', 'subject', 'body']) + eq_(rdr.next(), ['admin@mozilla.org', 'funnyguy@mozilla.org', + 'the subject', 'Hello Ivan Krsti\xc4\x87']) + + +class TestMemcache(amo.tests.TestCase): + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group') + + def setUp(self): + self.url = reverse('zadmin.memcache') + cache.set('foo', 'bar') + self.client.login(username='admin@mozilla.com', password='password') + + def test_login(self): + self.client.logout() + eq_(self.client.get(self.url).status_code, 302) + + def test_can_clear(self): + self.client.post(self.url, {'yes': 'True'}) + eq_(cache.get('foo'), None) + + def test_cant_clear(self): + self.client.post(self.url, {'yes': 'False'}) + eq_(cache.get('foo'), 'bar') + + +class TestElastic(amo.tests.ESTestCase): + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group') + + def setUp(self): + self.url = reverse('zadmin.elastic') + self.client.login(username='admin@mozilla.com', password='password') + + def test_login(self): + self.client.logout() + self.assertRedirects(self.client.get(self.url), + reverse('users.login') + '?to=/admin/elastic') + + +class TestEmailDevs(amo.tests.TestCase): + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group', + 'webapp_337141') + + def setUp(self): + self.login('admin') + self.addon = Addon.objects.get(pk=337141) + + def post(self, recipients=None, subject='subject', message='msg', + preview_only=False): + return self.client.post(reverse('zadmin.email_devs'), + dict(recipients=recipients, subject=subject, + message=message, + preview_only=preview_only)) + + def test_preview(self): + self.addon.update(premium_type=amo.ADDON_PREMIUM) + res = self.post(recipients='payments', preview_only=True) + self.assertNoFormErrors(res) + preview = EmailPreviewTopic(topic='email-devs') + eq_([e.recipient_list for e in preview.filter()], + ['steamcube@mozilla.com']) + eq_(len(mail.outbox), 0) + + def test_only_apps_with_payments(self): + self.addon.update(premium_type=amo.ADDON_PREMIUM) + res = self.post(recipients='payments') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(status=amo.STATUS_PENDING) + res = self.post(recipients='payments') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(status=amo.STATUS_DELETED) + res = self.post(recipients='payments') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + def test_only_free_apps_with_new_regions(self): + res = self.post(recipients='free_apps_region_enabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + mail.outbox = [] + res = self.post(recipients='free_apps_region_disabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(enable_new_regions=True) + res = self.post(recipients='free_apps_region_enabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + mail.outbox = [] + res = self.post(recipients='free_apps_region_disabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + def test_only_apps_with_payments_and_new_regions(self): + self.addon.update(premium_type=amo.ADDON_PREMIUM) + res = self.post(recipients='payments_region_enabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + mail.outbox = [] + res = self.post(recipients='payments_region_disabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(enable_new_regions=True) + res = self.post(recipients='payments_region_enabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + mail.outbox = [] + res = self.post(recipients='payments_region_disabled') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + def test_only_desktop_apps(self): + from addons.models import AddonDeviceType + AddonDeviceType.objects.create(addon=self.addon, + device_type=amo.DEVICE_MOBILE.id) + res = self.post(recipients='desktop_apps') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + mail.outbox = [] + AddonDeviceType.objects.create(addon=self.addon, + device_type=amo.DEVICE_DESKTOP.id) + res = self.post(recipients='desktop_apps') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(status=amo.STATUS_PENDING) + res = self.post(recipients='desktop_apps') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + mail.outbox = [] + self.addon.update(status=amo.STATUS_DELETED) + res = self.post(recipients='desktop_apps') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + def test_only_apps(self): + res = self.post(recipients='apps') + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 1) + + def test_ignore_deleted_always(self): + self.addon.update(status=amo.STATUS_DELETED) + for name, label in DevMailerForm._choices: + res = self.post(recipients=name) + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + def test_exclude_pending_for_addons(self): + self.addon.update(status=amo.STATUS_PENDING) + for name, label in DevMailerForm._choices: + if name in ('payments', 'desktop_apps'): + continue + res = self.post(recipients=name) + self.assertNoFormErrors(res) + eq_(len(mail.outbox), 0) + + +class TestPerms(amo.tests.TestCase): + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group', + 'user_999') + + def test_admin_user(self): + # Admin should see views with Django's perm decorator and our own. + assert self.client.login(username='admin@mozilla.com', + password='password') + eq_(self.client.get(reverse('zadmin.index')).status_code, 200) + eq_(self.client.get(reverse('zadmin.settings')).status_code, 200) + + def test_staff_user(self): + # Staff users have some privileges. + user = UserProfile.objects.get(email='regular@mozilla.com') + group = Group.objects.create(name='Staff', rules='AdminTools:View') + GroupUser.objects.create(group=group, user=user) + assert self.client.login(username='regular@mozilla.com', + password='password') + eq_(self.client.get(reverse('zadmin.index')).status_code, 200) + eq_(self.client.get(reverse('zadmin.settings')).status_code, 200) + + def test_sr_reviewers_user(self): + # Sr Reviewers users have only a few privileges. + user = UserProfile.objects.get(email='regular@mozilla.com') + group = Group.objects.create(name='Sr Reviewer', + rules='ReviewerAdminTools:View') + GroupUser.objects.create(group=group, user=user) + assert self.client.login(username='regular@mozilla.com', + password='password') + eq_(self.client.get(reverse('zadmin.index')).status_code, 200) + eq_(self.client.get(reverse('zadmin.settings')).status_code, 403) + + def test_unprivileged_user(self): + # Unprivileged user. + assert self.client.login(username='regular@mozilla.com', + password='password') + eq_(self.client.get(reverse('zadmin.index')).status_code, 403) + eq_(self.client.get(reverse('zadmin.settings')).status_code, 403) + # Anonymous users should also get a 403. + self.client.logout() + self.assertRedirects(self.client.get(reverse('zadmin.index')), + reverse('users.login') + '?to=/admin/') + class TestHome(amo.tests.TestCase): - fixtures = ['base/users'] + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group') def setUp(self): self.client.login(username='admin@mozilla.com', password='password') @@ -25,7 +266,7 @@ def test_home(self): class TestGenerateError(amo.tests.TestCase): - fixtures = ['base/users'] + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group') def setUp(self): self.client.login(username='admin@mozilla.com', password='password') @@ -101,24 +342,9 @@ def test_heka_sentry(self): eq_(msg.type, 'sentry') -class TestAddonAdmin(amo.tests.TestCase): - fixtures = ['base/users', 'base/337141-steamcube', 'base/addon_3615'] - - def setUp(self): - self.login('admin@mozilla.com') - self.url = reverse('admin:addons_addon_changelist') - - def test_no_webapps(self): - res = self.client.get(self.url, follow=True) - eq_(res.status_code, 200) - doc = pq(res.content) - rows = doc('#result_list tbody tr') - eq_(rows.length, 1) - eq_(rows.find('a').attr('href'), '/admin/models/addons/addon/337141/') - - class TestManifestRevalidation(amo.tests.TestCase): - fixtures = fixture('webapp_337141') + ['base/users'] + fixtures = fixture('user_admin', 'group_admin', 'user_admin_group', + 'webapp_337141', 'user_999') def setUp(self): self.url = reverse('zadmin.manifest_revalidation') diff --git a/mkt/zadmin/urls.py b/mkt/zadmin/urls.py index ac83175ae9b..994b86983a6 100644 --- a/mkt/zadmin/urls.py +++ b/mkt/zadmin/urls.py @@ -1,9 +1,51 @@ -from django.conf.urls import patterns, url +from django.conf.urls import include, patterns, url +from django.contrib import admin +from django.core.exceptions import PermissionDenied +from django.shortcuts import redirect +from amo.urlresolvers import reverse from . import views -urlpatterns = patterns( - '', + +# Hijack the admin's login to use our pages. +def login(request): + # If someone is already auth'd then they're getting directed to login() + # because they don't have sufficient permissions. + if request.user.is_authenticated(): + raise PermissionDenied + else: + return redirect('%s?to=%s' % (reverse('users.login'), request.path)) + + +admin.site.login = login + + +urlpatterns = patterns('', + # AMO stuff. + url('^$', views.index, name='zadmin.index'), + url('^models$', lambda r: redirect('admin:index'), name='zadmin.home'), + url('^env$', views.env, name='amo.env'), + url('^memcache$', views.memcache, name='zadmin.memcache'), + url('^settings', views.show_settings, name='zadmin.settings'), + url('^fix-disabled', views.fix_disabled_file, name='zadmin.fix-disabled'), + url(r'^email_preview/(?P.*)\.csv$', + views.email_preview_csv, name='zadmin.email_preview_csv'), + + url('^mail$', views.mail, name='zadmin.mail'), + url('^email-devs$', views.email_devs, name='zadmin.email_devs'), + url('^generate-error$', views.generate_error, + name='zadmin.generate-error'), + + url('^export_email_addresses$', views.export_email_addresses, + name='zadmin.export_email_addresses'), + url('^email_addresses_file$', views.email_addresses_file, + name='zadmin.email_addresses_file'), + + url('^price-tiers$', views.price_tiers, name='zadmin.price_tiers'), + + # The Django admin. + url('^models/', include(admin.site.urls)), + url('^elastic$', views.elastic, name='zadmin.elastic'), url('^manifest-revalidation$', views.manifest_revalidation, name='zadmin.manifest_revalidation'), diff --git a/mkt/zadmin/views.py b/mkt/zadmin/views.py index 0eef93f57aa..9c27fe58450 100644 --- a/mkt/zadmin/views.py +++ b/mkt/zadmin/views.py @@ -1,16 +1,237 @@ +import csv + +from django import http from django.conf import settings +from django.contrib import admin +from django.core.cache import cache +from django.core.files.storage import default_storage as storage from django.db.models import Q -from django.shortcuts import render +from django.shortcuts import get_object_or_404, redirect, render +from django.views import debug +import commonware.log import elasticutils.contrib.django as elasticutils +import jinja2 import amo +from addons.models import AddonUser +from amo import messages +from amo.decorators import any_permission_required, json_view, post_required +from amo.mail import FakeEmailBackend +from amo.urlresolvers import reverse from amo.utils import chunked -from zadmin.decorators import admin_required +from files.models import File +from mkt.developers.models import ActivityLog +from mkt.prices.utils import update_from_csv +from users.models import UserProfile from mkt.webapps.models import Webapp from mkt.webapps.tasks import update_manifests +from . import tasks +from .decorators import admin_required +from .forms import DevMailerForm, GenerateErrorForm, PriceTiersForm, YesImSure +from .models import EmailPreviewTopic + + +log = commonware.log.getLogger('z.zadmin') + + +@admin_required +def show_settings(request): + settings_dict = debug.get_safe_settings() + + for i in ['GOOGLE_ANALYTICS_CREDENTIALS']: + settings_dict[i] = debug.cleanse_setting(i, + getattr(settings, i, {})) + + settings_dict['WEBAPPS_RECEIPT_KEY'] = '********************' + + return render(request, 'zadmin/settings.html', + {'settings_dict': settings_dict}) + + +@admin_required +def env(request): + return http.HttpResponse(u'
%s
' % (jinja2.escape(request))) + + +@admin.site.admin_view +def fix_disabled_file(request): + file_ = None + if request.method == 'POST' and 'file' in request.POST: + file_ = get_object_or_404(File, id=request.POST['file']) + if 'confirm' in request.POST: + file_.unhide_disabled_file() + messages.success(request, 'We have done a great thing.') + return redirect('zadmin.fix-disabled') + return render(request, 'zadmin/fix-disabled.html', + {'file': file_, 'file_id': request.POST.get('file', '')}) + + +@admin_required +def email_preview_csv(request, topic): + resp = http.HttpResponse() + resp['Content-Type'] = 'text/csv; charset=utf-8' + resp['Content-Disposition'] = "attachment; filename=%s.csv" % (topic) + writer = csv.writer(resp) + fields = ['from_email', 'recipient_list', 'subject', 'body'] + writer.writerow(fields) + rs = EmailPreviewTopic(topic=topic).filter().values_list(*fields) + for row in rs: + writer.writerow([r.encode('utf8') for r in row]) + return resp + + +@admin.site.admin_view +def mail(request): + backend = FakeEmailBackend() + if request.method == 'POST': + backend.clear() + return redirect('zadmin.mail') + return render(request, 'zadmin/mail.html', dict(mail=backend.view_all())) + + +@admin.site.admin_view +def email_devs(request): + form = DevMailerForm(request.POST or None) + preview = EmailPreviewTopic(topic='email-devs') + if preview.filter().count(): + preview_csv = reverse('zadmin.email_preview_csv', + args=[preview.topic]) + else: + preview_csv = None + if request.method == 'POST' and form.is_valid(): + data = form.cleaned_data + qs = (AddonUser.objects.filter(role__in=(amo.AUTHOR_ROLE_DEV, + amo.AUTHOR_ROLE_OWNER)) + .exclude(user__email=None)) + + if data['recipients'] in ('payments', 'desktop_apps'): + qs = qs.exclude(addon__status=amo.STATUS_DELETED) + else: + qs = qs.filter(addon__status__in=amo.LISTED_STATUSES) + + if data['recipients'] in ('payments', 'payments_region_enabled', + 'payments_region_disabled'): + qs = qs.filter(addon__type=amo.ADDON_WEBAPP) + qs = qs.exclude(addon__premium_type__in=(amo.ADDON_FREE, + amo.ADDON_OTHER_INAPP)) + if data['recipients'] == 'payments_region_enabled': + qs = qs.filter(addon__enable_new_regions=True) + elif data['recipients'] == 'payments_region_disabled': + qs = qs.filter(addon__enable_new_regions=False) + elif data['recipients'] in ('apps', 'free_apps_region_enabled', + 'free_apps_region_disabled'): + qs = qs.filter(addon__type=amo.ADDON_WEBAPP) + if data['recipients'] == 'free_apps_region_enabled': + qs = qs.filter(addon__enable_new_regions=True) + elif data['recipients'] == 'free_apps_region_disabled': + qs = qs.filter(addon__enable_new_regions=False) + elif data['recipients'] == 'desktop_apps': + qs = (qs.filter(addon__type=amo.ADDON_WEBAPP, + addon__addondevicetype__device_type=amo.DEVICE_DESKTOP.id)) + else: + raise NotImplementedError('If you want to support emailing other ' + 'types of developers, do it here!') + if data['preview_only']: + # Clear out the last batch of previewed emails. + preview.filter().delete() + total = 0 + for emails in chunked(set(qs.values_list('user__email', flat=True)), + 100): + total += len(emails) + tasks.admin_email.delay(emails, data['subject'], data['message'], + preview_only=data['preview_only'], + preview_topic=preview.topic) + msg = 'Emails queued for delivery: %s' % total + if data['preview_only']: + msg = '%s (for preview only, emails not sent!)' % msg + messages.success(request, msg) + return redirect('zadmin.email_devs') + return render(request, 'zadmin/email-devs.html', + dict(form=form, preview_csv=preview_csv)) + + +@any_permission_required([('Admin', '%'), + ('AdminTools', 'View'), + ('ReviewerAdminTools', 'View')]) +def index(request): + log = ActivityLog.objects.admin_events()[:5] + return render(request, 'zadmin/index.html', {'log': log}) + + +@admin.site.admin_view +@post_required +@json_view +def recalc_hash(request, file_id): + + file = get_object_or_404(File, pk=file_id) + file.size = storage.size(file.file_path) + file.hash = file.generate_hash() + file.save() + + log.info('Recalculated hash for file ID %d' % file.id) + messages.success(request, + 'File hash and size recalculated for file %d.' % file.id) + return {'success': 1} + + +@admin.site.admin_view +def memcache(request): + form = YesImSure(request.POST or None) + if form.is_valid() and form.cleaned_data['yes']: + cache.clear() + form = YesImSure() + messages.success(request, 'Cache cleared') + if cache._cache and hasattr(cache._cache, 'get_stats'): + stats = cache._cache.get_stats() + else: + stats = [] + return render(request, 'zadmin/memcache.html', + {'form': form, 'stats': stats}) + + +@admin_required +def generate_error(request): + form = GenerateErrorForm(request.POST or None) + if request.method == 'POST' and form.is_valid(): + form.explode() + return render(request, 'zadmin/generate-error.html', {'form': form}) + + +@any_permission_required([('Admin', '%'), + ('MailingLists', 'View')]) +def export_email_addresses(request): + return render(request, 'zadmin/export_button.html', {}) + + +@any_permission_required([('Admin', '%'), + ('MailingLists', 'View')]) +def email_addresses_file(request): + resp = http.HttpResponse() + resp['Content-Type'] = 'text/plain; charset=utf-8' + resp['Content-Disposition'] = ('attachment; ' + 'filename=amo_optin_emails.txt') + emails = (UserProfile.objects.filter(notifications__notification_id=13, + notifications__enabled=1) + .values_list('email', flat=True)) + for e in emails: + if e is not None: + resp.write(e + '\n') + return resp + + +@admin_required +def price_tiers(request): + output = [] + form = PriceTiersForm(request.POST or None, request.FILES) + if request.method == 'POST' and form.is_valid(): + output = update_from_csv(form.cleaned_data['prices']) + + return render(request, 'zadmin/update-prices.html', + {'result': output, 'form': form}) + @admin_required(reviewers=True) def manifest_revalidation(request):