We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use OAuth client ids instead of names in Firefox App Targeting.
List of client ids: https://docs.telemetry.mozilla.org/datasets/fxa_metrics/attribution.html#service-attribution

Related bug https://bugzilla.mozilla.org/show_bug.cgi?id=1596514#c5

 - [ ] After pushing to prod, generate JEXL for related Targets
 - [ ] ping mindy when is complete

<!-- probot = {"419912":{"who":"glogiotatidis","what":"","when":"2020-01-08T09:00:00.000Z"}} -->

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `snippets/base/admin/fields.py`
Content:
```
1 from django.core.exceptions import ValidationError
2 from django.forms import (ChoiceField, ModelChoiceField, ModelMultipleChoiceField,
3                           MultiValueField, MultipleChoiceField)
4 
5 from snippets.base.models import Addon, TargetedCountry
6 
7 from .widgets import JEXLMultiWidget
8 
9 
10 class MultipleChoiceFieldCSV(MultipleChoiceField):
11     # To be used with in snippets.base.forms.SnippetAdminForm and in
12     # combination with DynamicField. We don't directly save() this field in the
13     # database so get_prep_value has not been implemented.
14 
15     def prepare_value(self, value):
16         value = super(MultipleChoiceFieldCSV, self).prepare_value(value)
17         if not isinstance(value, list):
18             value = value.split(';')
19         return value
20 
21     def clean(self, value):
22         value = super(MultipleChoiceFieldCSV, self).clean(value)
23         return ';'.join(value)
24 
25 
26 class JEXLBaseField():
27     def to_jexl(self, value):
28         if value:
29             return self.jexl.format(attr_name=self.attr_name, value=value)
30 
31         return None
32 
33 
34 class JEXLChoiceField(JEXLBaseField, ChoiceField):
35     def __init__(self, attr_name, *args, **kwargs):
36         self.attr_name = attr_name
37         self.jexl = '{attr_name} == {value}'
38         self.jexl = kwargs.pop('jexl', self.jexl)
39         return super().__init__(*args, **kwargs)
40 
41     def to_jexl(self, value):
42         if value:
43             return self.jexl.format(attr_name=self.attr_name, value=value)
44 
45 
46 class JEXLModelMultipleChoiceField(JEXLBaseField, ModelMultipleChoiceField):
47     def __init__(self, attr_name, *args, **kwargs):
48         self.attr_name = attr_name
49         self.jexl = '{attr_name} in {value}'
50         self.jexl = kwargs.pop('jexl', self.jexl)
51         return super().__init__(*args, **kwargs)
52 
53     def prepare_value(self, value):
54         if isinstance(value, str):
55             value = value.split(';')
56         return super().prepare_value(value)
57 
58     def clean(self, value):
59         value = super().clean(value)
60         return ';'.join([str(x.id) for x in value])
61 
62 
63 class JEXLCountryField(JEXLModelMultipleChoiceField):
64     def to_jexl(self, value):
65         if value:
66             values = TargetedCountry.objects.filter(id__in=value.split(";"))
67             return f'region in {[x.code for x in values]}'
68         return None
69 
70 
71 class JEXLRangeField(JEXLBaseField, MultiValueField):
72     def __init__(self, attr_name, choices, **kwargs):
73         self.attr_name = attr_name
74         self.jexl = {
75             'minimum': '{value} <= {attr_name}',
76             'maximum': '{attr_name} < {value}'
77         }
78         self.jexl = kwargs.pop('jexl', self.jexl)
79         fields = (
80             ChoiceField(choices=choices),
81             ChoiceField(choices=choices),
82         )
83         super().__init__(fields, **kwargs)
84         self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields],
85                                       template_name='widgets/jexlrange.html')
86 
87     def compress(self, data_list):
88         return ','.join(data_list)
89 
90     def to_jexl(self, value):
91         final_jexl = []
92         if value:
93             minimum, maximum = value.split(',')
94             if minimum:
95                 final_jexl.append(
96                     self.jexl['minimum'].format(attr_name=self.attr_name, value=minimum)
97                 )
98             if maximum:
99                 final_jexl.append(
100                     self.jexl['maximum'].format(attr_name=self.attr_name, value=maximum)
101                 )
102         return ' && '.join(final_jexl)
103 
104     def validate(self, value):
105         minimum, maximum = value.split(',')
106         self.fields[0].validate(minimum)
107         self.fields[1].validate(maximum)
108 
109         if minimum and maximum and int(minimum) > int(maximum):
110             raise ValidationError('Minimum value must be lower or equal to maximum value.')
111         return value
112 
113 
114 class JEXLFirefoxRangeField(JEXLRangeField):
115     def __init__(self, **kwargs):
116         # Include only versions greater than 63, where ASRSnippets exist.
117         min_version = 64
118         # Need to be able to dynamically change this, probably using
119         # product_details. Issue #855
120         max_version = 84
121 
122         choices = (
123             [(None, 'No limit')] +
124             [(x, x) for x in reversed(range(min_version, max_version + 1))]
125         )
126         super().__init__('firefoxVersion', choices, **kwargs)
127 
128     def validate(self, value):
129         minimum, maximum = value.split(',')
130         self.fields[0].validate(minimum)
131         self.fields[1].validate(maximum)
132 
133         if minimum and maximum and minimum > maximum:
134             raise ValidationError('Minimum value must be lower or equal to maximum value.')
135         return value
136 
137 
138 class JEXLAddonField(MultiValueField):
139     def __init__(self, **kwargs):
140         choices = (
141             (None, "I don't care"),
142             ('not_installed', 'Not Installed'),
143             ('installed', 'Installed'),
144         )
145         fields = (
146             ChoiceField(choices=choices),
147             ModelChoiceField(queryset=Addon.objects.all(), required=False),
148         )
149         super().__init__(fields, **kwargs)
150         self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])
151 
152     def compress(self, data_list):
153         if data_list:
154             return '{},{}'.format(data_list[0], getattr(data_list[1], 'id', ''))
155         return ''
156 
157     def to_jexl(self, value):
158         check, addon_id = value.split(',')
159         if not check or not addon_id:
160             return ''
161 
162         addon = Addon.objects.get(id=addon_id)
163         if check == 'not_installed':
164             jexl = '("{}" in addonsInfo.addons|keys) == false'.format(addon.guid)
165         elif check == 'installed':
166             jexl = '("{}" in addonsInfo.addons|keys) == true'.format(addon.guid)
167 
168         return jexl
169 
170     def validate(self, value):
171         check, addon_id = value.split(',')
172 
173         self.fields[0].validate(check)
174         self.fields[1].validate(addon_id)
175 
176         if check and not addon_id:
177             raise ValidationError('You must select an add-on')
178 
179         if not check and addon_id:
180             raise ValidationError('You must select a check')
181         return value
182 
183 
184 class JEXLFirefoxServicesField(MultiValueField):
185     def __init__(self, **kwargs):
186         check_choices = (
187             (None, "I don't care"),
188             ('no_account', "User hasn't signed up for"),
189             ('has_account', 'User has signed up for'),
190         )
191         service_choices = (
192             (None, '---------'),
193             ('Firefox Lockwise', 'Firefox Lockwise'),
194             ('Firefox Monitor', 'Firefox Monitor'),
195             ('Firefox Send', 'Firefox Send'),
196             ('Firefox Private Network', 'Firefox Private Network'),
197             ('Notes', 'Notes'),
198             ('Pocket', 'Pocket'),
199         )
200         fields = (
201             ChoiceField(choices=check_choices),
202             ChoiceField(choices=service_choices),
203         )
204         super().__init__(fields, **kwargs)
205         self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])
206 
207     def compress(self, data_list):
208         if data_list:
209             return f'{data_list[0]},{data_list[1]}'
210         return ''
211 
212     def to_jexl(self, value):
213         check, service_name = value.split(',')
214         if not check or not service_name:
215             return ''
216 
217         if check == 'no_account':
218             jexl = f'("{service_name}" in attachedFxAOAuthClients|mapToProperty("name")) == false'
219         elif check == 'has_account':
220             jexl = f'("{service_name}" in attachedFxAOAuthClients|mapToProperty("name")) == true'
221 
222         return jexl
223 
224     def validate(self, value):
225         check, service_name = value.split(',')
226 
227         self.fields[0].validate(check)
228         self.fields[1].validate(service_name)
229 
230         if check and not service_name:
231             raise ValidationError('You must select an Service.')
232 
233         if not check and service_name:
234             raise ValidationError('You must select a check.')
235         return value
236
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/snippets/base/admin/fields.py b/snippets/base/admin/fields.py
--- a/snippets/base/admin/fields.py
+++ b/snippets/base/admin/fields.py
@@ -188,14 +188,19 @@
             ('no_account', "User hasn't signed up for"),
             ('has_account', 'User has signed up for'),
         )
+        # Verify IDs using
+        # curl -s https://oauth.stage.mozaws.net/v1/client/<ID> | jq .
+        # Incomplete list of IDs
+        # https://docs.telemetry.mozilla.org/datasets/fxa_metrics/attribution.html#service-attribution  # noqa
         service_choices = (
             (None, '---------'),
-            ('Firefox Lockwise', 'Firefox Lockwise'),
-            ('Firefox Monitor', 'Firefox Monitor'),
-            ('Firefox Send', 'Firefox Send'),
-            ('Firefox Private Network', 'Firefox Private Network'),
-            ('Notes', 'Notes'),
-            ('Pocket', 'Pocket'),
+            ('e7ce535d93522896|98adfa37698f255b', 'Firefox Lockwise'),
+            ('802d56ef2a9af9fa', 'Firefox Monitor'),
+            ('1f30e32975ae5112|20f7931c9054d833', 'Firefox Send'),
+            ('a8c528140153d1c6|565585c1745a144d|e6eb0d1e856335fc', 'Firefox Private Network'),
+            ('7ad9917f6c55fb77', 'Firefox Reality'),
+            ('7377719276ad44ee|749818d3f2e7857f', 'Pocket'),
+
         )
         fields = (
             ChoiceField(choices=check_choices),
@@ -210,14 +215,21 @@
         return ''
 
     def to_jexl(self, value):
-        check, service_name = value.split(',')
-        if not check or not service_name:
+        check, ids = value.split(',')
+        ids = ids.split('|') if ids else ''
+
+        if not check or not ids:
             return ''
 
+        jexl = '('
+        for id in ids:
+            jexl += f'("{id}" in attachedFxAOAuthClients|mapToProperty("id")) || '
+        jexl = jexl[:-4]
+
         if check == 'no_account':
-            jexl = f'("{service_name}" in attachedFxAOAuthClients|mapToProperty("name")) == false'
+            jexl += ') == false'
         elif check == 'has_account':
-            jexl = f'("{service_name}" in attachedFxAOAuthClients|mapToProperty("name")) == true'
+            jexl += ') == true'
 
         return jexl

{"golden_diff": "diff --git a/snippets/base/admin/fields.py b/snippets/base/admin/fields.py\n--- a/snippets/base/admin/fields.py\n+++ b/snippets/base/admin/fields.py\n@@ -188,14 +188,19 @@\n             ('no_account', \"User hasn't signed up for\"),\n             ('has_account', 'User has signed up for'),\n         )\n+        # Verify IDs using\n+        # curl -s https://oauth.stage.mozaws.net/v1/client/<ID> | jq .\n+        # Incomplete list of IDs\n+        # https://docs.telemetry.mozilla.org/datasets/fxa_metrics/attribution.html#service-attribution  # noqa\n         service_choices = (\n             (None, '---------'),\n-            ('Firefox Lockwise', 'Firefox Lockwise'),\n-            ('Firefox Monitor', 'Firefox Monitor'),\n-            ('Firefox Send', 'Firefox Send'),\n-            ('Firefox Private Network', 'Firefox Private Network'),\n-            ('Notes', 'Notes'),\n-            ('Pocket', 'Pocket'),\n+            ('e7ce535d93522896|98adfa37698f255b', 'Firefox Lockwise'),\n+            ('802d56ef2a9af9fa', 'Firefox Monitor'),\n+            ('1f30e32975ae5112|20f7931c9054d833', 'Firefox Send'),\n+            ('a8c528140153d1c6|565585c1745a144d|e6eb0d1e856335fc', 'Firefox Private Network'),\n+            ('7ad9917f6c55fb77', 'Firefox Reality'),\n+            ('7377719276ad44ee|749818d3f2e7857f', 'Pocket'),\n+\n         )\n         fields = (\n             ChoiceField(choices=check_choices),\n@@ -210,14 +215,21 @@\n         return ''\n \n     def to_jexl(self, value):\n-        check, service_name = value.split(',')\n-        if not check or not service_name:\n+        check, ids = value.split(',')\n+        ids = ids.split('|') if ids else ''\n+\n+        if not check or not ids:\n             return ''\n \n+        jexl = '('\n+        for id in ids:\n+            jexl += f'(\"{id}\" in attachedFxAOAuthClients|mapToProperty(\"id\")) || '\n+        jexl = jexl[:-4]\n+\n         if check == 'no_account':\n-            jexl = f'(\"{service_name}\" in attachedFxAOAuthClients|mapToProperty(\"name\")) == false'\n+            jexl += ') == false'\n         elif check == 'has_account':\n-            jexl = f'(\"{service_name}\" in attachedFxAOAuthClients|mapToProperty(\"name\")) == true'\n+            jexl += ') == true'\n \n         return jexl\n", "issue": "Use OAuth client ids instead of names in Firefox App Targeting.\nList of client ids: https://docs.telemetry.mozilla.org/datasets/fxa_metrics/attribution.html#service-attribution\r\n\r\nRelated bug https://bugzilla.mozilla.org/show_bug.cgi?id=1596514#c5\r\n\r\n - [ ] After pushing to prod, generate JEXL for related Targets\r\n - [ ] ping mindy when is complete\r\n\r\n<!-- probot = {\"419912\":{\"who\":\"glogiotatidis\",\"what\":\"\",\"when\":\"2020-01-08T09:00:00.000Z\"}} -->\n", "before_files": [{"content": "from django.core.exceptions import ValidationError\nfrom django.forms import (ChoiceField, ModelChoiceField, ModelMultipleChoiceField,\n                          MultiValueField, MultipleChoiceField)\n\nfrom snippets.base.models import Addon, TargetedCountry\n\nfrom .widgets import JEXLMultiWidget\n\n\nclass MultipleChoiceFieldCSV(MultipleChoiceField):\n    # To be used with in snippets.base.forms.SnippetAdminForm and in\n    # combination with DynamicField. We don't directly save() this field in the\n    # database so get_prep_value has not been implemented.\n\n    def prepare_value(self, value):\n        value = super(MultipleChoiceFieldCSV, self).prepare_value(value)\n        if not isinstance(value, list):\n            value = value.split(';')\n        return value\n\n    def clean(self, value):\n        value = super(MultipleChoiceFieldCSV, self).clean(value)\n        return ';'.join(value)\n\n\nclass JEXLBaseField():\n    def to_jexl(self, value):\n        if value:\n            return self.jexl.format(attr_name=self.attr_name, value=value)\n\n        return None\n\n\nclass JEXLChoiceField(JEXLBaseField, ChoiceField):\n    def __init__(self, attr_name, *args, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = '{attr_name} == {value}'\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        return super().__init__(*args, **kwargs)\n\n    def to_jexl(self, value):\n        if value:\n            return self.jexl.format(attr_name=self.attr_name, value=value)\n\n\nclass JEXLModelMultipleChoiceField(JEXLBaseField, ModelMultipleChoiceField):\n    def __init__(self, attr_name, *args, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = '{attr_name} in {value}'\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        return super().__init__(*args, **kwargs)\n\n    def prepare_value(self, value):\n        if isinstance(value, str):\n            value = value.split(';')\n        return super().prepare_value(value)\n\n    def clean(self, value):\n        value = super().clean(value)\n        return ';'.join([str(x.id) for x in value])\n\n\nclass JEXLCountryField(JEXLModelMultipleChoiceField):\n    def to_jexl(self, value):\n        if value:\n            values = TargetedCountry.objects.filter(id__in=value.split(\";\"))\n            return f'region in {[x.code for x in values]}'\n        return None\n\n\nclass JEXLRangeField(JEXLBaseField, MultiValueField):\n    def __init__(self, attr_name, choices, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = {\n            'minimum': '{value} <= {attr_name}',\n            'maximum': '{attr_name} < {value}'\n        }\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        fields = (\n            ChoiceField(choices=choices),\n            ChoiceField(choices=choices),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields],\n                                      template_name='widgets/jexlrange.html')\n\n    def compress(self, data_list):\n        return ','.join(data_list)\n\n    def to_jexl(self, value):\n        final_jexl = []\n        if value:\n            minimum, maximum = value.split(',')\n            if minimum:\n                final_jexl.append(\n                    self.jexl['minimum'].format(attr_name=self.attr_name, value=minimum)\n                )\n            if maximum:\n                final_jexl.append(\n                    self.jexl['maximum'].format(attr_name=self.attr_name, value=maximum)\n                )\n        return ' && '.join(final_jexl)\n\n    def validate(self, value):\n        minimum, maximum = value.split(',')\n        self.fields[0].validate(minimum)\n        self.fields[1].validate(maximum)\n\n        if minimum and maximum and int(minimum) > int(maximum):\n            raise ValidationError('Minimum value must be lower or equal to maximum value.')\n        return value\n\n\nclass JEXLFirefoxRangeField(JEXLRangeField):\n    def __init__(self, **kwargs):\n        # Include only versions greater than 63, where ASRSnippets exist.\n        min_version = 64\n        # Need to be able to dynamically change this, probably using\n        # product_details. Issue #855\n        max_version = 84\n\n        choices = (\n            [(None, 'No limit')] +\n            [(x, x) for x in reversed(range(min_version, max_version + 1))]\n        )\n        super().__init__('firefoxVersion', choices, **kwargs)\n\n    def validate(self, value):\n        minimum, maximum = value.split(',')\n        self.fields[0].validate(minimum)\n        self.fields[1].validate(maximum)\n\n        if minimum and maximum and minimum > maximum:\n            raise ValidationError('Minimum value must be lower or equal to maximum value.')\n        return value\n\n\nclass JEXLAddonField(MultiValueField):\n    def __init__(self, **kwargs):\n        choices = (\n            (None, \"I don't care\"),\n            ('not_installed', 'Not Installed'),\n            ('installed', 'Installed'),\n        )\n        fields = (\n            ChoiceField(choices=choices),\n            ModelChoiceField(queryset=Addon.objects.all(), required=False),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])\n\n    def compress(self, data_list):\n        if data_list:\n            return '{},{}'.format(data_list[0], getattr(data_list[1], 'id', ''))\n        return ''\n\n    def to_jexl(self, value):\n        check, addon_id = value.split(',')\n        if not check or not addon_id:\n            return ''\n\n        addon = Addon.objects.get(id=addon_id)\n        if check == 'not_installed':\n            jexl = '(\"{}\" in addonsInfo.addons|keys) == false'.format(addon.guid)\n        elif check == 'installed':\n            jexl = '(\"{}\" in addonsInfo.addons|keys) == true'.format(addon.guid)\n\n        return jexl\n\n    def validate(self, value):\n        check, addon_id = value.split(',')\n\n        self.fields[0].validate(check)\n        self.fields[1].validate(addon_id)\n\n        if check and not addon_id:\n            raise ValidationError('You must select an add-on')\n\n        if not check and addon_id:\n            raise ValidationError('You must select a check')\n        return value\n\n\nclass JEXLFirefoxServicesField(MultiValueField):\n    def __init__(self, **kwargs):\n        check_choices = (\n            (None, \"I don't care\"),\n            ('no_account', \"User hasn't signed up for\"),\n            ('has_account', 'User has signed up for'),\n        )\n        service_choices = (\n            (None, '---------'),\n            ('Firefox Lockwise', 'Firefox Lockwise'),\n            ('Firefox Monitor', 'Firefox Monitor'),\n            ('Firefox Send', 'Firefox Send'),\n            ('Firefox Private Network', 'Firefox Private Network'),\n            ('Notes', 'Notes'),\n            ('Pocket', 'Pocket'),\n        )\n        fields = (\n            ChoiceField(choices=check_choices),\n            ChoiceField(choices=service_choices),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])\n\n    def compress(self, data_list):\n        if data_list:\n            return f'{data_list[0]},{data_list[1]}'\n        return ''\n\n    def to_jexl(self, value):\n        check, service_name = value.split(',')\n        if not check or not service_name:\n            return ''\n\n        if check == 'no_account':\n            jexl = f'(\"{service_name}\" in attachedFxAOAuthClients|mapToProperty(\"name\")) == false'\n        elif check == 'has_account':\n            jexl = f'(\"{service_name}\" in attachedFxAOAuthClients|mapToProperty(\"name\")) == true'\n\n        return jexl\n\n    def validate(self, value):\n        check, service_name = value.split(',')\n\n        self.fields[0].validate(check)\n        self.fields[1].validate(service_name)\n\n        if check and not service_name:\n            raise ValidationError('You must select an Service.')\n\n        if not check and service_name:\n            raise ValidationError('You must select a check.')\n        return value\n", "path": "snippets/base/admin/fields.py"}], "after_files": [{"content": "from django.core.exceptions import ValidationError\nfrom django.forms import (ChoiceField, ModelChoiceField, ModelMultipleChoiceField,\n                          MultiValueField, MultipleChoiceField)\n\nfrom snippets.base.models import Addon, TargetedCountry\n\nfrom .widgets import JEXLMultiWidget\n\n\nclass MultipleChoiceFieldCSV(MultipleChoiceField):\n    # To be used with in snippets.base.forms.SnippetAdminForm and in\n    # combination with DynamicField. We don't directly save() this field in the\n    # database so get_prep_value has not been implemented.\n\n    def prepare_value(self, value):\n        value = super(MultipleChoiceFieldCSV, self).prepare_value(value)\n        if not isinstance(value, list):\n            value = value.split(';')\n        return value\n\n    def clean(self, value):\n        value = super(MultipleChoiceFieldCSV, self).clean(value)\n        return ';'.join(value)\n\n\nclass JEXLBaseField():\n    def to_jexl(self, value):\n        if value:\n            return self.jexl.format(attr_name=self.attr_name, value=value)\n\n        return None\n\n\nclass JEXLChoiceField(JEXLBaseField, ChoiceField):\n    def __init__(self, attr_name, *args, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = '{attr_name} == {value}'\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        return super().__init__(*args, **kwargs)\n\n    def to_jexl(self, value):\n        if value:\n            return self.jexl.format(attr_name=self.attr_name, value=value)\n\n\nclass JEXLModelMultipleChoiceField(JEXLBaseField, ModelMultipleChoiceField):\n    def __init__(self, attr_name, *args, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = '{attr_name} in {value}'\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        return super().__init__(*args, **kwargs)\n\n    def prepare_value(self, value):\n        if isinstance(value, str):\n            value = value.split(';')\n        return super().prepare_value(value)\n\n    def clean(self, value):\n        value = super().clean(value)\n        return ';'.join([str(x.id) for x in value])\n\n\nclass JEXLCountryField(JEXLModelMultipleChoiceField):\n    def to_jexl(self, value):\n        if value:\n            values = TargetedCountry.objects.filter(id__in=value.split(\";\"))\n            return f'region in {[x.code for x in values]}'\n        return None\n\n\nclass JEXLRangeField(JEXLBaseField, MultiValueField):\n    def __init__(self, attr_name, choices, **kwargs):\n        self.attr_name = attr_name\n        self.jexl = {\n            'minimum': '{value} <= {attr_name}',\n            'maximum': '{attr_name} < {value}'\n        }\n        self.jexl = kwargs.pop('jexl', self.jexl)\n        fields = (\n            ChoiceField(choices=choices),\n            ChoiceField(choices=choices),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields],\n                                      template_name='widgets/jexlrange.html')\n\n    def compress(self, data_list):\n        return ','.join(data_list)\n\n    def to_jexl(self, value):\n        final_jexl = []\n        if value:\n            minimum, maximum = value.split(',')\n            if minimum:\n                final_jexl.append(\n                    self.jexl['minimum'].format(attr_name=self.attr_name, value=minimum)\n                )\n            if maximum:\n                final_jexl.append(\n                    self.jexl['maximum'].format(attr_name=self.attr_name, value=maximum)\n                )\n        return ' && '.join(final_jexl)\n\n    def validate(self, value):\n        minimum, maximum = value.split(',')\n        self.fields[0].validate(minimum)\n        self.fields[1].validate(maximum)\n\n        if minimum and maximum and int(minimum) > int(maximum):\n            raise ValidationError('Minimum value must be lower or equal to maximum value.')\n        return value\n\n\nclass JEXLFirefoxRangeField(JEXLRangeField):\n    def __init__(self, **kwargs):\n        # Include only versions greater than 63, where ASRSnippets exist.\n        min_version = 64\n        # Need to be able to dynamically change this, probably using\n        # product_details. Issue #855\n        max_version = 84\n\n        choices = (\n            [(None, 'No limit')] +\n            [(x, x) for x in reversed(range(min_version, max_version + 1))]\n        )\n        super().__init__('firefoxVersion', choices, **kwargs)\n\n    def validate(self, value):\n        minimum, maximum = value.split(',')\n        self.fields[0].validate(minimum)\n        self.fields[1].validate(maximum)\n\n        if minimum and maximum and minimum > maximum:\n            raise ValidationError('Minimum value must be lower or equal to maximum value.')\n        return value\n\n\nclass JEXLAddonField(MultiValueField):\n    def __init__(self, **kwargs):\n        choices = (\n            (None, \"I don't care\"),\n            ('not_installed', 'Not Installed'),\n            ('installed', 'Installed'),\n        )\n        fields = (\n            ChoiceField(choices=choices),\n            ModelChoiceField(queryset=Addon.objects.all(), required=False),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])\n\n    def compress(self, data_list):\n        if data_list:\n            return '{},{}'.format(data_list[0], getattr(data_list[1], 'id', ''))\n        return ''\n\n    def to_jexl(self, value):\n        check, addon_id = value.split(',')\n        if not check or not addon_id:\n            return ''\n\n        addon = Addon.objects.get(id=addon_id)\n        if check == 'not_installed':\n            jexl = '(\"{}\" in addonsInfo.addons|keys) == false'.format(addon.guid)\n        elif check == 'installed':\n            jexl = '(\"{}\" in addonsInfo.addons|keys) == true'.format(addon.guid)\n\n        return jexl\n\n    def validate(self, value):\n        check, addon_id = value.split(',')\n\n        self.fields[0].validate(check)\n        self.fields[1].validate(addon_id)\n\n        if check and not addon_id:\n            raise ValidationError('You must select an add-on')\n\n        if not check and addon_id:\n            raise ValidationError('You must select a check')\n        return value\n\n\nclass JEXLFirefoxServicesField(MultiValueField):\n    def __init__(self, **kwargs):\n        check_choices = (\n            (None, \"I don't care\"),\n            ('no_account', \"User hasn't signed up for\"),\n            ('has_account', 'User has signed up for'),\n        )\n        # Verify IDs using\n        # curl -s https://oauth.stage.mozaws.net/v1/client/<ID> | jq .\n        # Incomplete list of IDs\n        # https://docs.telemetry.mozilla.org/datasets/fxa_metrics/attribution.html#service-attribution  # noqa\n        service_choices = (\n            (None, '---------'),\n            ('e7ce535d93522896|98adfa37698f255b', 'Firefox Lockwise'),\n            ('802d56ef2a9af9fa', 'Firefox Monitor'),\n            ('1f30e32975ae5112|20f7931c9054d833', 'Firefox Send'),\n            ('a8c528140153d1c6|565585c1745a144d|e6eb0d1e856335fc', 'Firefox Private Network'),\n            ('7ad9917f6c55fb77', 'Firefox Reality'),\n            ('7377719276ad44ee|749818d3f2e7857f', 'Pocket'),\n\n        )\n        fields = (\n            ChoiceField(choices=check_choices),\n            ChoiceField(choices=service_choices),\n        )\n        super().__init__(fields, **kwargs)\n        self.widget = JEXLMultiWidget(widgets=[f.widget for f in self.fields])\n\n    def compress(self, data_list):\n        if data_list:\n            return f'{data_list[0]},{data_list[1]}'\n        return ''\n\n    def to_jexl(self, value):\n        check, ids = value.split(',')\n        ids = ids.split('|') if ids else ''\n\n        if not check or not ids:\n            return ''\n\n        jexl = '('\n        for id in ids:\n            jexl += f'(\"{id}\" in attachedFxAOAuthClients|mapToProperty(\"id\")) || '\n        jexl = jexl[:-4]\n\n        if check == 'no_account':\n            jexl += ') == false'\n        elif check == 'has_account':\n            jexl += ') == true'\n\n        return jexl\n\n    def validate(self, value):\n        check, service_name = value.split(',')\n\n        self.fields[0].validate(check)\n        self.fields[1].validate(service_name)\n\n        if check and not service_name:\n            raise ValidationError('You must select an Service.')\n\n        if not check and service_name:\n            raise ValidationError('You must select a check.')\n        return value\n", "path": "snippets/base/admin/fields.py"}]}