Adds form and backend for searching the editor queue (bug 622171)

This commit is contained in:
Kumar McMillan 2011-02-16 16:19:57 -06:00
Родитель 42b2d546ae
Коммит 569332223e
13 изменённых файлов: 614 добавлений и 27 удалений

Просмотреть файл

@ -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 }}">
{% if column.sortable %}
<a href="?sort={{ column.name_toggled }}" class="sort-icon ed-sprite-sort-{{ sprite }}">
{{ column }}
</a>
{% else %}
{{ column }}
{% 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>