Bug 661651 - Adding admin tool for the selection of addons of the month by locale.
This commit is contained in:
Родитель
0b0d751a60
Коммит
96073b59cf
|
@ -17,6 +17,7 @@ import amo.models
|
||||||
import sharing.utils as sharing
|
import sharing.utils as sharing
|
||||||
from amo.utils import sorted_groupby
|
from amo.utils import sorted_groupby
|
||||||
from amo.urlresolvers import reverse
|
from amo.urlresolvers import reverse
|
||||||
|
from product_details import product_details
|
||||||
from addons.models import Addon, AddonRecommendation
|
from addons.models import Addon, AddonRecommendation
|
||||||
from applications.models import Application
|
from applications.models import Application
|
||||||
from stats.models import CollectionShareCountTotal
|
from stats.models import CollectionShareCountTotal
|
||||||
|
@ -558,3 +559,21 @@ class FeaturedCollection(amo.models.ModelBase):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s (%s: %s)' % (self.collection, self.application,
|
return u'%s (%s: %s)' % (self.collection, self.application,
|
||||||
self.locale)
|
self.locale)
|
||||||
|
|
||||||
|
|
||||||
|
def locale_generator():
|
||||||
|
return (('', u'(Default Locale)'),) + tuple(
|
||||||
|
(i, product_details.languages[i]['native'])
|
||||||
|
for i in settings.AMO_LANGUAGES)
|
||||||
|
|
||||||
|
|
||||||
|
class MonthlyPick(amo.models.ModelBase):
|
||||||
|
LOCALES = locale_generator()
|
||||||
|
|
||||||
|
addon = models.ForeignKey(Addon)
|
||||||
|
blurb = models.TextField()
|
||||||
|
image = models.URLField()
|
||||||
|
locale = models.CharField(max_length=30, choices=LOCALES, unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "monthly_pick"
|
||||||
|
|
|
@ -15,7 +15,7 @@ import amo
|
||||||
from product_details import product_details
|
from product_details import product_details
|
||||||
from amo.urlresolvers import reverse
|
from amo.urlresolvers import reverse
|
||||||
from applications.models import Application, AppVersion
|
from applications.models import Application, AppVersion
|
||||||
from bandwagon.models import Collection, FeaturedCollection
|
from bandwagon.models import Collection, FeaturedCollection, MonthlyPick
|
||||||
from zadmin.models import ValidationJob
|
from zadmin.models import ValidationJob
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,3 +165,16 @@ class OAuthConsumerForm(happyforms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Consumer
|
model = Consumer
|
||||||
fields = ['name', 'description', 'status']
|
fields = ['name', 'description', 'status']
|
||||||
|
|
||||||
|
|
||||||
|
class MonthlyPickForm(happyforms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = MonthlyPick
|
||||||
|
widgets = {
|
||||||
|
'addon': forms.TextInput(),
|
||||||
|
'blurb': forms.Textarea(attrs={'cols': 20, 'rows': 2})
|
||||||
|
}
|
||||||
|
|
||||||
|
MonthlyPickFormSet = modelformset_factory(MonthlyPick,
|
||||||
|
form=MonthlyPickForm,
|
||||||
|
can_delete=True, extra=0)
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% set title = 'Monthly Pick Manager' %}
|
||||||
|
{% block title %}{{ page_title(title) }}{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyattrs %}
|
||||||
|
data-collections-url="{{ url('zadmin.collections_json') }}"
|
||||||
|
data-featured-collection-url="{{ url('zadmin.featured_collection') }}"
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<link rel="stylesheet" href="{{ media('css/zamboni/admin_features.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{ media('js/zamboni/admin_features.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro fc(form) %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ form.id }}
|
||||||
|
{{ form.addon }}
|
||||||
|
{{ form.addon.errors }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ form.locale }}
|
||||||
|
{{ form.locale.errors }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ form.blurb }}
|
||||||
|
{{ form.blurb.errors }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ form.image }}
|
||||||
|
{{ form.image.errors }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="js-hidden delete">{{ form.DELETE }}{{ form.DELETE.label_tag() }}</span>
|
||||||
|
<a href="#" class="remove">×</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
<form action="" method="post">
|
||||||
|
{{ csrf() }}
|
||||||
|
{% include "messages.html" %}
|
||||||
|
{{ form.non_form_errors() }}
|
||||||
|
{{ form.management_form }}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th>Addon ID</th>
|
||||||
|
<th>Locale</th>
|
||||||
|
<th>Blurb</th>
|
||||||
|
<th>Image URL</th>
|
||||||
|
<th class="js-hidden">Delete</th>
|
||||||
|
</thead>
|
||||||
|
<tbody id="features">
|
||||||
|
{% for form in form.forms %}
|
||||||
|
{{ fc(form) }}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="hidden">
|
||||||
|
{{ fc(form.empty_form) }}
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
<p><a href="#" id="add">Add a Featured Addon</a></p>
|
||||||
|
<p>
|
||||||
|
<button type="submit">Save Changes</button> or <a href="">Cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -21,7 +21,7 @@ from amo.tests import (formset, initial, close_to_now,
|
||||||
from amo.urlresolvers import reverse
|
from amo.urlresolvers import reverse
|
||||||
from addons.models import Addon
|
from addons.models import Addon
|
||||||
from applications.models import AppVersion
|
from applications.models import AppVersion
|
||||||
from bandwagon.models import FeaturedCollection
|
from bandwagon.models import Collection, FeaturedCollection, MonthlyPick
|
||||||
from devhub.models import ActivityLog
|
from devhub.models import ActivityLog
|
||||||
from files.models import Approval, File
|
from files.models import Approval, File
|
||||||
from users.models import UserProfile
|
from users.models import UserProfile
|
||||||
|
@ -1028,6 +1028,55 @@ class TestEmailPreview(amo.tests.TestCase):
|
||||||
'the subject', 'Hello Ivan Krsti\xc4\x87'])
|
'the subject', 'Hello Ivan Krsti\xc4\x87'])
|
||||||
|
|
||||||
|
|
||||||
|
class TestMonthlyPick(amo.tests.TestCase):
|
||||||
|
fixtures = ['base/addon_3615', 'base/apps', 'base/users']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
assert self.client.login(username='admin@mozilla.com',
|
||||||
|
password='password')
|
||||||
|
self.url = reverse('zadmin.monthly_pick')
|
||||||
|
addon = Addon.objects.get(pk=3615)
|
||||||
|
MonthlyPick.objects.create(addon=addon,
|
||||||
|
locale='zh-CN',
|
||||||
|
blurb="test data",
|
||||||
|
image="http://www.google.com")
|
||||||
|
self.f = self.client.get(self.url).context['form'].initial_forms[0]
|
||||||
|
self.initial = self.f.initial
|
||||||
|
|
||||||
|
def test_form_initial(self):
|
||||||
|
eq_(self.initial['addon'], 3615)
|
||||||
|
eq_(self.initial['locale'], 'zh-CN')
|
||||||
|
eq_(self.initial['blurb'], 'test data')
|
||||||
|
eq_(self.initial['image'], 'http://www.google.com')
|
||||||
|
|
||||||
|
def test_success_insert(self):
|
||||||
|
dupe = initial(self.f)
|
||||||
|
del dupe['id']
|
||||||
|
dupe.update(locale='fr')
|
||||||
|
data = formset(initial(self.f), dupe, initial_count=1)
|
||||||
|
r = self.client.post(self.url, data)
|
||||||
|
eq_(MonthlyPick.objects.count(), 2)
|
||||||
|
eq_(MonthlyPick.objects.all()[1].locale, 'fr')
|
||||||
|
|
||||||
|
def test_success_update(self):
|
||||||
|
d = initial(self.f)
|
||||||
|
d.update(locale='fr')
|
||||||
|
r = self.client.post(self.url, formset(d, initial_count=1))
|
||||||
|
eq_(r.status_code, 302)
|
||||||
|
eq_(MonthlyPick.objects.all()[0].locale, 'fr')
|
||||||
|
|
||||||
|
def test_success_delete(self):
|
||||||
|
d = initial(self.f)
|
||||||
|
d.update(DELETE=True)
|
||||||
|
r = self.client.post(self.url, formset(d, initial_count=1))
|
||||||
|
eq_(MonthlyPick.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_require_login(self):
|
||||||
|
self.client.logout()
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
eq_(r.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
class TestFeatures(amo.tests.TestCase):
|
class TestFeatures(amo.tests.TestCase):
|
||||||
fixtures = ['base/apps', 'base/users', 'base/collections']
|
fixtures = ['base/apps', 'base/users', 'base/collections']
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,10 @@ urlpatterns = patterns('',
|
||||||
name='zadmin.collections_json'),
|
name='zadmin.collections_json'),
|
||||||
url('^features/featured-collection$', views.featured_collection,
|
url('^features/featured-collection$', views.featured_collection,
|
||||||
name='zadmin.featured_collection'),
|
name='zadmin.featured_collection'),
|
||||||
|
|
||||||
|
url('^monthly-pick$', views.monthly_pick,
|
||||||
|
name='zadmin.monthly_pick'),
|
||||||
|
|
||||||
url('^elastic$', views.elastic, name='zadmin.elastic'),
|
url('^elastic$', views.elastic, name='zadmin.elastic'),
|
||||||
url('^mail$', views.mail, name='zadmin.mail'),
|
url('^mail$', views.mail, name='zadmin.mail'),
|
||||||
url('^celery$', views.celery, name='zadmin.celery'),
|
url('^celery$', views.celery, name='zadmin.celery'),
|
||||||
|
|
|
@ -43,7 +43,7 @@ from versions.models import Version
|
||||||
|
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from .forms import (BulkValidationForm, FeaturedCollectionFormSet, NotifyForm,
|
from .forms import (BulkValidationForm, FeaturedCollectionFormSet, NotifyForm,
|
||||||
OAuthConsumerForm)
|
OAuthConsumerForm, MonthlyPickFormSet)
|
||||||
from .models import ValidationJob, EmailPreviewTopic, ValidationJobTally
|
from .models import ValidationJob, EmailPreviewTopic, ValidationJobTally
|
||||||
|
|
||||||
log = commonware.log.getLogger('z.zadmin')
|
log = commonware.log.getLogger('z.zadmin')
|
||||||
|
@ -394,6 +394,16 @@ def features(request):
|
||||||
return jingo.render(request, 'zadmin/features.html', dict(form=form))
|
return jingo.render(request, 'zadmin/features.html', dict(form=form))
|
||||||
|
|
||||||
|
|
||||||
|
@admin.site.admin_view
|
||||||
|
def monthly_pick(request):
|
||||||
|
form = MonthlyPickFormSet(request.POST or None)
|
||||||
|
if request.method == 'POST' and form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, 'Changes successfully saved.')
|
||||||
|
return redirect('zadmin.monthly_pick')
|
||||||
|
return jingo.render(request, 'zadmin/monthly_pick.html', dict(form=form))
|
||||||
|
|
||||||
|
|
||||||
@admin.site.admin_view
|
@admin.site.admin_view
|
||||||
def elastic(request):
|
def elastic(request):
|
||||||
INDEX = site_settings.ES_INDEX
|
INDEX = site_settings.ES_INDEX
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE `monthly_pick` (
|
||||||
|
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||||
|
`created` datetime NOT NULL,
|
||||||
|
`modified` datetime NOT NULL,
|
||||||
|
`addon_id` int(11) unsigned NOT NULL,
|
||||||
|
`blurb` longtext NOT NULL,
|
||||||
|
`image` varchar(200) NOT NULL,
|
||||||
|
`locale` varchar(30) NOT NULL UNIQUE
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||||
|
|
||||||
|
ALTER TABLE `monthly_pick` ADD CONSTRAINT `addon_id_refs_id_a94677f3` FOREIGN KEY (`addon_id`) REFERENCES `addons` (`id`);
|
||||||
|
CREATE INDEX `monthly_pick_cc3d5937` ON `monthly_pick` (`addon_id`);
|
Загрузка…
Ссылка в новой задаче