Repository: mozmeao/snippets-service

Issue:
Ability to add custom JEXL in Target



Discussion:

Resolving patch:
diff --git a/snippets/base/admin/adminmodels.py b/snippets/base/admin/adminmodels.py
--- a/snippets/base/admin/adminmodels.py
+++ b/snippets/base/admin/adminmodels.py
@@ -924,13 +924,14 @@ def save_model(self, request, obj, form, change):
 
 
 class TargetAdmin(RelatedJobsMixin, admin.ModelAdmin):
-    form = forms.TargetAdminForm
+    change_list_template = 'admin/base/target/change_list.html'
+    form_custom = forms.TargetAdminCustomForm
+    form_full = forms.TargetAdminForm
     save_on_top = True
     readonly_fields = [
         'created',
         'modified',
         'creator',
-        'jexl_expr',
         'jobs_list',
         'related_total_jobs',
         'related_published_jobs',
@@ -943,7 +944,18 @@ class TargetAdmin(RelatedJobsMixin, admin.ModelAdmin):
         'related_published_jobs',
         'related_total_jobs',
     ]
-    fieldsets = [
+    list_filter = [
+        filters.RelatedPublishedASRSnippetFilter,
+    ]
+    fieldsets_custom = [
+        ('ID', {'fields': ('name',)}),
+        ('Product channels', {
+            'description': 'What channels will this snippet be available in?',
+            'fields': (('on_release', 'on_beta', 'on_aurora', 'on_nightly', 'on_esr'),)
+        }),
+        ('Targeting', {'fields': ('jexl_expr',)}),
+    ]
+    fieldsets_full = [
         ('ID', {'fields': ('name',)}),
         ('Product channels', {
             'description': 'What channels will this snippet be available in?',
@@ -992,9 +1004,6 @@ class TargetAdmin(RelatedJobsMixin, admin.ModelAdmin):
             'fields': ('creator', ('created', 'modified'), 'jexl_expr'),
         }),
     ]
-    list_filter = [
-        filters.RelatedPublishedASRSnippetFilter,
-    ]
 
     class Media:
         css = {
@@ -1007,6 +1016,24 @@ class Media:
             'js/admin/alert-page-leaving.js',
         )
 
+    def _wants_custom_form(self, request, obj):
+        return request.GET.get('custom') == 'true' or (obj and obj.is_custom)
+
+    def get_form(self, request, obj=None, **kwargs):
+        if self._wants_custom_form(request, obj):
+            return self.form_custom
+        return self.form_full
+
+    def get_readonly_fields(self, request, obj=None, **kwargs):
+        if self._wants_custom_form(request, obj):
+            return self.readonly_fields
+        return self.readonly_fields + ['jexl_expr']
+
+    def get_fieldsets(self, request, obj=None, **kwargs):
+        if self._wants_custom_form(request, obj):
+            return self.fieldsets_custom
+        return self.fieldsets_full
+
     def save_model(self, request, obj, form, change):
         if not obj.creator_id:
             obj.creator = request.user
diff --git a/snippets/base/forms.py b/snippets/base/forms.py
--- a/snippets/base/forms.py
+++ b/snippets/base/forms.py
@@ -649,6 +649,12 @@ def save(self, *args, **kwargs):
         return snippet
 
 
+class TargetAdminCustomForm(forms.ModelForm):
+    class Meta:
+        model = models.Target
+        fields = ['name', 'jexl_expr', 'on_release', 'on_esr', 'on_beta', 'on_aurora', 'on_nightly']
+
+
 class TargetAdminForm(forms.ModelForm):
     filtr_is_default_browser = fields.JEXLChoiceField(
         'isDefaultBrowser',
diff --git a/snippets/base/models.py b/snippets/base/models.py
--- a/snippets/base/models.py
+++ b/snippets/base/models.py
@@ -534,6 +534,14 @@ class Meta:
     def __str__(self):
         return self.name
 
+    @property
+    def is_custom(self):
+        """Returns True if Target's JEXL has been manualy created."""
+        for field in self._meta.get_fields():
+            if field.name.startswith('filtr_') and getattr(self, field.name):
+                return False
+        return True
+
 
 class Campaign(models.Model):
     creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)


Test patch:
diff --git a/snippets/base/tests/test_models.py b/snippets/base/tests/test_models.py
--- a/snippets/base/tests/test_models.py
+++ b/snippets/base/tests/test_models.py
@@ -901,3 +901,11 @@ def test_duplicate(self):
         self.assertEqual(duplicate_job.metric_clicks, 0)
         self.assertEqual(duplicate_job.metric_blocks, 0)
         self.assertEqual(duplicate_job.completed_on, None)
+
+
+class TargetTests(TestCase):
+    def test_is_custom(self):
+        target = TargetFactory(on_release=True)
+        self.assertTrue(target.is_custom)
+        not_custom_target = TargetFactory(on_release=True, filtr_is_default_browser='true')
+        self.assertFalse(not_custom_target.is_custom)