暫無描述

forms.py 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from django import forms
  2. from django.core.exceptions import ValidationError
  3. from django.contrib.auth import get_user_model
  4. from decimal import Decimal
  5. from django.utils import timezone
  6. from django.contrib.contenttypes.models import ContentType
  7. from .models import (
  8. MaterialCategory,
  9. Material,
  10. MaterialImage,
  11. ProvidedService,
  12. Customer,
  13. CustomerSite,
  14. )
  15. from markdownfield.widgets import MDEWidget
  16. from orgs.models import UserProfile
  17. class MaterialCategoryForm(forms.ModelForm):
  18. class Meta:
  19. model = MaterialCategory
  20. fields = ["organization", "name"]
  21. class MultiFileInput(forms.ClearableFileInput):
  22. allow_multiple_selected = True
  23. class MultiImageField(forms.Field):
  24. widget = MultiFileInput
  25. def __init__(self, *args, **kwargs):
  26. kwargs.setdefault("required", False)
  27. super().__init__(*args, **kwargs)
  28. def to_python(self, data):
  29. return data
  30. def validate(self, value):
  31. # Basic required check; skip per-file validation here
  32. if self.required and not value:
  33. raise ValidationError("This field is required.")
  34. class MaterialForm(forms.ModelForm):
  35. images = MultiImageField(help_text="Upload one or more sample images (optional)")
  36. class Meta:
  37. model = Material
  38. fields = ["organization", "category", "name", "code", "default_unit", "images"]
  39. def save(self, commit=True):
  40. instance = super().save(commit=commit)
  41. files = self.files.getlist("images") if hasattr(self, "files") else []
  42. if commit and files:
  43. # Instance has a PK; we can create images now
  44. order_start = instance.images.count()
  45. for i, f in enumerate(files):
  46. MaterialImage.objects.create(material=instance, image=f, display_order=order_start + i)
  47. else:
  48. # Defer image saving until caller completes save
  49. self._pending_images = files
  50. return instance
  51. def save_images(self, instance: Material | None = None):
  52. """Persist any pending images after the Material has been saved."""
  53. if not hasattr(self, "_pending_images"):
  54. return
  55. target = instance or getattr(self, "instance", None)
  56. if not target or not getattr(target, "pk", None):
  57. return
  58. order_start = target.images.count()
  59. for i, f in enumerate(self._pending_images or []):
  60. MaterialImage.objects.create(material=target, image=f, display_order=order_start + i)
  61. # Clear pending list
  62. self._pending_images = []
  63. class CustomerForm(forms.ModelForm):
  64. class Meta:
  65. model = Customer
  66. fields = ["organization", "name", "email", "phone", "billing_address", "price_list"]
  67. class CustomerSiteForm(forms.ModelForm):
  68. class Meta:
  69. model = CustomerSite
  70. fields = ["customer", "name", "address", "contact_name", "contact_phone", "contact_email"]
  71. class ProvidedServiceForm(forms.ModelForm):
  72. class Meta:
  73. model = ProvidedService
  74. fields = ["title", "description", "body", "display_order", "is_enabled"]
  75. widgets = {
  76. "description": forms.Textarea(attrs={"rows": 3}),
  77. "body": MDEWidget(),
  78. }
  79. # Operational forms ----------------------------------------------------------
  80. User = get_user_model()
  81. class PickupAssignForm(forms.Form):
  82. driver = forms.ModelChoiceField(queryset=User.objects.all(), required=True, label="Assign Driver")
  83. class PickupStatusForm(forms.Form):
  84. status = forms.ChoiceField(choices=(), required=True, label="Set Status")
  85. def __init__(self, *args, **kwargs):
  86. from .models import PickupOrder
  87. super().__init__(*args, **kwargs)
  88. self.fields["status"].choices = PickupOrder.STATUS_CHOICES
  89. class PaymentForm(forms.Form):
  90. amount = forms.DecimalField(max_digits=14, decimal_places=2)
  91. received_at = forms.DateTimeField(required=False, initial=timezone.now)
  92. reference = forms.CharField(max_length=128, required=False)
  93. class DocumentForm(forms.Form):
  94. organization = forms.ModelChoiceField(queryset=None)
  95. file = forms.FileField()
  96. kind = forms.CharField(max_length=64, required=False)
  97. content_type = forms.ModelChoiceField(queryset=ContentType.objects.all(), required=True, label="Attach To Model")
  98. object_id = forms.IntegerField(required=True, label="Object ID")
  99. def __init__(self, *args, **kwargs):
  100. from orgs.models import Organization as Org
  101. super().__init__(*args, **kwargs)
  102. self.fields["organization"].queryset = Org.objects.all()
  103. # User management ------------------------------------------------------------
  104. class UserCreateForm(forms.Form):
  105. username = forms.CharField(max_length=150)
  106. email = forms.EmailField(required=False)
  107. role = forms.ChoiceField(choices=UserProfile.ROLE_CHOICES)
  108. password1 = forms.CharField(widget=forms.PasswordInput)
  109. password2 = forms.CharField(widget=forms.PasswordInput)
  110. def clean_username(self):
  111. User = get_user_model()
  112. username = self.cleaned_data["username"].strip()
  113. if User.objects.filter(username__iexact=username).exists():
  114. raise forms.ValidationError("Username already taken")
  115. return username
  116. def clean(self):
  117. cleaned = super().clean()
  118. p1 = cleaned.get("password1")
  119. p2 = cleaned.get("password2")
  120. if p1 and p2 and p1 != p2:
  121. self.add_error("password2", "Passwords do not match")
  122. return cleaned
  123. class UserEditForm(forms.Form):
  124. email = forms.EmailField(required=False)
  125. role = forms.ChoiceField(choices=UserProfile.ROLE_CHOICES)
  126. password1 = forms.CharField(widget=forms.PasswordInput, required=False)
  127. password2 = forms.CharField(widget=forms.PasswordInput, required=False)
  128. def clean(self):
  129. cleaned = super().clean()
  130. p1 = cleaned.get("password1")
  131. p2 = cleaned.get("password2")
  132. if (p1 or p2) and p1 != p2:
  133. self.add_error("password2", "Passwords do not match")
  134. return cleaned