- Implement comprehensive tests for the ticket board provider to validate kanban and pipeline modes, including ticket movement and error handling. - Create tests for ticket status settable to ensure proper status selection logic. - Enhance web form model tests to cover ticket-specific fields and submission behavior. - Introduce CSV import sheet tests to verify file handling and error reporting. - Add deal aging badge tests to confirm correct display of aging statuses. - Implement tickets selection tests to ensure UI responsiveness and functionality at various screen sizes.
121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
"""The fields a web form may collect, and their limits.
|
|
|
|
A form targets either a Lead or a ticket (`cases.Case`), and each target has
|
|
its own whitelist below.
|
|
|
|
This is a whitelist rather than "any field on Lead" on purpose. A form is
|
|
filled in by an anonymous stranger, so the reachable columns have to be a
|
|
decision someone made, not whatever the model happens to expose. Assignment,
|
|
pipeline stage, probability and deal value are all deliberately absent.
|
|
|
|
`title` and `salutation` are easy to confuse and mean different things:
|
|
`Lead.title` is the lead's subject line ("Website Inquiry"), `Lead.salutation`
|
|
is the honorific ("Ms", "Dr"). The legacy endpoint's `title` request parameter
|
|
maps to `salutation`, not to `Lead.title`
|
|
(see `leads/views/lead_interactions.py`).
|
|
"""
|
|
|
|
from common.utils import COUNTRIES, INDCHOICES
|
|
|
|
# (value, human label). Value is the attribute name on Lead.
|
|
LEAD_FIELD_CHOICES = [
|
|
("salutation", "Salutation"),
|
|
("first_name", "First name"),
|
|
("last_name", "Last name"),
|
|
("email", "Email"),
|
|
("phone", "Phone"),
|
|
("company_name", "Company name"),
|
|
("job_title", "Job title"),
|
|
("website", "Website"),
|
|
("title", "Subject"),
|
|
("description", "Message"),
|
|
("city", "City"),
|
|
("state", "State"),
|
|
("country", "Country"),
|
|
("postcode", "Postal code"),
|
|
("industry", "Industry"),
|
|
]
|
|
|
|
LEAD_FIELD_VALUES = [value for value, _ in LEAD_FIELD_CHOICES]
|
|
|
|
# Mirrors the max_length on each Lead column so the dynamic serializer can
|
|
# reject over-length input with a clean 400 rather than letting the database
|
|
# raise. `description` is a TextField with no limit of its own; 5000 is our
|
|
# cap, so a single submission cannot be used to write an unbounded blob.
|
|
LEAD_FIELD_MAX_LENGTHS = {
|
|
"salutation": 64,
|
|
"first_name": 255,
|
|
"last_name": 255,
|
|
"email": 254,
|
|
"phone": 25,
|
|
"company_name": 255,
|
|
"job_title": 255,
|
|
"website": 255,
|
|
"title": 255,
|
|
"description": 5000,
|
|
"city": 255,
|
|
"state": 255,
|
|
"country": 3,
|
|
"postcode": 64,
|
|
"industry": 255,
|
|
}
|
|
|
|
# Fields whose value must be a member of a choice tuple on Lead. The dynamic
|
|
# serializer validates membership rather than trusting the visitor, because
|
|
# Django does not enforce `choices` at the database level.
|
|
LEAD_FIELD_CHOICE_SOURCES = {
|
|
"country": COUNTRIES,
|
|
"industry": INDCHOICES,
|
|
}
|
|
|
|
# The one field a form has to collect before it can be published. It is the
|
|
# key the submission service dedupes on, and without it a repeat submission
|
|
# from the same address hits Lead's `UniqueConstraint(Lower("email"), "org")`.
|
|
REQUIRED_LEAD_FIELD = "email"
|
|
|
|
# ---- ticket forms ----------------------------------------------------------
|
|
#
|
|
# A ticket form writes one Case and finds or creates one Contact. Each value
|
|
# lands on exactly one of the two:
|
|
#
|
|
# * `name` and `description` go on the Case, as its subject and body;
|
|
# * `email`, `first_name`, `last_name`, `phone` and `company_name` describe the
|
|
# person, and only ever reach a Contact this submission CREATES. An existing
|
|
# contact matched by email is never edited, because anyone who knows an
|
|
# address can post the form.
|
|
#
|
|
# Priority, type, status and assignment are deliberately absent. Those are the
|
|
# form's own settings (`WebForm.ticket_priority`, `ticket_type`, `assign_to`)
|
|
# or routing's decision, never a stranger's: a visitor who could pick
|
|
# "Urgent" would.
|
|
TICKET_FIELD_CHOICES = [
|
|
("email", "Email"),
|
|
("first_name", "First name"),
|
|
("last_name", "Last name"),
|
|
("phone", "Phone"),
|
|
("company_name", "Company name"),
|
|
("name", "Subject"),
|
|
("description", "Message"),
|
|
]
|
|
|
|
# `name` accepts more than Case.name's 64 characters on purpose: the service
|
|
# truncates it, because refusing a visitor's long subject line would lose the
|
|
# ticket over a cosmetic limit. The rest mirror the Contact columns they land
|
|
# in (`company_name` is Contact.organization), and `description` shares the
|
|
# lead form's 5000 cap.
|
|
TICKET_FIELD_MAX_LENGTHS = {
|
|
"email": 254,
|
|
"first_name": 255,
|
|
"last_name": 255,
|
|
"phone": 25,
|
|
"company_name": 255,
|
|
"name": 255,
|
|
"description": 5000,
|
|
}
|
|
|
|
# Case.name is a CharField(max_length=64).
|
|
TICKET_SUBJECT_MAX_LENGTH = 64
|
|
|
|
# The key a ticket form resolves its contact by, and so the field it has to
|
|
# collect before it can be published.
|
|
REQUIRED_TICKET_FIELD = "email"
|