forked from mozilla/zamboni
/
forms.py
291 lines (244 loc) · 12.5 KB
/
forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
from datetime import timedelta
from django import forms
from django.forms import widgets
from django.db.models import Q
from django.utils.translation import get_language
from django.core.validators import ValidationError
import happyforms
import jinja2
from tower import ugettext as _, ugettext_lazy as _lazy
import amo
from amo.urlresolvers import reverse
from applications.models import AppVersion
from editors.models import CannedResponse
from editors.helpers import (ReviewHelper, ReviewAddon, ReviewFiles,
file_review_status)
from files.models import File
ACTION_FILTERS = (('', ''), ('approved', _lazy('Approved reviews')),
('deleted', _lazy('Deleted reviews')))
ACTION_DICT = dict(approved=amo.LOG.APPROVE_REVIEW,
deleted=amo.LOG.DELETE_REVIEW)
class EventLogForm(happyforms.Form):
start = forms.DateField(required=False,
label=_lazy(u'View entries between'))
end = forms.DateField(required=False,
label=_lazy(u'and'))
filter = forms.ChoiceField(required=False, choices=ACTION_FILTERS,
label=_lazy(u'Filter by type/action'))
def clean(self):
data = self.cleaned_data
# We want this to be inclusive of the end date.
if 'end' in data and data['end']:
data['end'] += timedelta(days=1)
if 'filter' in data and data['filter']:
data['filter'] = ACTION_DICT[data['filter']]
return data
class ReviewLogForm(happyforms.Form):
start = forms.DateField(required=False,
label=_lazy(u'View entries between'))
end = forms.DateField(required=False,
label=_lazy(u'and'))
def clean(self):
data = self.cleaned_data
# We want this to be inclusive of the end date.
if 'end' in data and data['end']:
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_addon_type_ids(self):
if self.cleaned_data['addon_type_ids']:
# Remove "Any Addon Extension" from the list so that no filter
# is applied in that case.
ids = set(self.cleaned_data['addon_type_ids'])
self.cleaned_data['addon_type_ids'] = ids - set(str(amo.ADDON_ANY))
return self.cleaned_data['addon_type_ids']
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'])
# Adjust _file_platform_ids so that it includes ALL platforms
# not the ones filtered by the search criteria:
qs.base_query['from'].extend([
"""LEFT JOIN files all_files
ON (all_files.version_id = versions.id)"""])
group = 'GROUP_CONCAT(DISTINCT all_files.platform_id)'
qs.base_query['select']['_file_platform_ids'] = group
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'])
qs = qs.having(*args)
return qs
class AddonFilesMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, addon_file):
addon = addon_file.version.addon
# L10n: 0 = platform, 1 = filename, 2 = status message
return jinja2.Markup(_(u"<strong>%s</strong> · %s · %s") %
(addon_file.platform, addon_file.filename,
file_review_status(addon, addon_file)))
class NonValidatingChoiceField(forms.ChoiceField):
"""A ChoiceField that doesn't validate."""
def validate(self, value):
pass
class ReviewAddonForm(happyforms.Form):
addon_files = AddonFilesMultipleChoiceField(required=False,
queryset=File.objects.none(), label=_lazy('Files:'),
widget=forms.CheckboxSelectMultiple())
comments = forms.CharField(required=True, widget=forms.Textarea(),
label=_lazy('Comments:'))
canned_response = NonValidatingChoiceField(required=False)
action = forms.ChoiceField(required=True, widget=forms.RadioSelect())
operating_systems = forms.CharField(required=False,
label=_lazy('Operating systems:'))
applications = forms.CharField(required=False,
label=_lazy('Applications:'))
notify = forms.BooleanField(required=False,
label=_lazy('Notify me the next time this '
'add-on is updated. (Subsequent '
'updates will not generate an '
'email)'))
def is_valid(self):
result = super(ReviewAddonForm, self).is_valid()
if result:
self.helper.set_data(self.cleaned_data)
return result
def __init__(self, *args, **kw):
self.helper = kw.pop('helper')
super(ReviewAddonForm, self).__init__(*args, **kw)
self.fields['addon_files'].queryset = self.helper.all_files
self.addon_files_disabled = (self.helper.all_files
# We can't review disabled, and public are already reviewed.
.filter(status__in=[amo.STATUS_DISABLED, amo.STATUS_PUBLIC])
.values_list('pk', flat=True))
# We're starting with an empty one, which will be hidden via CSS.
canned_choices = [['', [('', _('Choose a canned response...'))]]]
responses = CannedResponse.objects.all()
# Loop through the actions (prelim, public, etc).
for k, action in self.helper.actions.iteritems():
action_choices = [[c.response, c.name] for c in responses
if c.sort_group and k in c.sort_group.split(',')]
# Add the group of responses to the canned_choices array.
if action_choices:
canned_choices.append([action['label'], action_choices])
# Now, add everything not in a group.
for r in responses:
if not r.sort_group:
canned_choices.append([r.response, r.name])
self.fields['canned_response'].choices = canned_choices
self.fields['action'].choices = [(k, v['label']) for k, v
in self.helper.actions.items()]
class ReviewFileForm(ReviewAddonForm):
def clean_addon_files(self):
files = self.data.getlist('addon_files')
if self.data.get('action', '') == 'prelim':
if not files:
raise ValidationError(_('You must select some files.'))
for pk in files:
file = self.helper.all_files.get(pk=pk)
if (file.status != amo.STATUS_UNREVIEWED and not
(self.helper.addon.status == amo.STATUS_LITE and
file.status == amo.STATUS_UNREVIEWED)):
raise ValidationError(_('File %s is not pending review.')
% file.filename)
return self.fields['addon_files'].queryset.filter(pk__in=files)
def get_review_form(data, request=None, addon=None, version=None):
helper = ReviewHelper(request=request, addon=addon, version=version)
form = {ReviewAddon: ReviewAddonForm,
ReviewFiles: ReviewFileForm}[helper.handler.__class__]
return form(data, helper=helper)
class MOTDForm(happyforms.Form):
motd = forms.CharField(required=True, widget=widgets.Textarea())