• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chiefonboarding / ChiefOnboarding / 16129945001

07 Jul 2025 11:17PM UTC coverage: 90.649% (+0.01%) from 90.638%
16129945001

push

github

web-flow
Add patch method to execute and revoke integration (#559)

8269 of 9122 relevant lines covered (90.65%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.24
back/admin/integrations/builder_forms.py
1
from crispy_forms.helper import FormHelper
1✔
2
from crispy_forms.layout import Div, Field, Layout
1✔
3
from django import forms
1✔
4
from django.contrib.postgres.forms import SimpleArrayField
1✔
5
from django.utils.translation import gettext_lazy as _
1✔
6

7
from admin.integrations.utils import (
1✔
8
    convert_array_to_object,
9
    prepare_initial_data,
10
)
11
from admin.integrations.validators import (
1✔
12
    validate_continue_if,
13
    validate_ID,
14
    validate_polling,
15
    validate_status_code,
16
)
17
from admin.templates.forms import FieldWithExtraContext
1✔
18

19

20
class JSONToDict(forms.JSONField):
1✔
21
    def clean(self, value):
1✔
22
        value = super().clean(value)
×
23
        if isinstance(value, list):
×
24
            value = convert_array_to_object(value)
×
25
        return value
×
26

27

28
class ValueKeyArrayField(FieldWithExtraContext):
1✔
29
    template = "value_key_array_field.html"
1✔
30

31

32
class IntegerListField(FieldWithExtraContext):
1✔
33
    template = "manifest_test/integer_list_field.html"
1✔
34

35

36
class ManifestFormForm(forms.Form):
1✔
37
    id = forms.CharField(
1✔
38
        label="ID",
39
        help_text=_(
40
            "This value can be used in the other calls. Please do not use spaces or weird characters. A single word in capitals is prefered."
41
        ),
42
        validators=[validate_ID],
43
    )
44
    name = forms.CharField(
1✔
45
        label="Name", help_text=_("The form label shown to the admin")
46
    )
47
    type = forms.ChoiceField(
1✔
48
        choices=(("choice", "choice"), ("input", "input")),
49
        label="Type",
50
        help_text=_(
51
            "If you choose choice, you will be able to set the options yourself OR fetch from an external url."
52
        ),
53
    )
54
    options_source = forms.ChoiceField(
1✔
55
        choices=(("fixed list", "fixed list"), ("fetch url", "fetch url")),
56
        initial="fixed list",
57
    )
58

59
    # fixed items
60
    items = forms.JSONField(
1✔
61
        initial=list,
62
        help_text=_(
63
            "Use only if you set type to 'choice'. This is for fixed items (if you don't want to fetch from a URL)"
64
        ),
65
        required=False,
66
    )
67

68
    # dynamic choices
69
    url = forms.URLField(
1✔
70
        max_length=255,
71
        help_text=_("The url it should fetch the options from."),
72
        required=False,
73
    )
74
    method = forms.ChoiceField(
1✔
75
        choices=(
76
            ("GET", "GET"),
77
            ("POST", "POST"),
78
            ("PUT", "PUT"),
79
            ("DELETE", "DELETE"),
80
        ),
81
        initial="GET",
82
        label=_("Request method"),
83
    )
84
    data = forms.JSONField(initial=dict, required=False)
1✔
85
    cast_data_to_json = forms.BooleanField(
1✔
86
        initial=True,
87
        help_text=_(
88
            "Check this if the data should be send as json. When unchecked, it's send as a string."
89
        ),
90
        required=False,
91
    )
92
    headers = JSONToDict(
1✔
93
        initial=list,
94
        help_text=_("(optionally) This will overwrite the default headers."),
95
        required=False,
96
    )
97
    data_from = forms.CharField(
1✔
98
        max_length=255,
99
        initial="",
100
        help_text=_(
101
            "The property it should use from the response of the url if you need to go deeper into the result."
102
        ),
103
        required=False,
104
    )
105
    choice_value = forms.CharField(
1✔
106
        max_length=255,
107
        initial="id",
108
        help_text=_(
109
            "The value it should take for using in other parts of the integration"
110
        ),
111
        required=False,
112
    )
113
    choice_name = forms.CharField(
1✔
114
        max_length=255,
115
        initial="name",
116
        help_text=_("The name that should be displayed to the admin as an option."),
117
        required=False,
118
    )
119

120
    def __init__(self, disabled=False, *args, **kwargs):
1✔
121
        super().__init__(*args, **kwargs)
×
122
        self.helper = FormHelper()
×
123
        self.helper.form_tag = False
×
124
        self.initial = prepare_initial_data(self.initial)
×
125

126
        if disabled:
×
127
            for field in self.fields:
×
128
                self.fields[field].disabled = True
×
129

130
        show_manual_items = "d-none"
×
131
        show_fetch_url = ""
×
132
        show_choice_options = ""
×
133
        if self.initial.get("options_source", "fixed list") == "fixed list":
×
134
            show_manual_items = ""
×
135
            show_fetch_url = "d-none"
×
136

137
        if self.initial.get("type", "") == "input":
×
138
            show_choice_options = "d-none"
×
139

140
        self.helper.layout = Layout(
×
141
            Div(
142
                Div(Field("id"), css_class="col-6"),
143
                Div(Field("name"), css_class="col-6"),
144
                css_class="row",
145
            ),
146
            Div(
147
                Div(Field("type"), css_class="col-6"),
148
                Div(Field("options_source"), css_class=f"col-6 {show_choice_options}"),
149
                css_class="row",
150
            ),
151
            Div(
152
                ValueKeyArrayField("items", extra_context={"disabled": disabled}),
153
                css_class=f"manual_items {show_manual_items} {show_choice_options}",
154
            ),
155
            Div(
156
                Div(
157
                    Div(Field("method"), css_class="col-3"),
158
                    Div(Field("url"), css_class="col-9"),
159
                    css_class="row",
160
                ),
161
                Div(Field("data_from")),
162
                Div(Field("data")),
163
                Div(Field("cast_data_to_json")),
164
                Div(
165
                    Div(Field("choice_value"), css_class="col-6"),
166
                    Div(Field("choice_name"), css_class="col-6"),
167
                    css_class="row",
168
                ),
169
                ValueKeyArrayField("headers", extra_context={"disabled": disabled}),
170
                css_class=f"fetch_items {show_fetch_url} {show_choice_options}",
171
            ),
172
        )
173

174

175
class ManifestRevokeForm(forms.Form):
1✔
176
    url = forms.URLField(
1✔
177
        max_length=255,
178
        help_text=_("The url it should fetch the options from."),
179
        required=False,
180
    )
181
    method = forms.ChoiceField(
1✔
182
        choices=(
183
            ("GET", "GET"),
184
            ("POST", "POST"),
185
            ("PUT", "PUT"),
186
            ("PATCH", "PATCH"),
187
            ("DELETE", "DELETE"),
188
        ),
189
        initial="GET",
190
        label=_("Request method"),
191
        required=False,
192
    )
193
    data = forms.JSONField(initial=dict, required=False)
1✔
194
    cast_data_to_json = forms.BooleanField(
1✔
195
        initial=True,
196
        help_text=_(
197
            "Check this if the data should be send as json. When unchecked, it's send as a string."
198
        ),
199
        required=False,
200
    )
201
    expected = forms.CharField(initial="", required=False)
1✔
202
    status_code = SimpleArrayField(
1✔
203
        forms.CharField(max_length=1000), required=False, initial=list
204
    )
205
    headers = JSONToDict(
1✔
206
        initial=list,
207
        help_text=_("(optionally) This will overwrite the default headers."),
208
        required=False,
209
    )
210

211
    def __init__(self, disabled=False, *args, **kwargs):
1✔
212
        super().__init__(*args, **kwargs)
×
213
        self.helper = FormHelper()
×
214
        self.helper.form_tag = False
×
215
        self.initial = prepare_initial_data(self.initial)
×
216

217
        if disabled:
×
218
            for field in self.fields:
×
219
                self.fields[field].disabled = True
×
220

221
        self.helper.layout = Layout(
×
222
            Div(
223
                Div(
224
                    Div(Field("method"), css_class="col-3"),
225
                    Div(Field("url"), css_class="col-9"),
226
                    css_class="row",
227
                ),
228
                Div(Field("data")),
229
                Div(Field("cast_data_to_json")),
230
                Div(Field("expected")),
231
                IntegerListField("status_code", extra_context={"disabled": disabled}),
232
                ValueKeyArrayField("headers", extra_context={"disabled": disabled}),
233
            )
234
        )
235

236

237
class ManifestHeadersForm(forms.Form):
1✔
238
    headers = forms.JSONField(
1✔
239
        initial=list,
240
        help_text=_("(optionally) This will overwrite the default headers."),
241
        required=False,
242
    )
243

244
    def __init__(self, *args, **kwargs):
1✔
245
        super().__init__(*args, **kwargs)
×
246
        self.helper = FormHelper()
×
247
        self.helper.form_tag = False
×
248

249
        self.helper.layout = Layout(ValueKeyArrayField("headers"))
×
250

251

252
class ManifestOauthForm(forms.Form):
1✔
253
    oauth = forms.JSONField(
1✔
254
        initial=list, help_text=_("OAuth settings"), required=False, label="OAuth"
255
    )
256

257
    def __init__(self, *args, **kwargs):
1✔
258
        super().__init__(*args, **kwargs)
×
259
        self.helper = FormHelper()
×
260
        self.helper.form_tag = False
×
261

262

263
class ManifestExtractDataForm(forms.Form):
1✔
264
    action = forms.ChoiceField(
1✔
265
        choices=(("create", "create"), ("sync", "sync")),
266
        initial="create",
267
    )
268
    data_from = forms.CharField(
1✔
269
        max_length=255,
270
        initial="",
271
        help_text=_(
272
            "The property it should use from the response of the url if you need to go deeper into the result."
273
        ),
274
        required=False,
275
    )
276
    data_structure = forms.JSONField(
1✔
277
        initial=dict,
278
        help_text=_("How to map the data to the user"),
279
        required=True,
280
        label="Data structure",
281
    )
282
    schedule = forms.CharField(
1✔
283
        max_length=255,
284
        initial="",
285
        help_text=_("cron type schedule for running this in the background"),
286
        required=False,
287
    )
288
    amount_pages_to_fetch = forms.IntegerField(
1✔
289
        initial=5,
290
        label=_("Paginated response: amount pages to fetch"),
291
        help_text=_(
292
            "Maximum amount of page to fetch. It will stop earlier if there are no users found anymore."
293
        ),
294
        required=False,
295
    )
296
    next_page_token_from = forms.CharField(
1✔
297
        max_length=255,
298
        label=_("Paginated response: Next page token from"),
299
        initial="",
300
        help_text=_(
301
            "The place to look for the next page token. You can use the dot notation to do go deeper into the JSON. If it's not found, it will stop."
302
        ),
303
        required=False,
304
    )
305
    next_page = forms.CharField(
1✔
306
        max_length=255,
307
        label=_("Paginated response: Next page url"),
308
        initial="",
309
        help_text=_(
310
            "A fixed url that will be used to fetch the new result in combination with the 'Next page token from'"
311
        ),
312
        required=False,
313
    )
314
    next_page_from = forms.CharField(
1✔
315
        max_length=255,
316
        label=_("Paginated response: Next page from"),
317
        initial="",
318
        help_text=_(
319
            "The place to look for the next page url (if not using 'next_page' and 'next_page_token_from'. You can use the dot notation to do go deeper into the JSON. If it's not found, it will stop."
320
        ),
321
        required=False,
322
    )
323

324
    def __init__(self, *args, **kwargs):
1✔
325
        super().__init__(*args, **kwargs)
×
326
        self.helper = FormHelper()
×
327
        self.helper.form_tag = False
×
328

329

330
class ManifestExistsForm(forms.Form):
1✔
331
    url = forms.URLField(
1✔
332
        max_length=255, help_text=_("The url it should check"), required=False
333
    )
334
    method = forms.ChoiceField(
1✔
335
        choices=(
336
            ("GET", "GET"),
337
            ("POST", "POST"),
338
            ("PUT", "PUT"),
339
            ("DELETE", "DELETE"),
340
        ),
341
        initial="GET",
342
        label=_("Request method"),
343
        required=False,
344
    )
345
    expected = forms.CharField(initial="", required=False)
1✔
346
    status_code = SimpleArrayField(
1✔
347
        forms.CharField(max_length=1000),
348
        required=False,
349
        initial=[],
350
        validators=[validate_status_code],
351
    )
352
    headers = forms.JSONField(
1✔
353
        initial=list,
354
        help_text=_("(optionally) This will overwrite the default headers."),
355
        required=False,
356
    )
357

358
    def __init__(self, *args, **kwargs):
1✔
359
        super().__init__(*args, **kwargs)
×
360
        self.helper = FormHelper()
×
361
        self.helper.form_tag = False
×
362
        self.initial = prepare_initial_data(self.initial)
×
363

364
        self.helper.layout = Layout(
×
365
            Div(
366
                Div(Field("method"), css_class="col-3"),
367
                Div(Field("url"), css_class="col-9"),
368
                css_class="row",
369
            ),
370
            Div(
371
                Field("expected"),
372
            ),
373
            Div(
374
                IntegerListField("status_code"),
375
            ),
376
            ValueKeyArrayField("headers"),
377
        )
378

379

380
class ManifestInitialDataForm(forms.Form):
1✔
381
    id = forms.CharField(
1✔
382
        max_length=100,
383
        help_text=_(
384
            "This value can be used in the other calls. Please do not use spaces or weird characters. A single word in capitals is prefered."
385
        ),
386
        validators=[validate_ID],
387
    )
388
    name = forms.CharField(
1✔
389
        max_length=255,
390
        help_text=_(
391
            "Type 'generate' if you want this value to be generated on the fly (different for each execution), will not need to be filled by a user"
392
        ),
393
    )
394
    description = forms.CharField(
1✔
395
        max_length=1255,
396
        help_text=_("This will be shown under the input field for extra context"),
397
        required=False,
398
    )
399
    secret = forms.BooleanField(
1✔
400
        initial=False,
401
        help_text="Enable this if the value should always be masked",
402
        required=False,
403
    )
404

405
    def __init__(self, disabled=False, *args, **kwargs):
1✔
406
        super().__init__(*args, **kwargs)
×
407
        self.helper = FormHelper()
×
408
        self.helper.form_tag = False
×
409

410
        if disabled:
×
411
            for field in self.fields:
×
412
                self.fields[field].disabled = True
×
413

414
        self.helper.layout = Layout(
×
415
            Div(
416
                Div(Field("id"), css_class="col-9"),
417
                Div(Field("secret"), css_class="col-3"),
418
                css_class="row",
419
            ),
420
            Div(
421
                Field("name"),
422
            ),
423
            Div(
424
                Field("description"),
425
            ),
426
        )
427

428

429
class ManifestUserInfoForm(forms.Form):
1✔
430
    id = forms.CharField(
1✔
431
        max_length=100,
432
        help_text=_(
433
            "This value can be used in the other calls. Please do not use spaces or weird characters. A single word in capitals is prefered."
434
        ),
435
        validators=[validate_ID],
436
    )
437
    name = forms.CharField(max_length=255)
1✔
438
    description = forms.CharField(
1✔
439
        max_length=1255,
440
        help_text=_("This will be shown under the input field for extra context"),
441
    )
442

443
    def __init__(self, disabled=False, *args, **kwargs):
1✔
444
        super().__init__(*args, **kwargs)
×
445
        self.helper = FormHelper()
×
446
        self.helper.form_tag = False
×
447

448
        if disabled:
×
449
            for field in self.fields:
×
450
                self.fields[field].disabled = True
×
451

452
        self.helper.layout = Layout(
×
453
            Div(
454
                Field("id"),
455
            ),
456
            Div(
457
                Field("name"),
458
            ),
459
            Div(
460
                Field("description"),
461
            ),
462
        )
463

464

465
class ManifestExecuteForm(forms.Form):
1✔
466
    url = forms.URLField(
1✔
467
        max_length=255, help_text=_("The url it should trigger"), required=False
468
    )
469
    method = forms.ChoiceField(
1✔
470
        choices=(
471
            ("GET", "GET"),
472
            ("POST", "POST"),
473
            ("PUT", "PUT"),
474
            ("PATCH", "PATCH"),
475
            ("DELETE", "DELETE"),
476
        ),
477
        initial="GET",
478
        label=_("Request method"),
479
        required=False,
480
    )
481
    status_code = SimpleArrayField(
1✔
482
        forms.CharField(max_length=1000), required=False, initial=[]
483
    )
484
    cast_data_to_json = forms.BooleanField(
1✔
485
        initial=True,
486
        help_text=_(
487
            "Check this if the data should be send as json. When unchecked, it's send as a string."
488
        ),
489
        required=False,
490
    )
491
    headers = forms.JSONField(
1✔
492
        initial=list,
493
        help_text=_("(optionally) This will overwrite the default headers."),
494
        required=False,
495
    )
496
    data = forms.JSONField(initial=dict, required=False)
1✔
497
    store_data = forms.JSONField(
1✔
498
        initial=dict,
499
        help_text=_(
500
            "(optionally) if you want to store data that's the request returns, then you can do that here."
501
        ),
502
        required=False,
503
    )
504
    continue_if = forms.JSONField(
1✔
505
        initial=dict,
506
        help_text=_("(optionally) set up a condition to block any further requests"),
507
        required=False,
508
        validators=[validate_continue_if],
509
    )
510
    polling = forms.JSONField(
1✔
511
        initial=dict,
512
        help_text=_(
513
            "(optionally) rerun this request a specific amount of times until it passes"
514
        ),
515
        required=False,
516
        validators=[validate_polling],
517
    )
518
    save_as_file = forms.CharField(
1✔
519
        initial="",
520
        help_text=_(
521
            "(optionally) if this request returns a file, then you can save it to use later"
522
        ),
523
        required=False,
524
    )
525
    files = forms.JSONField(
1✔
526
        initial=dict,
527
        help_text=_(
528
            "(optionally) if you want to use any of the previous files to submit"
529
        ),
530
        required=False,
531
    )
532

533
    class Meta:
1✔
534
        fields = (
1✔
535
            "url",
536
            "method",
537
            "data",
538
            "headers",
539
            "store_data",
540
            "continue_if",
541
            "polling",
542
            "save_as_file",
543
            "files",
544
        )
545

546
    def __init__(self, disabled=False, *args, **kwargs):
1✔
547
        super().__init__(*args, **kwargs)
×
548
        self.helper = FormHelper()
×
549
        self.helper.form_tag = False
×
550
        self.initial = prepare_initial_data(self.initial)
×
551

552
        if disabled:
×
553
            for field in self.fields:
×
554
                self.fields[field].disabled = True
×
555

556
        self.helper.layout = Layout(
×
557
            Div(
558
                Div(
559
                    Div(Field("method"), css_class="col-3"),
560
                    Div(Field("url"), css_class="col-9"),
561
                    css_class="row",
562
                ),
563
                Div(Field("data")),
564
                Div(Field("cast_data_to_json")),
565
                Div(Field("store_data")),
566
                Div(Field("continue_if")),
567
                Div(Field("polling")),
568
                Div(Field("save_as_file")),
569
                Div(Field("files")),
570
                ValueKeyArrayField("headers", extra_context={"disabled": disabled}),
571
            )
572
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc