bottleneckcrm/backend/webforms/legacy.py
Ashwin 41250838ad Add comprehensive tests for web forms functionality
- Implement tests for web forms screen to ensure proper rendering and behavior on different devices.
- Create unit tests for web form model to validate JSON serialization and deserialization.
- Add tests for web forms provider to verify API interactions and state management.
- Ensure that publishing rules and field validations are correctly enforced in the tests.
2026-08-17 13:33:14 +05:30

108 lines
4.1 KiB
Python

"""Provisioning the backing web form for a legacy web-to-lead API key.
`CreateLeadFromSite` writes leads through `webforms.service.submit_form`, which
needs a `WebForm` describing what the key collects. Two callers need to build
one and they cannot share a model import:
* the backfill migration, which must use the historical models
`apps.get_model` hands it;
* `ensure_web_form` below, on the request path, which uses the real ones.
So `build_web_form` takes its model classes as arguments. That is the whole
reason for the signature: it keeps one copy of "what a legacy key collects"
without the migration reaching for models that may have moved on by the time
someone runs it on a fresh database.
WHY THE REQUEST PATH PROVISIONS AT ALL, GIVEN THE MIGRATION
The migration is a one-shot over the rows that existed when it ran. Nothing
else ever set `legacy_api_setting`, so every key minted afterwards through
`common/views/settings_views.py` had no form and the endpoint answered 409 for
good. Provisioning on first use covers every path that can create a key,
including a shell session and a future importer, instead of one of them.
"""
# (lead_field, label, is_required). Exactly what the legacy view read out of
# the request body, in the order a person would fill them in. The two renames
# it needs are in `LEGACY_PARAM_MAP` on the view, because they are properties
# of the request parameters rather than of the form.
LEGACY_FIELDS = [
("salutation", "Title", False),
("first_name", "First name", False),
("last_name", "Last name", False),
("email", "Email", True),
("phone", "Phone", False),
("description", "Message", False),
]
def build_web_form(api_setting, *, web_form_model, field_model, profile_model):
"""Create and return the form backing `api_setting`.
Assumes the caller has already established there is none. The recipients
come from the key's own `lead_assigned_to`, falling back to a profile for
whoever created it: the old view ignored that M2M entirely and notified
`created_by`, which is why a key's configured recipients heard nothing.
`lead_source` is "other" rather than the key's `website`. The old view
assigned the URL to `Lead.source`, which declares `choices=LEAD_SOURCE`, so
those rows were invisible to every source-based filter.
"""
recipients = list(api_setting.lead_assigned_to.all())
if not recipients:
recipients = list(
profile_model.objects.filter(
user_id=api_setting.created_by_id, org=api_setting.org
)
)
form = web_form_model.objects.create(
name=api_setting.title or "Web to lead",
org=api_setting.org,
is_published=True,
assign_to=recipients[0] if recipients else None,
lead_source="other",
legacy_api_setting=api_setting,
created_by_id=api_setting.created_by_id,
)
if recipients:
form.notify_profiles.set(recipients)
tags = list(api_setting.tags.all())
if tags:
form.tags.set(tags)
for order, (lead_field, label, is_required) in enumerate(LEGACY_FIELDS):
field_model.objects.create(
form=form,
org=api_setting.org,
order=order,
source="lead",
lead_field=lead_field,
label=label,
is_required=is_required,
created_by_id=api_setting.created_by_id,
)
return form
def ensure_web_form(api_setting):
"""The form backing this key, creating it on first use.
Returns the existing form untouched when there is one. It belongs to
whoever has been editing it since, so re-deriving its fields or flipping
`is_published` back on here would make unpublishing a legacy form
impossible.
"""
from webforms.models import WebForm, WebFormField
existing = WebForm.objects.filter(legacy_api_setting=api_setting).first()
if existing is not None:
return existing
from common.models import Profile
return build_web_form(
api_setting,
web_form_model=WebForm,
field_model=WebFormField,
profile_model=Profile,
)