Adds form and backend for searching the editor queue (bug 622171)
This commit is contained in:
Родитель
42b2d546ae
Коммит
569332223e
|
@ -1,11 +1,16 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django import forms
|
||||
|
||||
from django.db.models import Q
|
||||
from django.forms.widgets import Select
|
||||
from django.utils.translation import get_language
|
||||
import happyforms
|
||||
from tower import ugettext_lazy as _lazy
|
||||
|
||||
import amo
|
||||
from amo.urlresolvers import reverse
|
||||
from applications.models import AppVersion
|
||||
|
||||
|
||||
ACTION_FILTERS = (('', ''), ('approved', _lazy('Approved reviews')),
|
||||
('deleted', _lazy('Deleted reviews')))
|
||||
|
@ -46,3 +51,118 @@ class ReviewLogForm(happyforms.Form):
|
|||
data['end'] += timedelta(days=1)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class QueueSearchForm(happyforms.Form):
|
||||
text_query = forms.CharField(
|
||||
required=False,
|
||||
label=_lazy(u'Search by add-on name / author email'))
|
||||
admin_review = forms.ChoiceField(required=False,
|
||||
choices=[('', ''),
|
||||
('1', _lazy(u'yes')),
|
||||
('0', _lazy(u'no'))],
|
||||
label=_lazy(u'Admin Flag'))
|
||||
application_id = forms.ChoiceField(
|
||||
required=False,
|
||||
label=_lazy(u'Application'),
|
||||
choices=([('', '')] +
|
||||
[(a.id, a.pretty) for a in amo.APPS_ALL.values()]))
|
||||
max_version = forms.ChoiceField(
|
||||
required=False,
|
||||
label=_lazy(u'Max. Version'),
|
||||
choices=[('', _lazy(u'Select an application first'))])
|
||||
waiting_time_days = forms.ChoiceField(
|
||||
required=False,
|
||||
label=_lazy(u'Days Since Submission'),
|
||||
choices=([('', '')] +
|
||||
[(i, i) for i in range(1,10)] + [('10+', '10+')]))
|
||||
addon_type_ids = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
label=_lazy(u'Add-on Types'),
|
||||
choices=((id, tp) for id, tp in amo.ADDON_TYPES.items()))
|
||||
platform_ids = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
label=_lazy(u'Platforms'),
|
||||
choices=[(p.id, p.name)
|
||||
for p in amo.PLATFORMS.values()
|
||||
if p not in (amo.PLATFORM_ANY, amo.PLATFORM_ALL)])
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(QueueSearchForm, self).__init__(*args, **kw)
|
||||
w = self.fields['application_id'].widget
|
||||
# Get the URL after the urlconf has loaded.
|
||||
w.attrs['data-url'] = reverse('editors.application_versions_json')
|
||||
|
||||
def version_choices_for_app_id(self, app_id):
|
||||
versions = AppVersion.objects.filter(application__id=app_id)
|
||||
return [('', '')] + [(v.version, v.version) for v in versions]
|
||||
|
||||
def clean_application_id(self):
|
||||
if self.cleaned_data['application_id']:
|
||||
choices = self.version_choices_for_app_id(
|
||||
self.cleaned_data['application_id'])
|
||||
self.fields['max_version'].choices = choices
|
||||
return self.cleaned_data['application_id']
|
||||
|
||||
def clean_max_version(self):
|
||||
if self.cleaned_data['max_version']:
|
||||
if not self.cleaned_data['application_id']:
|
||||
raise forms.ValidationError("No application selected")
|
||||
return self.cleaned_data['max_version']
|
||||
|
||||
def filter_qs(self, qs):
|
||||
data = self.cleaned_data
|
||||
if data['admin_review']:
|
||||
qs = qs.filter(admin_review=data['admin_review'])
|
||||
if data['addon_type_ids']:
|
||||
qs = qs.filter_raw('addon_type_id IN', data['addon_type_ids'])
|
||||
if data['application_id']:
|
||||
qs = qs.filter_raw('apps.application_id =', data['application_id'])
|
||||
if data['max_version']:
|
||||
joins = [
|
||||
'JOIN versions_summary vs ON (versions.id = vs.version_id)',
|
||||
'JOIN appversions max_version on (max_version.id = vs.max)']
|
||||
qs.base_query['from'].extend(joins)
|
||||
qs = qs.filter_raw('max_version.version =', data['max_version'])
|
||||
if data['platform_ids']:
|
||||
qs = qs.filter_raw('files.platform_id IN', data['platform_ids'])
|
||||
if data['text_query']:
|
||||
lang = get_language()
|
||||
joins = [
|
||||
'LEFT JOIN addons_users au on (au.addon_id = addons.id)',
|
||||
'LEFT JOIN users u on (u.id = au.user_id)',
|
||||
"""LEFT JOIN translations AS supportemail_default ON
|
||||
(supportemail_default.id = addons.supportemail AND
|
||||
supportemail_default.locale=addons.defaultlocale)""",
|
||||
"""LEFT JOIN translations AS supportemail_local ON
|
||||
(supportemail_local.id = addons.supportemail AND
|
||||
supportemail_local.locale=%%(%s)s)"""
|
||||
% qs._param(lang),
|
||||
"""LEFT JOIN translations AS ad_name_local ON
|
||||
(ad_name_local.id = addons.name AND
|
||||
ad_name_local.locale=%%(%s)s)"""
|
||||
% qs._param(lang)]
|
||||
qs.base_query['from'].extend(joins)
|
||||
fuzzy_q = u'%' + data['text_query'] + u'%'
|
||||
qs = qs.filter_raw(
|
||||
Q('addon_name LIKE', fuzzy_q) |
|
||||
# Search translated add-on names / support emails in
|
||||
# the editor's locale:
|
||||
Q('ad_name_local.localized_string LIKE', fuzzy_q) |
|
||||
Q('supportemail_default.localized_string LIKE', fuzzy_q) |
|
||||
Q('supportemail_local.localized_string LIKE', fuzzy_q) |
|
||||
Q('au.role IN', [amo.AUTHOR_ROLE_OWNER,
|
||||
amo.AUTHOR_ROLE_DEV],
|
||||
'u.email LIKE', fuzzy_q))
|
||||
if data['waiting_time_days']:
|
||||
if data['waiting_time_days'] == '10+':
|
||||
# Special case
|
||||
args = ('waiting_time_days >=',
|
||||
int(data['waiting_time_days'][:-1]))
|
||||
else:
|
||||
args = ('waiting_time_days =', data['waiting_time_days'])
|
||||
if qs.sql_model.is_version_specific:
|
||||
qs = qs.having(*args)
|
||||
else:
|
||||
qs = qs.filter_raw(*args)
|
||||
return qs
|
||||
|
|
|
@ -30,9 +30,11 @@ class EditorQueueTable(SQLTable):
|
|||
addon_name = tables.Column(verbose_name=_(u'Addon'))
|
||||
addon_type_id = tables.Column(verbose_name=_(u'Type'))
|
||||
waiting_time_days = tables.Column(verbose_name=_(u'Waiting Time'))
|
||||
flags = tables.Column(verbose_name=_(u'Flags'))
|
||||
applications = tables.Column(verbose_name=_(u'Applications'))
|
||||
additional_info = tables.Column(verbose_name=_(u'Additional Information'))
|
||||
flags = tables.Column(verbose_name=_(u'Flags'), sortable=False)
|
||||
applications = tables.Column(verbose_name=_(u'Applications'),
|
||||
sortable=False)
|
||||
additional_info = tables.Column(verbose_name=_(u'Additional Information'),
|
||||
sortable=False)
|
||||
|
||||
def render_addon_name(self, row):
|
||||
url = '%s?num=%s' % (reverse('editors.review',
|
||||
|
|
|
@ -65,6 +65,7 @@ class ViewQueue(RawSQLModel):
|
|||
_application_ids = models.CharField(max_length=255)
|
||||
waiting_time_days = models.IntegerField()
|
||||
waiting_time_hours = models.IntegerField()
|
||||
is_version_specific = False
|
||||
|
||||
def base_query(self):
|
||||
return {
|
||||
|
@ -136,6 +137,7 @@ class ViewFullReviewQueue(ViewQueue):
|
|||
|
||||
|
||||
class VersionSpecificQueue(ViewQueue):
|
||||
is_version_specific = True
|
||||
|
||||
def base_query(self):
|
||||
q = copy.deepcopy(super(VersionSpecificQueue, self).base_query())
|
||||
|
|
|
@ -25,6 +25,46 @@
|
|||
|
||||
<div class="queue-outer">
|
||||
<div class="queue-inner">
|
||||
{% if search_form %}
|
||||
<div id="queue-search">
|
||||
<form action="" method="get">
|
||||
<label for="id_text_query">{{ search_form['text_query'].label }}</label>
|
||||
{{ search_form['text_query'] }}
|
||||
<button type="submit">{{ _('Search') }}</button>
|
||||
<a id="toggle-queue-search" href="#">{{ _('Advanced Search') }}</a>
|
||||
{% if search_form.data %}
|
||||
(<a href="{{ url('editors.queue_%s' % tab) }}">{{ _('clear search') }}</a>)
|
||||
{% endif %}
|
||||
<div id="advanced-search">
|
||||
<div class="column1">
|
||||
<div class="form-row">
|
||||
{% for elem in ('admin_review', 'application_id',
|
||||
'max_version', 'waiting_time_days') %}
|
||||
<label for="id_{{ elem }}">{{ search_form[elem].label }}</label>
|
||||
<div class="form-elem">{{ search_form[elem] }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column2">
|
||||
<div class="form-row">
|
||||
{% for elem in ('addon_type_ids',) %}
|
||||
<label for="id_{{ elem }}">{{ search_form[elem].label }}</label>
|
||||
<div class="form-elem">{{ search_form[elem] }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column3">
|
||||
<div class="form-row">
|
||||
{% for elem in ('platform_ids',) %}
|
||||
<label for="id_{{ elem }}">{{ search_form[elem].label }}</label>
|
||||
<div class="form-elem">{{ search_form[elem] }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page|paginator %}
|
||||
<div class="data-grid-content data-grid-top">
|
||||
{{ page|paginator }}
|
||||
|
@ -90,9 +130,13 @@
|
|||
{% set cls, sprite = '', 'both' %}
|
||||
{% endif %}
|
||||
<th class="{{ cls }}">
|
||||
<a href="?sort={{ column.name_toggled }}" class="sort-icon ed-sprite-sort-{{ sprite }}">
|
||||
{% if column.sortable %}
|
||||
<a href="?sort={{ column.name_toggled }}" class="sort-icon ed-sprite-sort-{{ sprite }}">
|
||||
{{ column }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ column }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
|
|
@ -6,7 +6,7 @@ import test_utils
|
|||
|
||||
import amo
|
||||
from addons.models import Addon
|
||||
from versions.models import Version, ApplicationsVersions
|
||||
from versions.models import Version, ApplicationsVersions, VersionSummary
|
||||
from files.models import Platform, File
|
||||
from applications.models import Application, AppVersion
|
||||
from editors.models import (ViewPendingQueue, ViewFullReviewQueue,
|
||||
|
@ -14,7 +14,8 @@ from editors.models import (ViewPendingQueue, ViewFullReviewQueue,
|
|||
|
||||
|
||||
def create_addon_file(name, version_str, addon_status, file_status,
|
||||
platform=amo.PLATFORM_ALL, application=amo.FIREFOX):
|
||||
platform=amo.PLATFORM_ALL, application=amo.FIREFOX,
|
||||
admin_review=False, addon_type=amo.ADDON_EXTENSION):
|
||||
app, created = Application.objects.get_or_create(id=application.id,
|
||||
guid=application.guid)
|
||||
app_vr, created = AppVersion.objects.get_or_create(application=app,
|
||||
|
@ -23,8 +24,15 @@ def create_addon_file(name, version_str, addon_status, file_status,
|
|||
try:
|
||||
ad = Addon.objects.get(name__localized_string=name)
|
||||
except Addon.DoesNotExist:
|
||||
ad = Addon.objects.create(type=amo.ADDON_EXTENSION, name=name)
|
||||
ad = Addon.objects.create(type=addon_type, name=name)
|
||||
if admin_review:
|
||||
ad.update(admin_review=True)
|
||||
vr, created = Version.objects.get_or_create(addon=ad, version=version_str)
|
||||
vs, created = VersionSummary.objects.get_or_create(version=vr,
|
||||
addon=ad,
|
||||
application=app,
|
||||
max=app_vr.id,
|
||||
min=app_vr.id)
|
||||
va, created = ApplicationsVersions.objects.get_or_create(
|
||||
version=vr, application=app, min=app_vr, max=app_vr)
|
||||
fi = File.objects.create(version=vr, filename=u"%s.xpi" % name,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf8 -*-
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django import forms
|
||||
|
||||
|
@ -12,13 +13,14 @@ import test_utils
|
|||
import amo
|
||||
from amo.urlresolvers import reverse
|
||||
from amo.tests import formset, initial
|
||||
from addons.models import Addon
|
||||
from addons.models import Addon, AddonUser
|
||||
from applications.models import Application
|
||||
from devhub.models import ActivityLog
|
||||
from editors.models import EventLog
|
||||
import reviews
|
||||
from reviews.models import Review, ReviewFlag
|
||||
from users.models import UserProfile
|
||||
from versions.models import Version
|
||||
from versions.models import Version, VersionSummary, AppVersion
|
||||
from files.models import Approval, Platform, File
|
||||
from . test_models import create_addon_file
|
||||
|
||||
|
@ -259,12 +261,12 @@ class TestQueueBasics(QueueTest):
|
|||
r = self.client.get(reverse('editors.queue_pending'))
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
eq_(doc('div.section table tr th:eq(0)').text(), u'Addon')
|
||||
eq_(doc('div.section table tr th:eq(1)').text(), u'Type')
|
||||
eq_(doc('div.section table tr th:eq(2)').text(), u'Waiting Time')
|
||||
eq_(doc('div.section table tr th:eq(3)').text(), u'Flags')
|
||||
eq_(doc('div.section table tr th:eq(4)').text(), u'Applications')
|
||||
eq_(doc('div.section table tr th:eq(5)').text(),
|
||||
eq_(doc('table.data-grid tr th:eq(0)').text(), u'Addon')
|
||||
eq_(doc('table.data-grid tr th:eq(1)').text(), u'Type')
|
||||
eq_(doc('table.data-grid tr th:eq(2)').text(), u'Waiting Time')
|
||||
eq_(doc('table.data-grid tr th:eq(3)').text(), u'Flags')
|
||||
eq_(doc('table.data-grid tr th:eq(4)').text(), u'Applications')
|
||||
eq_(doc('table.data-grid tr th:eq(5)').text(),
|
||||
u'Additional Information')
|
||||
|
||||
|
||||
|
@ -274,12 +276,12 @@ class TestPendingQueue(QueueTest):
|
|||
r = self.client.get(reverse('editors.queue_pending'))
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
row = doc('div.section table tr:eq(1)')
|
||||
row = doc('table.data-grid tr:eq(1)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Pending One 0.1')
|
||||
eq_(doc('td a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
args=[self.versions[u'Pending One'].id]) + '?num=1')
|
||||
row = doc('div.section table tr:eq(2)')
|
||||
row = doc('table.data-grid tr:eq(2)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Pending Two 0.1')
|
||||
eq_(doc('a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
|
@ -300,12 +302,12 @@ class TestNominatedQueue(QueueTest):
|
|||
r = self.client.get(reverse('editors.queue_nominated'))
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
row = doc('div.section table tr:eq(1)')
|
||||
row = doc('table.data-grid tr:eq(1)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Nominated One 0.1')
|
||||
eq_(doc('td a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
args=[self.versions[u'Nominated One'].id]) + '?num=1')
|
||||
row = doc('div.section table tr:eq(2)')
|
||||
row = doc('table.data-grid tr:eq(2)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Nominated Two 0.1')
|
||||
eq_(doc('a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
|
@ -326,12 +328,12 @@ class TestPreliminaryQueue(QueueTest):
|
|||
r = self.client.get(reverse('editors.queue_prelim'))
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
row = doc('div.section table tr:eq(1)')
|
||||
row = doc('table.data-grid tr:eq(1)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Prelim One 0.1')
|
||||
eq_(doc('td a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
args=[self.versions[u'Prelim One'].id]) + '?num=1')
|
||||
row = doc('div.section table tr:eq(2)')
|
||||
row = doc('table.data-grid tr:eq(2)')
|
||||
eq_(doc('td:eq(0)', row).text(), u'Prelim Two 0.1')
|
||||
eq_(doc('a:eq(0)', row).attr('href'),
|
||||
reverse('editors.review',
|
||||
|
@ -441,3 +443,225 @@ class TestModeratedQueue(QueueTest):
|
|||
eq_(doc('.tabnav li a:eq(3)').text(), u'Moderated Review (1)')
|
||||
eq_(doc('.tabnav li a:eq(3)').attr('href'),
|
||||
reverse('editors.queue_moderated'))
|
||||
|
||||
|
||||
class SearchTest(EditorTest):
|
||||
|
||||
def setUp(self):
|
||||
self.login_as_editor()
|
||||
|
||||
def named_addons(self, request):
|
||||
return [row.data.addon_name
|
||||
for row in request.context['page'].object_list]
|
||||
|
||||
def search(self, data):
|
||||
r = self.client.get(self.url, data=data)
|
||||
eq_(r.status_code, 200)
|
||||
eq_(r.context['search_form'].errors.as_text(), '')
|
||||
return r
|
||||
|
||||
|
||||
class TestQueueSearch(SearchTest):
|
||||
fixtures = ('base/users', 'base/apps', 'base/appversion')
|
||||
|
||||
def setUp(self):
|
||||
super(TestQueueSearch, self).setUp()
|
||||
self.url = reverse('editors.queue_nominated')
|
||||
create_addon_file('Not Admin Reviewed', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
|
||||
create_addon_file('Admin Reviewed', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
admin_review=True)
|
||||
create_addon_file('Justin Bieber Persona', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
addon_type=amo.ADDON_THEME)
|
||||
create_addon_file('Justin Bieber Search Bar', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
addon_type=amo.ADDON_SEARCH)
|
||||
create_addon_file('Bieber For Mobile', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
application=amo.MOBILE)
|
||||
create_addon_file('Linux Widget', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
platform=amo.PLATFORM_LINUX)
|
||||
create_addon_file('Mac Widget', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
platform=amo.PLATFORM_MAC)
|
||||
|
||||
def test_search_by_admin_reviewed(self):
|
||||
r = self.search({'admin_review': 1})
|
||||
eq_(self.named_addons(r), ['Admin Reviewed'])
|
||||
|
||||
def test_search_by_addon_name(self):
|
||||
r = self.search({'text_query': 'admin'})
|
||||
eq_(sorted(self.named_addons(r)), ['Admin Reviewed',
|
||||
'Not Admin Reviewed'])
|
||||
|
||||
def test_search_by_addon_in_locale(self):
|
||||
uni = 'フォクすけといっしょ'.decode('utf8')
|
||||
d = create_addon_file('Some Addon', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
|
||||
a = Addon.objects.get(pk=d['addon'].id)
|
||||
a.name = {'ja': uni}
|
||||
a.save()
|
||||
r = self.client.get('/ja/' + self.url, data={'text_query': uni},
|
||||
follow=True)
|
||||
eq_(r.status_code, 200)
|
||||
eq_(sorted(self.named_addons(r)), ['Some Addon'])
|
||||
|
||||
def test_search_by_addon_author(self):
|
||||
d = create_addon_file('For Author Search', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
|
||||
u = UserProfile.objects.create(username='fligtar',
|
||||
email='Fligtar@fligtar.com')
|
||||
au = AddonUser.objects.create(user=u, addon=d['addon'])
|
||||
author = AddonUser.objects.filter(id=au.id)
|
||||
for role in [amo.AUTHOR_ROLE_OWNER,
|
||||
amo.AUTHOR_ROLE_DEV]:
|
||||
author.update(role=role)
|
||||
r = self.search({'text_query': 'fligtar@fligtar.com'})
|
||||
eq_(self.named_addons(r), ['For Author Search'])
|
||||
author.update(role=amo.AUTHOR_ROLE_VIEWER)
|
||||
r = self.search({'text_query': 'fligtar@fligtar.com'})
|
||||
eq_(self.named_addons(r), [])
|
||||
|
||||
def test_search_by_supported_email_in_locale(self):
|
||||
d = create_addon_file('Some Addon', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
|
||||
uni = 'フォクすけといっしょ@site.co.jp'.decode('utf8')
|
||||
a = Addon.objects.get(pk=d['addon'].id)
|
||||
a.support_email = {'ja': uni}
|
||||
a.save()
|
||||
r = self.client.get('/ja/' + self.url, data={'text_query': uni},
|
||||
follow=True)
|
||||
eq_(r.status_code, 200)
|
||||
eq_(sorted(self.named_addons(r)), ['Some Addon'])
|
||||
|
||||
def test_search_by_addon_type(self):
|
||||
r = self.search({'addon_type_ids': [amo.ADDON_THEME]})
|
||||
eq_(self.named_addons(r), ['Justin Bieber Persona'])
|
||||
|
||||
def test_search_by_many_addon_types(self):
|
||||
r = self.search({'addon_type_ids': [amo.ADDON_THEME,
|
||||
amo.ADDON_SEARCH]})
|
||||
eq_(sorted(self.named_addons(r)),
|
||||
['Justin Bieber Persona', 'Justin Bieber Search Bar'])
|
||||
|
||||
def test_search_by_platform_mac(self):
|
||||
r = self.search({'platform_ids': [amo.PLATFORM_MAC.id]})
|
||||
eq_(r.status_code, 200)
|
||||
eq_(self.named_addons(r), ['Mac Widget'])
|
||||
|
||||
def test_search_by_platform_linux(self):
|
||||
r = self.search({'platform_ids': [amo.PLATFORM_LINUX.id]})
|
||||
eq_(r.status_code, 200)
|
||||
eq_(self.named_addons(r), ['Linux Widget'])
|
||||
|
||||
def test_search_by_platform_mac_linux(self):
|
||||
r = self.search({'platform_ids': [amo.PLATFORM_MAC.id,
|
||||
amo.PLATFORM_LINUX.id]})
|
||||
eq_(r.status_code, 200)
|
||||
eq_(sorted(self.named_addons(r)), ['Linux Widget', 'Mac Widget'])
|
||||
|
||||
def test_search_by_app(self):
|
||||
r = self.search({'application_id': [amo.MOBILE.id]})
|
||||
eq_(r.status_code, 200)
|
||||
eq_(self.named_addons(r), ['Bieber For Mobile'])
|
||||
|
||||
def test_search_by_version_requires_app(self):
|
||||
r = self.client.get(self.url, data={'max_version': '3.6'})
|
||||
eq_(r.status_code, 200)
|
||||
# This is not the most descriptive message but it's
|
||||
# the easiest to show. This missing app scenario is unlikely.
|
||||
eq_(r.context['search_form'].errors.as_text(),
|
||||
'* max_version\n * Select a valid choice. 3.6 is not '
|
||||
'one of the available choices.')
|
||||
|
||||
def test_search_by_app_version(self):
|
||||
d = create_addon_file('Bieber For Mobile 4.0b2pre', '0.1',
|
||||
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED,
|
||||
application=amo.MOBILE)
|
||||
app = Application.objects.get(pk=amo.MOBILE.id)
|
||||
max = AppVersion.objects.get(application=app, version='4.0b2pre')
|
||||
VersionSummary.objects.create(application=app,
|
||||
version=d['version'],
|
||||
addon=d['addon'],
|
||||
max=max.id)
|
||||
r = self.search({'application_id': amo.MOBILE.id,
|
||||
'max_version': '4.0b2pre'})
|
||||
eq_(self.named_addons(r), [u'Bieber For Mobile 4.0b2pre'])
|
||||
|
||||
def test_age_of_submission(self):
|
||||
Addon.objects.update(
|
||||
nomination_date=datetime.now() - timedelta(days=1))
|
||||
bieber = (Addon.objects
|
||||
.filter(name__localized_string='Justin Bieber Persona'))
|
||||
bieber.update(nomination_date=datetime.now() - timedelta(days=5))
|
||||
r = self.search({'waiting_time_days': 2})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' not in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
bieber.update(nomination_date=datetime.now() - timedelta(days=8))
|
||||
r = self.search({'waiting_time_days': '10+'})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' not in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
bieber.update(nomination_date=datetime.now() - timedelta(days=12))
|
||||
r = self.search({'waiting_time_days': '10+'})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
|
||||
def test_form(self):
|
||||
r = self.search({})
|
||||
doc = pq(r.content)
|
||||
eq_(doc('#id_application_id').attr('data-url'),
|
||||
reverse('editors.application_versions_json'))
|
||||
eq_(doc('#id_max_version option').text(),
|
||||
'Select an application first')
|
||||
r = self.search({'application_id': amo.MOBILE.id})
|
||||
doc = pq(r.content)
|
||||
eq_(doc('#id_max_version option').text(), '4.0b2pre 2.0a1pre 1.0')
|
||||
|
||||
def test_application_versions_json(self):
|
||||
r = self.client.post(reverse('editors.application_versions_json'),
|
||||
{'application_id': amo.MOBILE.id})
|
||||
eq_(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
eq_(data['choices'],
|
||||
[['', ''],
|
||||
['4.0b2pre', '4.0b2pre'],
|
||||
['2.0a1pre', '2.0a1pre'],
|
||||
['1.0', '1.0']])
|
||||
|
||||
|
||||
class TestQueueSearchVersionSpecific(SearchTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestQueueSearchVersionSpecific, self).setUp()
|
||||
self.url = reverse('editors.queue_prelim')
|
||||
create_addon_file('Not Admin Reviewed', '0.1',
|
||||
amo.STATUS_LITE, amo.STATUS_UNREVIEWED)
|
||||
create_addon_file('Justin Bieber Persona', '0.1',
|
||||
amo.STATUS_LITE, amo.STATUS_UNREVIEWED,
|
||||
addon_type=amo.ADDON_THEME)
|
||||
|
||||
def test_age_of_submission(self):
|
||||
Version.objects.update(created=datetime.now() - timedelta(days=1))
|
||||
bieber = (Version.objects.filter(
|
||||
addon__name__localized_string='Justin Bieber Persona'))
|
||||
bieber.update(created=datetime.now() - timedelta(days=5))
|
||||
r = self.search({'waiting_time_days': 2})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' not in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
bieber.update(created=datetime.now() - timedelta(days=8))
|
||||
r = self.search({'waiting_time_days': '10+'})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' not in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
bieber.update(created=datetime.now() - timedelta(days=12))
|
||||
r = self.search({'waiting_time_days': '10+'})
|
||||
addons = self.named_addons(r)
|
||||
assert 'Justin Bieber Persona' in addons, (
|
||||
'Unexpected results: %r' % addons)
|
||||
|
|
|
@ -13,6 +13,8 @@ urlpatterns = patterns('',
|
|||
name='editors.queue_prelim'),
|
||||
url(r'^queue/reviews$', views.queue_moderated,
|
||||
name='editors.queue_moderated'),
|
||||
url(r'^queue/application_versions\.json$', views.application_versions_json,
|
||||
name='editors.application_versions_json'),
|
||||
url(r'^logs$', views.eventlog, name='editors.eventlog'),
|
||||
url(r'^log/(\d+)$', views.eventlog_detail, name='editors.eventlog.detail'),
|
||||
url(r'^reviewlog$', views.reviewlog, name='editors.reviewlog'),
|
||||
|
|
|
@ -9,7 +9,7 @@ from tower import ugettext as _
|
|||
|
||||
import amo
|
||||
from access import acl
|
||||
from amo.decorators import login_required
|
||||
from amo.decorators import login_required, json_view, post_required
|
||||
from amo.utils import paginate
|
||||
from amo.urlresolvers import reverse
|
||||
from devhub.models import ActivityLog
|
||||
|
@ -76,6 +76,12 @@ def home(request):
|
|||
|
||||
def _queue(request, TableObj, tab):
|
||||
qs = TableObj.Meta.model.objects.all()
|
||||
if request.GET:
|
||||
search_form = forms.QueueSearchForm(request.GET)
|
||||
if search_form.is_valid():
|
||||
qs = search_form.filter_qs(qs)
|
||||
else:
|
||||
search_form = forms.QueueSearchForm()
|
||||
review_num = request.GET.get('num', None)
|
||||
if review_num:
|
||||
try:
|
||||
|
@ -99,6 +105,7 @@ def _queue(request, TableObj, tab):
|
|||
table.set_page(page)
|
||||
return jingo.render(request, 'editors/queue.html',
|
||||
{'table': table, 'page': page, 'tab': tab,
|
||||
'search_form': search_form,
|
||||
'queue_counts': queue_counts})
|
||||
|
||||
|
||||
|
@ -149,7 +156,17 @@ def queue_moderated(request):
|
|||
|
||||
return jingo.render(request, 'editors/queue.html',
|
||||
{'reviews_formset': reviews_formset, 'tab': 'moderated',
|
||||
'page': page, 'flags': flags, 'queue_counts': _queue_counts()})
|
||||
'page': page, 'flags': flags, 'queue_counts': _queue_counts(),
|
||||
'search_form': None})
|
||||
|
||||
|
||||
@editor_required
|
||||
@post_required
|
||||
@json_view
|
||||
def application_versions_json(request):
|
||||
app_id = request.POST['application_id']
|
||||
f = forms.QueueSearchForm()
|
||||
return {'choices': f.version_choices_for_app_id(app_id)}
|
||||
|
||||
|
||||
@editor_required
|
||||
|
|
|
@ -350,3 +350,51 @@ tr.comments {
|
|||
color: #9A2E2E;
|
||||
}
|
||||
|
||||
/* data grid search form */
|
||||
.queue-outer #queue-search table {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.queue-outer #queue-search {
|
||||
padding: 1em;
|
||||
}
|
||||
#advanced-search {
|
||||
display: none;
|
||||
margin-top: 1em;
|
||||
}
|
||||
#advanced-search .column1 {
|
||||
float: left;
|
||||
width: 375px;
|
||||
}
|
||||
#advanced-search .column1 label {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 13em;
|
||||
}
|
||||
#advanced-search .form-elem {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
#advanced-search .column2,
|
||||
#advanced-search .column3 {
|
||||
float: left;
|
||||
width: 250px;
|
||||
}
|
||||
#advanced-search .column2 label,
|
||||
#advanced-search .column3 label {
|
||||
display: block;
|
||||
}
|
||||
#advanced-search .form-row {
|
||||
clear: both;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
#advanced-search .column3:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
clear: both;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
#id_text_query {
|
||||
width: 25em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ $(function() {
|
|||
|
||||
$('a.show').click(show_comments);
|
||||
$('a.hide').click(hide_comments);
|
||||
|
||||
if ($('#queue-search').length) {
|
||||
initQueueSearch($('#queue-search'));
|
||||
}
|
||||
});
|
||||
|
||||
function initEditorsMain() {
|
||||
|
@ -48,3 +52,31 @@ function initEditorsMain() {
|
|||
} catch(e){}
|
||||
}
|
||||
}
|
||||
|
||||
function initQueueSearch(doc) {
|
||||
$('#toggle-queue-search', doc).click(function(e) {
|
||||
e.preventDefault();
|
||||
$(e.target).blur();
|
||||
$('#advanced-search', doc).toggle();
|
||||
});
|
||||
|
||||
$('#id_application_id', doc).change(function(e) {
|
||||
var maxVer = $('#id_max_version', doc),
|
||||
sel = $(e.target),
|
||||
appId = $('option:selected', sel).val();
|
||||
|
||||
if (!appId) {
|
||||
$('option', maxVer).remove();
|
||||
maxVer.append(format('<option value="{0}">{1}</option>',
|
||||
['', gettext('Select an application first')]));
|
||||
return;
|
||||
}
|
||||
$.post(sel.attr('data-url'), {'application_id': appId}, function(d) {
|
||||
$('option', maxVer).remove();
|
||||
$.each(d.choices, function(i, ch) {
|
||||
maxVer.append(format('<option value="{0}">{1}</option>',
|
||||
[ch[0], ch[1]]));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
$(document).ready(function(){
|
||||
|
||||
var editorFixtures = {
|
||||
setup: function() {
|
||||
this.sandbox = tests.createSandbox('#editors-search-form');
|
||||
initQueueSearch(this.sandbox);
|
||||
},
|
||||
teardown: function() {
|
||||
this.sandbox.remove();
|
||||
},
|
||||
selectOpt: function(index) {
|
||||
var doc = this.sandbox;
|
||||
$('#id_application_id option', doc).eq(index).attr('selected', 'selected');
|
||||
$('#id_application_id', doc).trigger('change');
|
||||
}
|
||||
};
|
||||
|
||||
module('editors search form 1', editorFixtures);
|
||||
|
||||
asyncTest('select application', function() {
|
||||
var doc = this.sandbox;
|
||||
$.mockjax({
|
||||
url: '/application_versions.json',
|
||||
status: 200,
|
||||
response: function(settings) {
|
||||
equals(settings.data.application_id, '1');
|
||||
this.responseText = {
|
||||
choices: [['', ''],
|
||||
['4.0b2pre', '4.0b2pre'],
|
||||
['2.0a1pre', '2.0a1pre'],
|
||||
['1.0', '1.0']]
|
||||
};
|
||||
}
|
||||
});
|
||||
this.selectOpt(1);
|
||||
tests.waitFor(function() {
|
||||
return ($('#id_max_version option', doc).length > 1);
|
||||
}).thenDo(function() {
|
||||
var values = [];
|
||||
$.each($('#id_max_version option', doc), function(i, e) {
|
||||
values.push($(e).val());
|
||||
});
|
||||
same(values, ["", "4.0b2pre", "2.0a1pre", "1.0"]);
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
module('editors search form 2', editorFixtures);
|
||||
|
||||
asyncTest('de-select application', function() {
|
||||
var suite = this,
|
||||
doc = this.sandbox;
|
||||
$.mockjax({
|
||||
url: '/application_versions.json',
|
||||
status: 200,
|
||||
responseText: {choices: [['', ''], ['4.0b2pre', '4.0b2pre']]}
|
||||
});
|
||||
suite.selectOpt(1);
|
||||
tests.waitFor(function() {
|
||||
return ($('#id_max_version option', doc).length > 1);
|
||||
}).thenDo(function() {
|
||||
suite.selectOpt(0);
|
||||
tests.waitFor(function() {
|
||||
return ($('#id_max_version option', doc).length == 1);
|
||||
}).thenDo(function() {
|
||||
equals($('#id_max_version option', doc).text(),
|
||||
'Select an application first');
|
||||
start();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -4,6 +4,7 @@
|
|||
"js/zamboni/jstestnet.js",
|
||||
"js/zamboni/tests.js",
|
||||
"js/zamboni/devhub.js",
|
||||
"js/zamboni/l10n.js"
|
||||
"js/zamboni/l10n.js",
|
||||
"js/zamboni/editors.js"
|
||||
]
|
||||
}
|
|
@ -177,6 +177,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editors-search-form">
|
||||
<form>
|
||||
<label for="id_application_id">Application</label>
|
||||
<select data-url="/application_versions.json" name="application_id" id="id_application_id">
|
||||
<option value="" selected="selected"></option>
|
||||
<option value="1">Firefox</option>
|
||||
<option value="2">Mozilla</option>
|
||||
</select>
|
||||
<label for="id_max_version">Max. Version</label>
|
||||
<select name="max_version" id="id_max_version">
|
||||
<option value="" selected="selected">Select an application first</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="{{ url('jsi18n') }}/build:{{ BUILD_ID_JS }}"></script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче