from __future__ import annotations from django import forms from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Field from .models import Organization, UserProfile from api.models import Profile as ApiProfile class OrganizationForm(forms.ModelForm): class Meta: model = Organization fields = ["name", "code", "timezone", "currency_code"] ## ProvidedService moved to recycle_core app; form now lives in recycle_core.forms class UserSelfForm(forms.ModelForm): class Meta: model = get_user_model() fields = ["first_name", "last_name", "email"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = False self.helper.layout = Layout( Field("first_name"), Field("last_name"), Field("email"), ) class ProfileSelfForm(forms.ModelForm): tags = forms.CharField(label="Tags", required=False, help_text="Comma-separated") class Meta: model = ApiProfile fields = ["bio", "interests", "industry"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = False self.helper.layout = Layout( Field("bio"), Field("interests"), Field("industry"), Field("tags"), ) if self.instance and getattr(self.instance, "pk", None): try: names = list(self.instance.tags.names()) self.fields["tags"].initial = ", ".join(names) except Exception: self.fields["tags"].initial = "" def save(self, commit: bool = True): profile = super().save(commit) tags = [t.strip() for t in self.cleaned_data.get("tags", "").split(",") if t.strip()] try: if commit and hasattr(profile, "tags"): profile.tags.set(tags) except Exception: pass return profile class UserProfilePhotoForm(forms.ModelForm): remove_photo = forms.BooleanField(label="Remove current photo", required=False) class Meta: model = UserProfile fields = ["my_photo", "remove_photo"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Friendlier widget: accept images only, keep input visually hidden (we trigger via button) self.fields["my_photo"].widget = forms.ClearableFileInput(attrs={ "accept": "image/*", "class": "hidden", "id": "id_my_photo", }) self.helper = FormHelper() self.helper.form_tag = False self.helper.layout = Layout( Field("my_photo"), Field("remove_photo"), ) def clean_my_photo(self): f = self.cleaned_data.get("my_photo") if not f: return f # Basic size/type validation (max ~5MB) try: content_type = getattr(f, "content_type", "") if content_type and not content_type.startswith("image/"): raise ValidationError("Please upload an image file.") except Exception: # If storage/file doesn't provide content_type, best-effort skip pass max_bytes = 5 * 1024 * 1024 if getattr(f, "size", 0) and f.size > max_bytes: raise ValidationError("Image too large (max 5MB).") return f def save(self, commit: bool = True): instance: UserProfile = super().save(commit=False) remove = self.cleaned_data.get("remove_photo", False) new_file = self.cleaned_data.get("my_photo") if remove and not new_file: # Clear current photo try: if instance.my_photo: instance.my_photo.delete(save=False) except Exception: pass instance.my_photo = None if commit: instance.save() return instance