1
+ import html
1
2
import zipfile
3
+ from collections import defaultdict
2
4
from datetime import datetime , timedelta
3
5
4
6
from django import forms
15
17
from django .utils .translation import gettext
16
18
17
19
import markupsafe
20
+ import waffle
18
21
19
22
import olympia .core .logger
20
23
from olympia import amo , ratings
21
24
from olympia .abuse .models import CinderJob , CinderPolicy , ContentDecision
22
25
from olympia .access import acl
26
+ from olympia .addons .models import Addon
23
27
from olympia .amo .forms import AMOModelForm
24
28
from olympia .constants .abuse import DECISION_ACTIONS
25
29
from olympia .constants .reviewers import REVIEWER_DELAYED_REJECTION_PERIOD_DAYS_DEFAULT
@@ -104,22 +108,10 @@ class NumberInput(widgets.Input):
104
108
input_type = 'number'
105
109
106
110
107
- class VersionsChoiceField (ModelMultipleChoiceField ):
108
- """
109
- Widget to use together with VersionsChoiceWidget to display the list of
110
- versions used by review page for some actions.
111
- """
112
-
113
- def label_from_instance (self , obj ):
114
- """Return the object instead of transforming into a label at this stage
115
- so that it's available in the widget."""
116
- return obj
117
-
118
-
119
111
class VersionsChoiceWidget (forms .SelectMultiple ):
120
112
"""
121
- Widget to use together with VersionsChoiceField to display the list of
122
- versions used by review page for some actions.
113
+ Widget to use together with WidgetRenderedModelMultipleChoiceField to display the
114
+ list of versions used by review page for some actions.
123
115
"""
124
116
125
117
actions_filters = {
@@ -308,6 +300,37 @@ def create_option(
308
300
)
309
301
310
302
303
+ class CinderPolicyWidget (forms .CheckboxSelectMultiple ):
304
+ """
305
+ Widget to use together with a WidgetRenderedModelMultipleChoiceField to display
306
+ select elements with additional attribute to allow toggling.
307
+ """
308
+
309
+ def create_option (
310
+ self , name , value , label , selected , index , subindex = None , attrs = None
311
+ ):
312
+ obj = label
313
+ label = str (obj )
314
+ attrs = attrs or {}
315
+ actions_on_policy = {
316
+ DECISION_ACTIONS .for_api_value (action ).value
317
+ for action in obj .enforcement_actions
318
+ if DECISION_ACTIONS .has_api_value (action )
319
+ }
320
+ actions = (
321
+ reviewer_action
322
+ for reviewer_action , defn in self .helper_actions .items ()
323
+ # show this policy for this action if there any common enforcement actions
324
+ if (ha_ea := defn .get ('enforcement_actions' , ()))
325
+ and actions_on_policy .intersection (ha_ea )
326
+ )
327
+ attrs ['class' ] = 'data-toggle'
328
+ attrs ['data-value' ] = ' ' .join (actions )
329
+ return super ().create_option (
330
+ name , value , label , selected , index , subindex , attrs
331
+ )
332
+
333
+
311
334
class ActionChoiceWidget (forms .RadioSelect ):
312
335
"""
313
336
Widget to add boilerplate_text to action options.
@@ -383,7 +406,7 @@ class ReviewForm(forms.Form):
383
406
required = True , widget = forms .Textarea (), label = 'Comments:'
384
407
)
385
408
action = forms .ChoiceField (required = True , widget = ActionChoiceWidget )
386
- versions = VersionsChoiceField (
409
+ versions = WidgetRenderedModelMultipleChoiceField (
387
410
# The <select> is displayed/hidden dynamically depending on the action
388
411
# so it needs the data-toggle class (data-value attribute is set later
389
412
# during __init__). VersionsChoiceWidget takes care of adding that to
@@ -444,13 +467,13 @@ class ReviewForm(forms.Form):
444
467
queryset = CinderJob .objects .none (),
445
468
widget = CinderJobsWidget (attrs = {'class' : 'data-toggle-hide' }),
446
469
)
447
-
448
- cinder_policies = forms . ModelMultipleChoiceField (
470
+ # queryset and widget are set later in __init__
471
+ cinder_policies = WidgetRenderedModelMultipleChoiceField (
449
472
# queryset is set later in __init__
450
473
queryset = CinderPolicy .objects .none (),
451
474
required = True ,
452
475
label = 'Choose one or more policies:' ,
453
- widget = widgets . CheckboxSelectMultiple ,
476
+ widget = CinderPolicyWidget () ,
454
477
)
455
478
appeal_action = forms .MultipleChoiceField (
456
479
required = False ,
@@ -470,8 +493,11 @@ def is_valid(self):
470
493
self .fields ['versions' ].required = True
471
494
if not action .get ('requires_reasons' , False ):
472
495
self .fields ['reasons' ].required = False
473
- if not action .get ('requires_policies ' ):
496
+ if not action .get ('enforcement_actions ' ):
474
497
self .fields ['cinder_policies' ].required = False
498
+ else :
499
+ # we no longer strictly require comments with cinder policies
500
+ self .fields ['comments' ].required = False
475
501
if self .data .get ('cinder_jobs_to_resolve' ):
476
502
# if a cinder job is being resolved we need a review reason
477
503
if action .get ('requires_reasons_for_cinder_jobs' ):
@@ -508,8 +534,9 @@ def clean(self):
508
534
if self .cleaned_data .get ('cinder_jobs_to_resolve' ) and self .cleaned_data .get (
509
535
'cinder_policies'
510
536
):
511
- actions = self .helper .handler .get_decision_actions_from_policies (
512
- self .cleaned_data .get ('cinder_policies' )
537
+ actions = CinderPolicy .get_decision_actions_from_policies (
538
+ self .cleaned_data .get ('cinder_policies' ),
539
+ for_entity = Addon ,
513
540
)
514
541
if len (actions ) == 0 :
515
542
self .add_error (
@@ -561,6 +588,23 @@ def clean(self):
561
588
),
562
589
)
563
590
591
+ # Add in any dynamic placeholder values for the cinder policies
592
+ # see jinja_helpers.render_text_with_input_fields for name format
593
+ if policies := {
594
+ str (p .id ): p for p in self .cleaned_data .get ('cinder_policies' , [])
595
+ }:
596
+ policy_values = defaultdict (dict )
597
+ for key , value in self .data .items ():
598
+ if (
599
+ key .startswith ('policy-value-' )
600
+ and (split_1 := key .split ('policy-value-' , 1 ))
601
+ and (id_and_placeholder := html .unescape (split_1 [1 ]))
602
+ and len (split_2 := id_and_placeholder .split ('-' , 1 )) == 2
603
+ and (policy := policies .get (split_2 [0 ]))
604
+ ):
605
+ policy_values [policy .uuid ][split_2 [1 ]] = value
606
+ self .cleaned_data ['policy_values' ] = policy_values
607
+
564
608
return self .cleaned_data
565
609
566
610
def clean_delayed_rejection_date (self ):
@@ -579,6 +623,9 @@ def clean_version_pk(self):
579
623
def __init__ (self , * args , ** kw ):
580
624
self .helper = kw .pop ('helper' )
581
625
super ().__init__ (* args , ** kw )
626
+ if waffle .switch_is_active ('policy_selection_rather_than_reasons' ):
627
+ # When we're using policies reviewers shouldn't need to write as much
628
+ self .fields ['comments' ].widget = forms .Textarea (attrs = {'rows' : 2 })
582
629
if any (action .get ('delayable' ) for action in self .helper .actions .values ()):
583
630
# Default delayed rejection date should be
584
631
# REVIEWER_DELAYED_REJECTION_PERIOD_DAYS_DEFAULT days in the
@@ -646,15 +693,19 @@ def __init__(self, *args, **kw):
646
693
]
647
694
648
695
# Set the queryset for reasons based on the add-on type.
649
- self .fields ['reasons' ].queryset = ReviewActionReason .objects .filter (
650
- is_active = True ,
651
- addon_type__in = [
652
- amo .ADDON_ANY ,
653
- amo .ADDON_STATICTHEME
654
- if self .helper .addon .type == amo .ADDON_STATICTHEME
655
- else amo .ADDON_EXTENSION ,
656
- ],
657
- ).exclude (canned_response = '' )
696
+ self .fields ['reasons' ].queryset = (
697
+ ReviewActionReason .objects .filter (
698
+ is_active = True ,
699
+ addon_type__in = [
700
+ amo .ADDON_ANY ,
701
+ amo .ADDON_STATICTHEME
702
+ if self .helper .addon .type == amo .ADDON_STATICTHEME
703
+ else amo .ADDON_EXTENSION ,
704
+ ],
705
+ )
706
+ .exclude (canned_response = '' )
707
+ .select_related ('cinder_policy__parent' )
708
+ )
658
709
659
710
# Add actions from the helper into the action widget so we can access
660
711
# them in create_option.
@@ -668,7 +719,10 @@ def __init__(self, *args, **kw):
668
719
# Set the queryset for policies to show as options
669
720
self .fields ['cinder_policies' ].queryset = CinderPolicy .objects .filter (
670
721
expose_in_reviewer_tools = True
671
- )
722
+ ).select_related ('parent' )
723
+
724
+ # Pass on the reviewer tools actions so we can set the show/hide on policies
725
+ self .fields ['cinder_policies' ].widget .helper_actions = self .helper .actions
672
726
673
727
@property
674
728
def unreviewed_files (self ):
0 commit comments