diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 646849f..09205dc 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -6,6 +6,7 @@ from auditlog.registry import auditlog from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail +from django.core.validators import RegexValidator from django.db import models, transaction from django.http import HttpRequest from django.utils.functional import cached_property @@ -20,7 +21,18 @@ from servala.core.odoo import CLIENT class Organization(ServalaModelMixin, models.Model): - name = models.CharField(max_length=100, verbose_name=_("Name")) + name = models.CharField( + max_length=32, + verbose_name=_("Name"), + validators=[ + RegexValidator( + regex=r"^[A-Za-z0-9\s]+$", + message=_( + "Organization name can only contain letters, numbers, and spaces." + ), + ) + ], + ) # The namespace is generated as "org-{id}" in accordance with RFC 1035 Label Names. # It is nullable as we need to write to the database in order to read the ID, but should # not be null in practical use. diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index 27e6a09..96b06e0 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -1,5 +1,6 @@ from django import forms from django.core.exceptions import ValidationError +from django.core.validators import RegexValidator from django.forms import ModelForm from django.utils.translation import gettext_lazy as _ @@ -16,9 +17,43 @@ class OrganizationForm(HtmxMixin, ModelForm): class Meta: model = Organization fields = ("name",) + widgets = { + "name": forms.TextInput( + attrs={ + "maxlength": "32", + "pattern": "[A-Za-z0-9\\s]+", + "title": _( + "Organization name can only contain letters, numbers, and spaces" + ), + } + ), + } class OrganizationCreateForm(OrganizationForm): + address_validator = RegexValidator( + regex=r"^[\w\s\.,\-/()\']+$", + message=_( + "This field can only contain letters, numbers, spaces, and basic punctuation (.,-/()')." + ), + ) + city_validator = RegexValidator( + regex=r"^[\w\s\-\']+$", + message=_("City name contains invalid characters."), + ) + postal_code_validator = RegexValidator( + regex=r"^[\w\s\-]+$", + message=_( + "Postal code can only contain letters, numbers, spaces, and hyphens." + ), + ) + phone_validator = RegexValidator( + regex=r"^[0-9\s\+\-()]+$", + message=_( + "Phone number can only contain numbers, spaces, and basic punctuation (+,-,())." + ), + ) + billing_processing_choice = forms.ChoiceField( choices=[ ("existing", _("Use an existing billing address")), @@ -34,17 +69,86 @@ class OrganizationCreateForm(OrganizationForm): ) # Fields for creating a new billing address in Odoo, prefixed with 'invoice_' - invoice_street = forms.CharField(label=_("Line 1"), required=False, max_length=100) - invoice_street2 = forms.CharField(label=_("Line 2"), required=False, max_length=100) - invoice_city = forms.CharField(label=_("City"), required=False, max_length=100) - invoice_zip = forms.CharField(label=_("Postal Code"), required=False, max_length=20) + invoice_street = forms.CharField( + label=_("Line 1"), + required=False, + max_length=128, + validators=[address_validator], + widget=forms.TextInput( + attrs={ + "maxlength": "128", + "title": _( + "Letters, numbers, spaces, and basic punctuation allowed. Emoji not allowed." + ), + } + ), + ) + invoice_street2 = forms.CharField( + label=_("Line 2"), + required=False, + max_length=128, + validators=[address_validator], + widget=forms.TextInput( + attrs={ + "maxlength": "128", + "title": _( + "Letters, numbers, spaces, and basic punctuation allowed. Emoji not allowed." + ), + } + ), + ) + invoice_city = forms.CharField( + label=_("City"), + required=False, + max_length=64, + validators=[city_validator], + widget=forms.TextInput( + attrs={ + "maxlength": "64", + "title": _( + "Letters, spaces, hyphens, and apostrophes allowed. Emoji not allowed." + ), + } + ), + ) + invoice_zip = forms.CharField( + label=_("Postal Code"), + required=False, + max_length=20, + validators=[postal_code_validator], + widget=forms.TextInput( + attrs={ + "maxlength": "20", + "title": _( + "Letters, numbers, spaces, and hyphens allowed. Emoji not allowed." + ), + } + ), + ) invoice_country = forms.ChoiceField( label=_("Country"), required=False, choices=get_odoo_countries(), ) - invoice_email = forms.EmailField(label=_("Billing Email"), required=False) - invoice_phone = forms.CharField(label=_("Phone"), required=False, max_length=30) + invoice_email = forms.EmailField( + label=_("Billing Email"), + required=False, + max_length=254, + widget=forms.EmailInput(attrs={"maxlength": "254"}), + ) + invoice_phone = forms.CharField( + label=_("Phone"), + required=False, + max_length=30, + validators=[phone_validator], + widget=forms.TextInput( + attrs={ + "maxlength": "30", + "pattern": r"[0-9\s\+\-()]+", + "title": _("Only numbers, spaces, and basic punctuation allowed"), + } + ), + ) class Meta(OrganizationForm.Meta): pass