snapshot: refine builder field rules and conditional summaries

This commit is contained in:
Md Bayazid Bostame
2026-03-27 23:03:17 +01:00
parent e84ddd558b
commit 8e950de994
5 changed files with 1405 additions and 553 deletions

View File

@@ -133,6 +133,46 @@ CONDITIONAL_RULE_OPERATOR_CHOICES = [
]
def _field_rule_summary(*, is_visible: bool, is_required, locked: bool) -> str:
if locked:
return str(_('Fixes Kernfeld, immer sichtbar.'))
if not is_visible:
return str(_('Ausgeblendet, erscheint nicht im Formular.'))
if is_required is True:
return str(_('Sichtbar und als Pflichtfeld markiert.'))
if is_required is False:
return str(_('Sichtbar und optional.'))
return str(_('Sichtbar mit Standardverhalten.'))
def _conditional_clause_sentence(clause: dict, field_label_map: dict[str, str]) -> str:
field_name = (clause.get('field') or '').strip()
operator = (clause.get('operator') or '').strip()
value = clause.get('value')
if not field_name or not operator:
return ''
field_label = field_label_map.get(field_name, field_name)
if operator == 'checked':
return _('%(field)s ist aktiviert') % {'field': field_label}
if operator == 'equals':
if value not in (None, ''):
return _('%(field)s ist gleich %(value)s') % {'field': field_label, 'value': value}
return _('%(field)s ist gleich') % {'field': field_label}
if operator == 'not_equals':
if value not in (None, ''):
return _('%(field)s ist nicht gleich %(value)s') % {'field': field_label, 'value': value}
return _('%(field)s ist nicht gleich') % {'field': field_label}
return _('%(field)s erfüllt die Bedingung') % {'field': field_label}
def _conditional_rule_summary(clauses: list[dict], field_label_map: dict[str, str]) -> str:
active_clauses = [clause for clause in clauses if clause.get('field') and clause.get('operator')]
if not active_clauses:
return str(_('Immer sichtbar.'))
parts = [str(_conditional_clause_sentence(clause, field_label_map)) for clause in active_clauses]
return str(_('Sichtbar, wenn %(conditions)s.') % {'conditions': ' und '.join(parts)})
def _normalized_conditional_rule_payload(form_type: str) -> dict[str, dict]:
configs = ensure_form_conditional_rule_configs(form_type)
payload = {}
@@ -2239,30 +2279,30 @@ def form_builder_page(request):
if delete_option_id:
option = FormOption.objects.filter(id=delete_option_id).first()
if not option:
messages.error(request, 'Option nicht gefunden.')
messages.error(request, _('Option nicht gefunden.'))
else:
option_category = option.category
deleted_label = option.label
deleted_id = option.id
option.delete()
_audit(request, 'form_option_deleted', target_type='form_option', target_id=deleted_id, target_label=deleted_label)
messages.success(request, 'Option wurde gelöscht.')
messages.success(request, _('Option wurde gelöscht.'))
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=options")
if delete_custom_field_id:
custom_field = FormCustomFieldConfig.objects.filter(id=delete_custom_field_id, form_type=form_type).first()
if not custom_field:
messages.error(request, 'Benutzerdefiniertes Feld nicht gefunden.')
messages.error(request, _('Benutzerdefiniertes Feld nicht gefunden.'))
else:
deleted_label = custom_field.label
deleted_id = custom_field.id
custom_field.delete()
_audit(request, 'form_custom_field_deleted', target_type='form_custom_field', target_id=deleted_id, target_label=deleted_label)
messages.success(request, 'Benutzerdefiniertes Feld wurde gelöscht.')
messages.success(request, _('Benutzerdefiniertes Feld wurde gelöscht.'))
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields")
if delete_custom_section_id:
custom_section = FormCustomSectionConfig.objects.filter(id=delete_custom_section_id, form_type=form_type).first()
if not custom_section:
messages.error(request, 'Benutzerdefinierter Abschnitt nicht gefunden.')
messages.error(request, _('Benutzerdefinierter Abschnitt nicht gefunden.'))
else:
deleted_label = custom_section.title
deleted_id = custom_section.id
@@ -2285,7 +2325,7 @@ def form_builder_page(request):
target_label=deleted_label,
details={'section_key': section_key, 'deleted_field_count': deleted_field_count},
)
messages.success(request, 'Benutzerdefinierter Abschnitt wurde gelöscht.')
messages.success(request, _('Benutzerdefinierter Abschnitt wurde gelöscht.'))
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-sections")
action = request.POST.get('builder_action', '')
@@ -2295,9 +2335,9 @@ def form_builder_page(request):
label_en = request.POST.get('label_en', '').strip()
value = request.POST.get('value', '').strip()
if category not in option_categories:
messages.error(request, 'Ungültige Kategorie.')
messages.error(request, _('Ungültige Kategorie.'))
elif not label:
messages.error(request, 'Bitte einen Namen für die Option angeben.')
messages.error(request, _('Bitte einen Namen für die Option angeben.'))
else:
next_sort = (
FormOption.objects.filter(category=category).order_by('-sort_order').values_list('sort_order', flat=True).first()
@@ -2318,7 +2358,7 @@ def form_builder_page(request):
target_label=label,
details={'category': category, 'label_en': label_en, 'value': value or label},
)
messages.success(request, 'Option wurde hinzugefügt.')
messages.success(request, _('Option wurde hinzugefügt.'))
option_category = category
elif action == 'save_options':
@@ -2336,11 +2376,11 @@ def form_builder_page(request):
try:
option.save(update_fields=['label', 'label_en', 'value', 'is_active', 'sort_order'])
except IntegrityError:
messages.error(request, f'Doppelte Bezeichnung in Kategorie: {next_label}')
messages.error(request, _('Doppelte Bezeichnung in Kategorie: %(label)s') % {'label': next_label})
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option.category}&module=options")
option_category = option.category
_audit(request, 'form_options_saved', target_type='form_option', target_label=option_category, details={'count': len(option_ids)})
messages.success(request, 'Optionen wurden gespeichert.')
messages.success(request, _('Optionen wurden gespeichert.'))
elif action == 'save_field_texts':
field_ids = request.POST.getlist('field_ids')
@@ -2354,14 +2394,14 @@ def form_builder_page(request):
cfg.help_text_override_en = (request.POST.get(f'help_text_override_en_{cfg.id}') or '').strip()
cfg.save(update_fields=['label_override', 'label_override_en', 'help_text_override', 'help_text_override_en'])
_audit(request, 'form_field_texts_saved', target_type='form_config', target_label=form_type, details={'count': len(field_ids)})
messages.success(request, 'Feldtexte wurden gespeichert.')
messages.success(request, _('Feldtexte wurden gespeichert.'))
elif action == 'add_custom_section' and form_type == 'onboarding':
title = (request.POST.get('custom_section_title') or '').strip()
title_en = (request.POST.get('custom_section_title_en') or '').strip()
sort_order_raw = (request.POST.get('custom_section_sort_order') or '').strip()
if not title:
messages.error(request, 'Bitte einen Titel für den benutzerdefinierten Abschnitt angeben.')
messages.error(request, _('Bitte einen Titel für den benutzerdefinierten Abschnitt angeben.'))
else:
section_key_base = build_custom_field_key(title)
section_key = section_key_base
@@ -2382,7 +2422,7 @@ def form_builder_page(request):
is_active=True,
)
_audit(request, 'form_custom_section_added', target_type='form_custom_section', target_label=title, details={'form_type': form_type, 'section_key': section_key})
messages.success(request, 'Benutzerdefinierter Abschnitt wurde hinzugefügt.')
messages.success(request, _('Benutzerdefinierter Abschnitt wurde hinzugefügt.'))
elif action == 'save_custom_sections' and form_type == 'onboarding':
section_ids = request.POST.getlist('custom_section_ids')
@@ -2402,7 +2442,7 @@ def form_builder_page(request):
cfg.save(update_fields=['title', 'title_en', 'is_active', 'sort_order'])
updated += 1
_audit(request, 'form_custom_sections_saved', target_type='form_custom_section', target_label=form_type, details={'count': updated})
messages.success(request, 'Benutzerdefinierte Abschnitte wurden gespeichert.')
messages.success(request, _('Benutzerdefinierte Abschnitte wurden gespeichert.'))
elif action == 'add_custom_field':
label = (request.POST.get('custom_label') or '').strip()
@@ -2417,13 +2457,13 @@ def form_builder_page(request):
section_choices = {key for key in get_section_order(form_type)}
field_type_choices = {key for key, _ in FormCustomFieldConfig.FIELD_TYPE_CHOICES}
if not label:
messages.error(request, 'Bitte eine Bezeichnung für das benutzerdefinierte Feld angeben.')
messages.error(request, _('Bitte eine Bezeichnung für das benutzerdefinierte Feld angeben.'))
elif section_key not in section_choices:
messages.error(request, 'Ungültiger Abschnitt für das benutzerdefinierte Feld.')
messages.error(request, _('Ungültiger Abschnitt für das benutzerdefinierte Feld.'))
elif field_type not in field_type_choices:
messages.error(request, 'Ungültiger Feldtyp.')
messages.error(request, _('Ungültiger Feldtyp.'))
elif field_type == FormCustomFieldConfig.FIELD_TYPE_SELECT and not select_options:
messages.error(request, 'Auswahlfelder benötigen mindestens eine Option.')
messages.error(request, _('Auswahlfelder benötigen mindestens eine Option.'))
else:
field_key_base = build_custom_field_key(label)
field_key = field_key_base
@@ -2451,7 +2491,7 @@ def form_builder_page(request):
select_options_en=select_options_en,
)
_audit(request, 'form_custom_field_added', target_type='form_custom_field', target_label=label, details={'form_type': form_type, 'field_type': field_type, 'section_key': section_key})
messages.success(request, 'Benutzerdefiniertes Feld wurde hinzugefügt.')
messages.success(request, _('Benutzerdefiniertes Feld wurde hinzugefügt.'))
elif action == 'save_custom_fields':
custom_ids = request.POST.getlist('custom_field_ids')
@@ -2482,12 +2522,12 @@ def form_builder_page(request):
cfg.select_options = (request.POST.get(f'custom_select_options_{cfg.id}') or '').strip()
cfg.select_options_en = (request.POST.get(f'custom_select_options_en_{cfg.id}') or '').strip()
if cfg.field_type == FormCustomFieldConfig.FIELD_TYPE_SELECT and not cfg.select_options:
messages.error(request, f'Auswahlfeld "{cfg.label}" benötigt mindestens eine Option.')
messages.error(request, _('Auswahlfeld "%(label)s" benötigt mindestens eine Option.') % {'label': cfg.label})
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields")
cfg.save()
updated += 1
_audit(request, 'form_custom_fields_saved', target_type='form_custom_field', target_label=form_type, details={'count': updated})
messages.success(request, 'Benutzerdefinierte Felder wurden gespeichert.')
messages.success(request, _('Benutzerdefinierte Felder wurden gespeichert.'))
elif action == 'save_field_rules':
field_ids = request.POST.getlist('field_rule_ids')
@@ -2507,7 +2547,7 @@ def form_builder_page(request):
cfg.save(update_fields=['is_visible', 'is_required'])
updated += 1
_audit(request, 'form_field_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
messages.success(request, 'Feldregeln wurden gespeichert.')
messages.success(request, _('Feldregeln wurden gespeichert.'))
elif action == 'save_section_rules' and form_type in {'onboarding', 'offboarding'}:
section_configs = ensure_form_section_configs(form_type)
@@ -2546,7 +2586,7 @@ def form_builder_page(request):
cfg.save(update_fields=['is_active'])
updated += 1
_audit(request, 'form_section_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
messages.success(request, 'Abschnittsregeln wurden gespeichert.')
messages.success(request, _('Abschnittsregeln wurden gespeichert.'))
elif action == 'save_conditional_rules' and form_type == 'onboarding':
rule_configs = ensure_form_conditional_rule_configs(form_type)
@@ -2567,16 +2607,16 @@ def form_builder_page(request):
cfg.save(update_fields=['is_active', 'clauses'])
updated += 1
_audit(request, 'form_conditional_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
messages.success(request, 'Bedingte Logik wurde gespeichert.')
messages.success(request, _('Bedingte Logik wurde gespeichert.'))
elif action == 'apply_preset':
preset_key = (request.POST.get('preset_key') or '').strip()
if apply_form_preset(form_type, preset_key):
active_module = 'preview'
_audit(request, 'form_preset_applied', target_type='form_config', target_label=form_type, details={'preset': preset_key})
messages.success(request, 'Preset wurde angewendet.')
messages.success(request, _('Preset wurde angewendet.'))
else:
messages.error(request, 'Preset konnte nicht angewendet werden.')
messages.error(request, _('Preset konnte nicht angewendet werden.'))
if action in {'add_option', 'save_options'}:
active_module = 'options'
@@ -2778,6 +2818,11 @@ def form_builder_page(request):
'is_visible': cfg.is_visible,
'is_required': cfg.is_required,
'locked': cfg.field_name in locked and not can_override_locked_builder_rules,
'summary': _field_rule_summary(
is_visible=cfg.is_visible,
is_required=cfg.is_required,
locked=cfg.field_name in locked and not can_override_locked_builder_rules,
),
}
)
@@ -2854,6 +2899,7 @@ def form_builder_page(request):
conditional_field_choices.append((field_name, labels.get(field_name, field_name)))
for cfg in custom_field_configs:
conditional_field_choices.append((f'custom__{cfg.field_key}', cfg.translated_label(language_code)))
conditional_field_label_map = {value: label for value, label in conditional_field_choices}
conditional_target_titles = {
'business-card-box': _('Visitenkarten-Details'),
'employment-end-box': _('Vertragsende'),
@@ -2893,6 +2939,7 @@ def form_builder_page(request):
'description': target_description,
'is_active': cfg.is_active,
'clauses': clauses[:2],
'summary': _conditional_rule_summary(clauses[:2], conditional_field_label_map),
'field_choices': conditional_field_choices,
'operator_choices': CONDITIONAL_RULE_OPERATOR_CHOICES,
'target_fields': target_fields,
@@ -2945,6 +2992,41 @@ def form_builder_page(request):
'custom_field_count': len([cfg for cfg in custom_field_configs if cfg.is_active]),
'custom_section_count': len([cfg for cfg in custom_section_configs if cfg.is_active]),
}
module_labels = {
'structure': _('Struktur & Reihenfolge'),
'section-rules': _('Abschnitte'),
'field-rules': _('Feldregeln'),
'conditional-rules': _('Bedingte Logik'),
'options': _('Optionen'),
'field-texts': _('Feldtexte'),
'custom-sections': _('Eigene Abschnitte'),
'custom-fields': _('Eigene Felder'),
'preview': _('Vorschau'),
}
option_category_labels = dict(_translate_choice_list(FormOption.CATEGORY_CHOICES))
form_type_labels = {
'onboarding': _('Onboarding'),
'offboarding': _('Offboarding'),
}
active_focus_label = ''
if active_module == 'structure' and active_structure_section:
active_focus_label = section_labels.get(active_structure_section, active_structure_section)
elif active_module == 'section-rules' and section_rule_items:
active_focus_label = _('Alle Abschnitte')
elif active_module == 'field-rules' and active_field_rules_section:
active_focus_label = section_labels.get(active_field_rules_section, active_field_rules_section)
elif active_module == 'conditional-rules' and active_conditional_target:
active_focus_label = next((item['title'] for item in conditional_rule_items if item['target_key'] == active_conditional_target), active_conditional_target)
elif active_module == 'options':
active_focus_label = option_category_labels.get(option_category, option_category)
elif active_module == 'field-texts' and active_field_texts_section:
active_focus_label = section_labels.get(active_field_texts_section, active_field_texts_section)
elif active_module == 'custom-sections':
active_focus_label = _('Onboarding')
elif active_module == 'custom-fields' and active_custom_fields_section:
active_focus_label = section_labels.get(active_custom_fields_section, active_custom_fields_section)
elif active_module == 'preview':
active_focus_label = _('Live-Vorschau')
return render(
request,
@@ -2952,7 +3034,7 @@ def form_builder_page(request):
{
'form_type': form_type,
'columns': columns,
'form_types': [('onboarding', 'Onboarding'), ('offboarding', 'Offboarding')],
'form_types': [('onboarding', _('Onboarding')), ('offboarding', _('Offboarding'))],
'option_categories': _translate_choice_list(FormOption.CATEGORY_CHOICES),
'selected_option_category': option_category,
'option_items': FormOption.objects.filter(category=option_category).order_by('sort_order', 'label'),
@@ -2971,6 +3053,9 @@ def form_builder_page(request):
'active_subpanel': active_subpanel,
'active_rules_panel': active_rules_panel,
'active_module': active_module,
'active_form_type_label': form_type_labels.get(form_type, form_type),
'active_module_label': module_labels.get(active_module, active_module),
'active_focus_label': active_focus_label,
'active_structure_section': active_structure_section,
'active_field_rules_section': active_field_rules_section,
'active_field_texts_section': active_field_texts_section,