django-forms-workflows 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_forms_workflows/__init__.py +11 -0
- django_forms_workflows/admin.py +434 -0
- django_forms_workflows/apps.py +22 -0
- django_forms_workflows/data_sources/__init__.py +79 -0
- django_forms_workflows/data_sources/base.py +119 -0
- django_forms_workflows/data_sources/database_source.py +242 -0
- django_forms_workflows/data_sources/ldap_source.py +147 -0
- django_forms_workflows/data_sources/user_source.py +73 -0
- django_forms_workflows/forms.py +359 -0
- django_forms_workflows/handlers/__init__.py +20 -0
- django_forms_workflows/handlers/api_handler.py +205 -0
- django_forms_workflows/handlers/base.py +90 -0
- django_forms_workflows/handlers/database_handler.py +201 -0
- django_forms_workflows/handlers/executor.py +272 -0
- django_forms_workflows/handlers/ldap_handler.py +228 -0
- django_forms_workflows/ldap_backend.py +424 -0
- django_forms_workflows/management/commands/seed_farm_demo.py +377 -0
- django_forms_workflows/management/commands/seed_prefill_sources.py +162 -0
- django_forms_workflows/migrations/0001_initial.py +755 -0
- django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +185 -0
- django_forms_workflows/migrations/0003_postsubmissionaction.py +252 -0
- django_forms_workflows/migrations/__init__.py +0 -0
- django_forms_workflows/models.py +1025 -0
- django_forms_workflows/tasks.py +263 -0
- django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +52 -0
- django_forms_workflows/templates/django_forms_workflows/approve.html +74 -0
- django_forms_workflows/templates/django_forms_workflows/base.html +119 -0
- django_forms_workflows/templates/django_forms_workflows/form_list.html +38 -0
- django_forms_workflows/templates/django_forms_workflows/form_submit.html +34 -0
- django_forms_workflows/templates/django_forms_workflows/my_submissions.html +80 -0
- django_forms_workflows/templates/django_forms_workflows/submission_detail.html +148 -0
- django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +38 -0
- django_forms_workflows/templates/emails/approval_notification.html +29 -0
- django_forms_workflows/templates/emails/approval_reminder.html +29 -0
- django_forms_workflows/templates/emails/approval_request.html +42 -0
- django_forms_workflows/templates/emails/email_styles.html +10 -0
- django_forms_workflows/templates/emails/escalation_notification.html +28 -0
- django_forms_workflows/templates/emails/rejection_notification.html +28 -0
- django_forms_workflows/templates/emails/submission_notification.html +29 -0
- django_forms_workflows/templates/registration/login.html +63 -0
- django_forms_workflows/urls.py +20 -0
- django_forms_workflows/utils.py +46 -0
- django_forms_workflows/views.py +358 -0
- django_forms_workflows/workflow_engine.py +290 -0
- django_forms_workflows-0.2.0.dist-info/LICENSE +165 -0
- django_forms_workflows-0.2.0.dist-info/METADATA +331 -0
- django_forms_workflows-0.2.0.dist-info/RECORD +48 -0
- django_forms_workflows-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Forms Workflows
|
|
3
|
+
Enterprise-grade, database-driven form builder with approval workflows
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = '0.2.0'
|
|
7
|
+
__author__ = 'Django Forms Workflows Contributors'
|
|
8
|
+
__license__ = 'LGPL-3.0-only'
|
|
9
|
+
|
|
10
|
+
default_app_config = 'django_forms_workflows.apps.DjangoFormsWorkflowsConfig'
|
|
11
|
+
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django admin for Django Forms Workflows
|
|
3
|
+
|
|
4
|
+
Provides a friendly admin interface to build forms (with fields),
|
|
5
|
+
configure approval workflows, and review submissions and audit logs.
|
|
6
|
+
"""
|
|
7
|
+
from django.contrib import admin
|
|
8
|
+
from django.utils.html import format_html
|
|
9
|
+
from .models import (
|
|
10
|
+
FormDefinition,
|
|
11
|
+
FormField,
|
|
12
|
+
PrefillSource,
|
|
13
|
+
PostSubmissionAction,
|
|
14
|
+
WorkflowDefinition,
|
|
15
|
+
FormSubmission,
|
|
16
|
+
ApprovalTask,
|
|
17
|
+
AuditLog,
|
|
18
|
+
UserProfile,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Inline for form fields when editing a form definition
|
|
23
|
+
class FormFieldInline(admin.StackedInline):
|
|
24
|
+
model = FormField
|
|
25
|
+
extra = 0
|
|
26
|
+
ordering = ("order",)
|
|
27
|
+
fk_name = "form_definition"
|
|
28
|
+
fieldsets = (
|
|
29
|
+
(
|
|
30
|
+
None,
|
|
31
|
+
{
|
|
32
|
+
"fields": (
|
|
33
|
+
("order", "field_label", "field_name", "field_type", "required"),
|
|
34
|
+
("help_text", "placeholder", "width", "css_class"),
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
(
|
|
39
|
+
"Validation",
|
|
40
|
+
{
|
|
41
|
+
"classes": ("collapse",),
|
|
42
|
+
"fields": (
|
|
43
|
+
("min_value", "max_value"),
|
|
44
|
+
("min_length", "max_length"),
|
|
45
|
+
"regex_validation",
|
|
46
|
+
"regex_error_message",
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
(
|
|
51
|
+
"Choices & Defaults",
|
|
52
|
+
{
|
|
53
|
+
"classes": ("collapse",),
|
|
54
|
+
"fields": ("choices", "prefill_source_config", "prefill_source", "default_value"),
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
"Conditional display",
|
|
59
|
+
{
|
|
60
|
+
"classes": ("collapse",),
|
|
61
|
+
"fields": (("show_if_field", "show_if_value"),),
|
|
62
|
+
},
|
|
63
|
+
),
|
|
64
|
+
(
|
|
65
|
+
"File upload",
|
|
66
|
+
{
|
|
67
|
+
"classes": ("collapse",),
|
|
68
|
+
"fields": ("allowed_extensions", "max_file_size_mb"),
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@admin.register(PrefillSource)
|
|
75
|
+
class PrefillSourceAdmin(admin.ModelAdmin):
|
|
76
|
+
list_display = (
|
|
77
|
+
"name",
|
|
78
|
+
"source_type",
|
|
79
|
+
"source_key",
|
|
80
|
+
"is_active",
|
|
81
|
+
"order",
|
|
82
|
+
)
|
|
83
|
+
list_filter = ("source_type", "is_active")
|
|
84
|
+
search_fields = ("name", "source_key", "description")
|
|
85
|
+
list_editable = ("order", "is_active")
|
|
86
|
+
fieldsets = (
|
|
87
|
+
(
|
|
88
|
+
None,
|
|
89
|
+
{
|
|
90
|
+
"fields": (
|
|
91
|
+
("name", "source_type"),
|
|
92
|
+
"source_key",
|
|
93
|
+
"description",
|
|
94
|
+
("is_active", "order"),
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
),
|
|
98
|
+
(
|
|
99
|
+
"Database Configuration",
|
|
100
|
+
{
|
|
101
|
+
"classes": ("collapse",),
|
|
102
|
+
"fields": (
|
|
103
|
+
"db_alias",
|
|
104
|
+
("db_schema", "db_table", "db_column"),
|
|
105
|
+
("db_lookup_field", "db_user_field"),
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
"LDAP Configuration",
|
|
111
|
+
{
|
|
112
|
+
"classes": ("collapse",),
|
|
113
|
+
"fields": ("ldap_attribute",),
|
|
114
|
+
},
|
|
115
|
+
),
|
|
116
|
+
(
|
|
117
|
+
"API Configuration",
|
|
118
|
+
{
|
|
119
|
+
"classes": ("collapse",),
|
|
120
|
+
"fields": ("api_endpoint", "api_field"),
|
|
121
|
+
},
|
|
122
|
+
),
|
|
123
|
+
(
|
|
124
|
+
"Custom Configuration",
|
|
125
|
+
{
|
|
126
|
+
"classes": ("collapse",),
|
|
127
|
+
"fields": ("custom_config",),
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@admin.register(FormDefinition)
|
|
134
|
+
class FormDefinitionAdmin(admin.ModelAdmin):
|
|
135
|
+
list_display = (
|
|
136
|
+
"name",
|
|
137
|
+
"slug",
|
|
138
|
+
"is_active",
|
|
139
|
+
"requires_login",
|
|
140
|
+
"version",
|
|
141
|
+
"created_at",
|
|
142
|
+
)
|
|
143
|
+
list_filter = ("is_active", "requires_login")
|
|
144
|
+
search_fields = ("name", "slug", "description")
|
|
145
|
+
prepopulated_fields = {"slug": ("name",)}
|
|
146
|
+
inlines = [FormFieldInline]
|
|
147
|
+
filter_horizontal = ("submit_groups", "view_groups", "admin_groups")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@admin.register(WorkflowDefinition)
|
|
151
|
+
class WorkflowDefinitionAdmin(admin.ModelAdmin):
|
|
152
|
+
list_display = (
|
|
153
|
+
"form_definition",
|
|
154
|
+
"requires_approval",
|
|
155
|
+
"approval_logic",
|
|
156
|
+
"requires_manager_approval",
|
|
157
|
+
)
|
|
158
|
+
list_filter = (
|
|
159
|
+
"requires_approval",
|
|
160
|
+
"approval_logic",
|
|
161
|
+
"requires_manager_approval",
|
|
162
|
+
)
|
|
163
|
+
search_fields = ("form_definition__name",)
|
|
164
|
+
filter_horizontal = ("approval_groups", "escalation_groups")
|
|
165
|
+
fieldsets = (
|
|
166
|
+
(
|
|
167
|
+
None,
|
|
168
|
+
{
|
|
169
|
+
"fields": (
|
|
170
|
+
"form_definition",
|
|
171
|
+
("requires_approval", "approval_logic"),
|
|
172
|
+
"approval_groups",
|
|
173
|
+
)
|
|
174
|
+
},
|
|
175
|
+
),
|
|
176
|
+
(
|
|
177
|
+
"Manager approval",
|
|
178
|
+
{
|
|
179
|
+
"classes": ("collapse",),
|
|
180
|
+
"fields": ("requires_manager_approval", "manager_can_override_group"),
|
|
181
|
+
},
|
|
182
|
+
),
|
|
183
|
+
(
|
|
184
|
+
"Conditional escalation",
|
|
185
|
+
{
|
|
186
|
+
"classes": ("collapse",),
|
|
187
|
+
"fields": (
|
|
188
|
+
("escalation_field", "escalation_threshold"),
|
|
189
|
+
"escalation_groups",
|
|
190
|
+
),
|
|
191
|
+
},
|
|
192
|
+
),
|
|
193
|
+
(
|
|
194
|
+
"Timeouts",
|
|
195
|
+
{
|
|
196
|
+
"classes": ("collapse",),
|
|
197
|
+
"fields": (
|
|
198
|
+
"approval_deadline_days",
|
|
199
|
+
"send_reminder_after_days",
|
|
200
|
+
"auto_approve_after_days",
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
),
|
|
204
|
+
(
|
|
205
|
+
"Notifications",
|
|
206
|
+
{
|
|
207
|
+
"classes": ("collapse",),
|
|
208
|
+
"fields": (
|
|
209
|
+
(
|
|
210
|
+
"notify_on_submission",
|
|
211
|
+
"notify_on_approval",
|
|
212
|
+
"notify_on_rejection",
|
|
213
|
+
"notify_on_withdrawal",
|
|
214
|
+
),
|
|
215
|
+
"additional_notify_emails",
|
|
216
|
+
),
|
|
217
|
+
},
|
|
218
|
+
),
|
|
219
|
+
(
|
|
220
|
+
"Post-approval DB updates",
|
|
221
|
+
{
|
|
222
|
+
"classes": ("collapse",),
|
|
223
|
+
"fields": ("enable_db_updates", "db_update_mappings"),
|
|
224
|
+
},
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@admin.register(PostSubmissionAction)
|
|
230
|
+
class PostSubmissionActionAdmin(admin.ModelAdmin):
|
|
231
|
+
list_display = (
|
|
232
|
+
"name",
|
|
233
|
+
"form_definition",
|
|
234
|
+
"action_type",
|
|
235
|
+
"trigger",
|
|
236
|
+
"is_active",
|
|
237
|
+
"order",
|
|
238
|
+
)
|
|
239
|
+
list_filter = (
|
|
240
|
+
"action_type",
|
|
241
|
+
"trigger",
|
|
242
|
+
"is_active",
|
|
243
|
+
"form_definition",
|
|
244
|
+
)
|
|
245
|
+
search_fields = (
|
|
246
|
+
"name",
|
|
247
|
+
"description",
|
|
248
|
+
"form_definition__name",
|
|
249
|
+
)
|
|
250
|
+
list_editable = ("is_active", "order")
|
|
251
|
+
ordering = ("form_definition", "order", "name")
|
|
252
|
+
|
|
253
|
+
fieldsets = (
|
|
254
|
+
(
|
|
255
|
+
None,
|
|
256
|
+
{
|
|
257
|
+
"fields": (
|
|
258
|
+
"form_definition",
|
|
259
|
+
"name",
|
|
260
|
+
"description",
|
|
261
|
+
("action_type", "trigger"),
|
|
262
|
+
("is_active", "order"),
|
|
263
|
+
)
|
|
264
|
+
},
|
|
265
|
+
),
|
|
266
|
+
(
|
|
267
|
+
"Database Update Configuration",
|
|
268
|
+
{
|
|
269
|
+
"classes": ("collapse",),
|
|
270
|
+
"fields": (
|
|
271
|
+
("db_alias", "db_schema", "db_table"),
|
|
272
|
+
("db_lookup_field", "db_user_field"),
|
|
273
|
+
"db_field_mappings",
|
|
274
|
+
),
|
|
275
|
+
"description": (
|
|
276
|
+
"Configure database updates. Field mappings format: "
|
|
277
|
+
'[{"form_field": "email", "db_column": "EMAIL_ADDRESS"}, ...]'
|
|
278
|
+
),
|
|
279
|
+
},
|
|
280
|
+
),
|
|
281
|
+
(
|
|
282
|
+
"LDAP Update Configuration",
|
|
283
|
+
{
|
|
284
|
+
"classes": ("collapse",),
|
|
285
|
+
"fields": (
|
|
286
|
+
"ldap_dn_template",
|
|
287
|
+
"ldap_field_mappings",
|
|
288
|
+
),
|
|
289
|
+
"description": (
|
|
290
|
+
"Configure LDAP updates. Field mappings format: "
|
|
291
|
+
'[{"form_field": "phone", "ldap_attribute": "telephoneNumber"}, ...]'
|
|
292
|
+
),
|
|
293
|
+
},
|
|
294
|
+
),
|
|
295
|
+
(
|
|
296
|
+
"API Call Configuration",
|
|
297
|
+
{
|
|
298
|
+
"classes": ("collapse",),
|
|
299
|
+
"fields": (
|
|
300
|
+
("api_endpoint", "api_method"),
|
|
301
|
+
"api_headers",
|
|
302
|
+
"api_body_template",
|
|
303
|
+
),
|
|
304
|
+
"description": (
|
|
305
|
+
"Configure API calls. Use {field_name} in body template for form field values."
|
|
306
|
+
),
|
|
307
|
+
},
|
|
308
|
+
),
|
|
309
|
+
(
|
|
310
|
+
"Custom Handler Configuration",
|
|
311
|
+
{
|
|
312
|
+
"classes": ("collapse",),
|
|
313
|
+
"fields": (
|
|
314
|
+
"custom_handler_path",
|
|
315
|
+
"custom_handler_config",
|
|
316
|
+
),
|
|
317
|
+
"description": (
|
|
318
|
+
"Python path to custom handler function (e.g., 'myapp.handlers.custom_update')"
|
|
319
|
+
),
|
|
320
|
+
},
|
|
321
|
+
),
|
|
322
|
+
(
|
|
323
|
+
"Conditional Execution",
|
|
324
|
+
{
|
|
325
|
+
"classes": ("collapse",),
|
|
326
|
+
"fields": (
|
|
327
|
+
"condition_field",
|
|
328
|
+
("condition_operator", "condition_value"),
|
|
329
|
+
),
|
|
330
|
+
"description": (
|
|
331
|
+
"Execute this action only when the condition is met."
|
|
332
|
+
),
|
|
333
|
+
},
|
|
334
|
+
),
|
|
335
|
+
(
|
|
336
|
+
"Error Handling",
|
|
337
|
+
{
|
|
338
|
+
"classes": ("collapse",),
|
|
339
|
+
"fields": (
|
|
340
|
+
"fail_silently",
|
|
341
|
+
("retry_on_failure", "max_retries"),
|
|
342
|
+
),
|
|
343
|
+
},
|
|
344
|
+
),
|
|
345
|
+
(
|
|
346
|
+
"Metadata",
|
|
347
|
+
{
|
|
348
|
+
"classes": ("collapse",),
|
|
349
|
+
"fields": (
|
|
350
|
+
"created_at",
|
|
351
|
+
"updated_at",
|
|
352
|
+
),
|
|
353
|
+
},
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
readonly_fields = ("created_at", "updated_at")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@admin.register(FormSubmission)
|
|
361
|
+
class FormSubmissionAdmin(admin.ModelAdmin):
|
|
362
|
+
list_display = (
|
|
363
|
+
"id",
|
|
364
|
+
"form_definition",
|
|
365
|
+
"submitter",
|
|
366
|
+
"status",
|
|
367
|
+
"created_at",
|
|
368
|
+
"submitted_at",
|
|
369
|
+
"completed_at",
|
|
370
|
+
)
|
|
371
|
+
list_filter = ("status", "form_definition")
|
|
372
|
+
date_hierarchy = "created_at"
|
|
373
|
+
search_fields = (
|
|
374
|
+
"id",
|
|
375
|
+
"form_definition__name",
|
|
376
|
+
"submitter__username",
|
|
377
|
+
"submitter__email",
|
|
378
|
+
)
|
|
379
|
+
raw_id_fields = ("submitter",)
|
|
380
|
+
readonly_fields = ("created_at", "submitted_at", "completed_at")
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@admin.register(ApprovalTask)
|
|
384
|
+
class ApprovalTaskAdmin(admin.ModelAdmin):
|
|
385
|
+
list_display = (
|
|
386
|
+
"id",
|
|
387
|
+
"submission",
|
|
388
|
+
"step_name",
|
|
389
|
+
"status",
|
|
390
|
+
"assigned_to",
|
|
391
|
+
"assigned_group",
|
|
392
|
+
"due_date",
|
|
393
|
+
"completed_at",
|
|
394
|
+
)
|
|
395
|
+
list_filter = ("status", "step_name", "assigned_group")
|
|
396
|
+
search_fields = (
|
|
397
|
+
"submission__id",
|
|
398
|
+
"submission__form_definition__name",
|
|
399
|
+
"assigned_to__username",
|
|
400
|
+
)
|
|
401
|
+
raw_id_fields = ("submission", "assigned_to", "completed_by")
|
|
402
|
+
readonly_fields = ("created_at", "reminder_sent_at")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@admin.register(AuditLog)
|
|
406
|
+
class AuditLogAdmin(admin.ModelAdmin):
|
|
407
|
+
list_display = ("created_at", "user", "action", "object_type", "object_id")
|
|
408
|
+
list_filter = ("action", "object_type")
|
|
409
|
+
date_hierarchy = "created_at"
|
|
410
|
+
search_fields = (
|
|
411
|
+
"user__username",
|
|
412
|
+
"object_type",
|
|
413
|
+
"object_id",
|
|
414
|
+
"comments",
|
|
415
|
+
)
|
|
416
|
+
readonly_fields = (
|
|
417
|
+
"created_at",
|
|
418
|
+
"user",
|
|
419
|
+
"action",
|
|
420
|
+
"object_type",
|
|
421
|
+
"object_id",
|
|
422
|
+
"user_ip",
|
|
423
|
+
"changes",
|
|
424
|
+
"comments",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@admin.register(UserProfile)
|
|
429
|
+
class UserProfileAdmin(admin.ModelAdmin):
|
|
430
|
+
list_display = ("user", "department", "title", "employee_id")
|
|
431
|
+
search_fields = ("user__username", "user__email", "department", "title")
|
|
432
|
+
raw_id_fields = ("user", "manager")
|
|
433
|
+
list_filter = ("department",)
|
|
434
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Forms Workflows App Configuration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DjangoFormsWorkflowsConfig(AppConfig):
|
|
9
|
+
"""App configuration for Django Forms Workflows"""
|
|
10
|
+
|
|
11
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
12
|
+
name = 'django_forms_workflows'
|
|
13
|
+
verbose_name = 'Forms Workflows'
|
|
14
|
+
|
|
15
|
+
def ready(self):
|
|
16
|
+
"""
|
|
17
|
+
Import signal handlers and perform app initialization.
|
|
18
|
+
"""
|
|
19
|
+
# Import signals if you have any
|
|
20
|
+
# from . import signals
|
|
21
|
+
pass
|
|
22
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Source Abstraction Layer
|
|
3
|
+
|
|
4
|
+
Pluggable system for prefilling form fields from external sources:
|
|
5
|
+
- LDAP/Active Directory
|
|
6
|
+
- External databases
|
|
7
|
+
- REST APIs
|
|
8
|
+
- Custom sources
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from django_forms_workflows.data_sources import get_data_source
|
|
12
|
+
|
|
13
|
+
source = get_data_source('ldap')
|
|
14
|
+
value = source.get_value(user, 'department')
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .base import DataSource, DataSourceRegistry
|
|
18
|
+
from .user_source import UserDataSource
|
|
19
|
+
from .ldap_source import LDAPDataSource
|
|
20
|
+
from .database_source import DatabaseDataSource
|
|
21
|
+
|
|
22
|
+
# Global registry
|
|
23
|
+
registry = DataSourceRegistry()
|
|
24
|
+
|
|
25
|
+
# Register built-in sources
|
|
26
|
+
registry.register('user', UserDataSource)
|
|
27
|
+
registry.register('ldap', LDAPDataSource)
|
|
28
|
+
registry.register('database', DatabaseDataSource)
|
|
29
|
+
registry.register('db', DatabaseDataSource) # Alias
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_data_source(source_type):
|
|
33
|
+
"""
|
|
34
|
+
Get a data source instance by type.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
source_type: Type of data source ('user', 'ldap', 'database', etc.)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
DataSource instance
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If source type is not registered
|
|
44
|
+
"""
|
|
45
|
+
return registry.get(source_type)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register_data_source(source_type, source_class):
|
|
49
|
+
"""
|
|
50
|
+
Register a custom data source.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
source_type: Unique identifier for the source
|
|
54
|
+
source_class: DataSource subclass
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
from django_forms_workflows.data_sources import register_data_source, DataSource
|
|
58
|
+
|
|
59
|
+
class SalesforceSource(DataSource):
|
|
60
|
+
def get_value(self, user, field_name, **kwargs):
|
|
61
|
+
# Query Salesforce API
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
register_data_source('salesforce', SalesforceSource)
|
|
65
|
+
"""
|
|
66
|
+
registry.register(source_type, source_class)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
'DataSource',
|
|
71
|
+
'DataSourceRegistry',
|
|
72
|
+
'UserDataSource',
|
|
73
|
+
'LDAPDataSource',
|
|
74
|
+
'DatabaseDataSource',
|
|
75
|
+
'get_data_source',
|
|
76
|
+
'register_data_source',
|
|
77
|
+
'registry',
|
|
78
|
+
]
|
|
79
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for data source abstraction
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Optional, Dict
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataSource(ABC):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for data sources.
|
|
15
|
+
|
|
16
|
+
All data sources must implement the get_value method.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def get_value(self, user, field_name: str, **kwargs) -> Optional[Any]:
|
|
21
|
+
"""
|
|
22
|
+
Get a value from the data source.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
user: Django User object
|
|
26
|
+
field_name: Name of the field to retrieve
|
|
27
|
+
**kwargs: Additional parameters (e.g., schema, table, column)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The field value, or None if not found
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def is_available(self) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if this data source is available/configured.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if the data source can be used, False otherwise
|
|
40
|
+
"""
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
def get_display_name(self) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Get a human-readable name for this data source.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Display name
|
|
49
|
+
"""
|
|
50
|
+
return self.__class__.__name__
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DataSourceRegistry:
|
|
54
|
+
"""
|
|
55
|
+
Registry for data sources.
|
|
56
|
+
|
|
57
|
+
Allows registration and retrieval of data source implementations.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self._sources: Dict[str, type] = {}
|
|
62
|
+
|
|
63
|
+
def register(self, source_type: str, source_class: type):
|
|
64
|
+
"""
|
|
65
|
+
Register a data source.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
source_type: Unique identifier for the source
|
|
69
|
+
source_class: DataSource subclass
|
|
70
|
+
"""
|
|
71
|
+
if not issubclass(source_class, DataSource):
|
|
72
|
+
raise ValueError(f"{source_class} must be a subclass of DataSource")
|
|
73
|
+
|
|
74
|
+
self._sources[source_type] = source_class
|
|
75
|
+
logger.info(f"Registered data source: {source_type} -> {source_class.__name__}")
|
|
76
|
+
|
|
77
|
+
def get(self, source_type: str) -> DataSource:
|
|
78
|
+
"""
|
|
79
|
+
Get a data source instance by type.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
source_type: Type of data source
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
DataSource instance
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If source type is not registered
|
|
89
|
+
"""
|
|
90
|
+
if source_type not in self._sources:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Unknown data source type: {source_type}. "
|
|
93
|
+
f"Available types: {', '.join(self._sources.keys())}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
source_class = self._sources[source_type]
|
|
97
|
+
return source_class()
|
|
98
|
+
|
|
99
|
+
def list_sources(self) -> list:
|
|
100
|
+
"""
|
|
101
|
+
List all registered data source types.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of source type names
|
|
105
|
+
"""
|
|
106
|
+
return list(self._sources.keys())
|
|
107
|
+
|
|
108
|
+
def is_registered(self, source_type: str) -> bool:
|
|
109
|
+
"""
|
|
110
|
+
Check if a source type is registered.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
source_type: Type to check
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
True if registered, False otherwise
|
|
117
|
+
"""
|
|
118
|
+
return source_type in self._sources
|
|
119
|
+
|