Нет описания

forms.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. from __future__ import annotations
  2. from django import forms
  3. from django.contrib.auth import get_user_model
  4. from django.conf import settings
  5. from django.core.exceptions import ValidationError
  6. from crispy_forms.helper import FormHelper
  7. from crispy_forms.layout import Layout, Field
  8. from django.contrib.auth import get_user_model
  9. from orgs.models import UserProfile
  10. class MultiFileInput(forms.ClearableFileInput):
  11. allow_multiple_selected = True
  12. class MultiFileField(forms.Field):
  13. widget = MultiFileInput
  14. def __init__(self, *args, **kwargs):
  15. kwargs.setdefault("required", False)
  16. super().__init__(*args, **kwargs)
  17. def to_python(self, data):
  18. # Accept a list of UploadedFile or an empty list/None
  19. return data
  20. def validate(self, value):
  21. # Only enforce presence if required=True
  22. if self.required and not value:
  23. raise forms.ValidationError("This field is required.")
  24. class PickupRequestForm(forms.Form):
  25. name = forms.CharField(max_length=255)
  26. email = forms.EmailField(required=False)
  27. phone = forms.CharField(max_length=64, required=False)
  28. address = forms.CharField(widget=forms.Textarea)
  29. preferred_at = forms.DateTimeField(required=False, widget=forms.DateTimeInput(attrs={"type": "datetime-local"}))
  30. materials = forms.CharField(label="Materials/Notes", widget=forms.Textarea, required=False)
  31. photos = MultiFileField(widget=MultiFileInput, help_text="Optional: upload photos of scrap")
  32. class ContactForm(forms.Form):
  33. name = forms.CharField(max_length=255)
  34. email = forms.EmailField()
  35. phone = forms.CharField(max_length=64, required=False)
  36. subject = forms.CharField(max_length=255, required=False)
  37. message = forms.CharField(widget=forms.Textarea)
  38. class RegistrationForm(forms.Form):
  39. username = forms.CharField(max_length=150)
  40. email = forms.EmailField()
  41. password1 = forms.CharField(widget=forms.PasswordInput)
  42. password2 = forms.CharField(widget=forms.PasswordInput)
  43. role = forms.ChoiceField(choices=[(k, v) for k, v in UserProfile.ROLE_CHOICES if k != UserProfile.ROLE_OWNER])
  44. def clean_username(self):
  45. username = self.cleaned_data["username"].strip()
  46. User = get_user_model()
  47. if User.objects.filter(username__iexact=username).exists():
  48. raise forms.ValidationError("Username already taken.")
  49. return username
  50. def clean_email(self):
  51. email = self.cleaned_data["email"].strip()
  52. User = get_user_model()
  53. if User.objects.filter(email__iexact=email).exists():
  54. raise forms.ValidationError("Email already registered.")
  55. return email
  56. def clean(self):
  57. data = super().clean()
  58. p1 = data.get("password1")
  59. p2 = data.get("password2")
  60. if p1 and p2 and p1 != p2:
  61. self.add_error("password2", "Passwords do not match.")
  62. return data
  63. class PublicUserForm(forms.ModelForm):
  64. class Meta:
  65. model = get_user_model()
  66. fields = ["first_name", "last_name", "email"]
  67. def __init__(self, *args, **kwargs):
  68. super().__init__(*args, **kwargs)
  69. self.helper = FormHelper()
  70. self.helper.form_tag = False
  71. self.helper.layout = Layout(
  72. Field("first_name"),
  73. Field("last_name"),
  74. Field("email"),
  75. )
  76. from api.models import Profile as ApiProfile
  77. from orgs.models import UserProfile
  78. class PublicProfileForm(forms.ModelForm):
  79. tags = forms.CharField(label="Tags", required=False, help_text="Comma-separated")
  80. class Meta:
  81. model = ApiProfile
  82. fields = ["bio", "interests", "industry"]
  83. def __init__(self, *args, **kwargs):
  84. super().__init__(*args, **kwargs)
  85. self.helper = FormHelper()
  86. self.helper.form_tag = False
  87. self.helper.layout = Layout(
  88. Field("bio"),
  89. Field("interests"),
  90. Field("industry"),
  91. Field("tags"),
  92. )
  93. if self.instance and getattr(self.instance, "pk", None):
  94. try:
  95. names = list(self.instance.tags.names())
  96. self.fields["tags"].initial = ", ".join(names)
  97. except Exception:
  98. self.fields["tags"].initial = ""
  99. def save(self, commit: bool = True):
  100. profile = super().save(commit)
  101. tags = [t.strip() for t in self.cleaned_data.get("tags", "").split(",") if t.strip()]
  102. try:
  103. if commit and hasattr(profile, "tags"):
  104. profile.tags.set(tags)
  105. except Exception:
  106. pass
  107. return profile
  108. class PublicUserPhotoForm(forms.ModelForm):
  109. remove_photo = forms.BooleanField(label="Remove current photo", required=False)
  110. class Meta:
  111. model = UserProfile
  112. fields = ["my_photo", "remove_photo"]
  113. def __init__(self, *args, **kwargs):
  114. super().__init__(*args, **kwargs)
  115. self.fields["my_photo"].widget = forms.ClearableFileInput(attrs={
  116. "accept": "image/*",
  117. "class": "hidden",
  118. "id": "id_my_photo",
  119. })
  120. self.helper = FormHelper()
  121. self.helper.form_tag = False
  122. self.helper.layout = Layout(
  123. Field("my_photo"),
  124. Field("remove_photo"),
  125. )
  126. def clean_my_photo(self):
  127. f = self.cleaned_data.get("my_photo")
  128. if not f:
  129. return f
  130. try:
  131. content_type = getattr(f, "content_type", "")
  132. if content_type and not content_type.startswith("image/"):
  133. raise ValidationError("Please upload an image file.")
  134. except Exception:
  135. pass
  136. max_bytes = 5 * 1024 * 1024
  137. if getattr(f, "size", 0) and f.size > max_bytes:
  138. raise ValidationError("Image too large (max 5MB).")
  139. return f
  140. def save(self, commit: bool = True):
  141. instance: UserProfile = super().save(commit=False)
  142. remove = self.cleaned_data.get("remove_photo", False)
  143. new_file = self.cleaned_data.get("my_photo")
  144. if remove and not new_file:
  145. try:
  146. if instance.my_photo:
  147. instance.my_photo.delete(save=False)
  148. except Exception:
  149. pass
  150. instance.my_photo = None
  151. if commit:
  152. instance.save()
  153. return instance
  154. class PublicUserProfileExtraForm(forms.ModelForm):
  155. class Meta:
  156. model = UserProfile
  157. fields = [
  158. "phone",
  159. "job_title",
  160. "department",
  161. "preferred_language",
  162. "address_line1",
  163. "address_line2",
  164. "city",
  165. "state",
  166. "postal_code",
  167. "country",
  168. ]
  169. def __init__(self, *args, **kwargs):
  170. super().__init__(*args, **kwargs)
  171. # Render preferred_language as a dropdown of available LANGUAGES
  172. try:
  173. lang_choices = list(getattr(settings, "LANGUAGES", []))
  174. except Exception:
  175. lang_choices = []
  176. if lang_choices:
  177. self.fields["preferred_language"] = forms.ChoiceField(
  178. choices=[("", "—")] + lang_choices,
  179. required=False,
  180. label=self.fields.get("preferred_language").label if self.fields.get("preferred_language") else "Preferred language",
  181. )
  182. self.helper = FormHelper()
  183. self.helper.form_tag = False
  184. self.helper.layout = Layout(
  185. Field("phone"),
  186. Field("job_title"),
  187. Field("department"),
  188. Field("preferred_language"),
  189. Field("address_line1"),
  190. Field("address_line2"),
  191. Field("city"),
  192. Field("state"),
  193. Field("postal_code"),
  194. Field("country"),
  195. )