40204-vm/core/forms.py
Flatlogic Bot cb35610046 1.0.3
2026-06-04 18:40:22 +00:00

165 lines
6.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from urllib.parse import urlparse
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from .models import PropertyEntry, PropertyFlag, PropertySuggestion
IDEALISTA_DOMAINS = ("idealista.com", "idealista.pt", "idealista.it")
def idealista_url_field(*, required=False, label="Idealista link"):
return forms.CharField(
required=required,
label=label,
help_text=(
"Optional. Paste the exact Idealista listing URL if you already found it. "
"If left empty, well create a best-effort Idealista search link."
),
widget=forms.URLInput(
attrs={
"placeholder": "https://www.idealista.com/inmueble/123456/",
"autocomplete": "url",
"inputmode": "url",
}
),
)
def clean_idealista_url_value(value, *, required=False):
value = (value or "").strip()
if not value:
if required:
raise ValidationError("Paste the exact Idealista listing URL.")
return ""
if "://" not in value:
value = f"https://{value}"
validator = URLValidator(schemes=["http", "https"])
try:
validator(value)
except ValidationError as exc:
raise ValidationError(
"Paste a valid Idealista URL, for example https://www.idealista.com/inmueble/123456/."
) from exc
host = (urlparse(value).hostname or "").lower()
is_idealista = any(host == domain or host.endswith(f".{domain}") for domain in IDEALISTA_DOMAINS)
if not is_idealista:
raise ValidationError("Use a link from idealista.com, idealista.pt, or idealista.it.")
return value
class IdealistaUrlCleanMixin:
def clean_idealista_url(self):
field = self.fields["idealista_url"]
return clean_idealista_url_value(self.cleaned_data.get("idealista_url"), required=field.required)
class BootstrapFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
widget = field.widget
if widget.__class__.__name__ == "HiddenInput":
continue
current = widget.attrs.get("class", "")
if widget.__class__.__name__ == "Select":
widget.attrs["class"] = f"form-select {current}".strip()
elif widget.__class__.__name__ == "Textarea":
widget.attrs["class"] = f"form-control {current}".strip()
else:
widget.attrs["class"] = f"form-control {current}".strip()
class PropertyLocationForm(IdealistaUrlCleanMixin, BootstrapFormMixin, forms.ModelForm):
idealista_url = idealista_url_field()
latitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
longitude = forms.DecimalField(required=False, max_digits=9, decimal_places=6, widget=forms.HiddenInput())
class Meta:
model = PropertyEntry
fields = ["address", "latitude", "longitude", "phone", "email", "listing_type", "idealista_url"]
widgets = {
"address": forms.TextInput(attrs={"placeholder": "Type or paste the full address", "autocomplete": "street-address", "data-manual-address": "true"}),
"phone": forms.TextInput(attrs={"placeholder": "+34 600 000 000"}),
"email": forms.EmailInput(attrs={"placeholder": "owner@example.com"}),
"listing_type": forms.Select(),
}
def clean(self):
cleaned = super().clean()
address = cleaned.get("address")
latitude = cleaned.get("latitude")
longitude = cleaned.get("longitude")
if address:
cleaned["latitude"] = None
cleaned["longitude"] = None
return cleaned
if latitude is None or longitude is None:
raise forms.ValidationError("Type or paste an address, or allow location, so this property can be placed on the pinboard.")
return cleaned
class PropertyPhotoForm(IdealistaUrlCleanMixin, BootstrapFormMixin, forms.ModelForm):
idealista_url = idealista_url_field()
class Meta:
model = PropertyEntry
fields = ["photo", "address", "phone", "email", "listing_type", "idealista_url"]
widgets = {
"address": forms.TextInput(attrs={"placeholder": "Optional if the photo contains GPS or visible text"}),
"phone": forms.TextInput(attrs={"placeholder": "Optional contact phone"}),
"email": forms.EmailInput(attrs={"placeholder": "Optional contact email"}),
"listing_type": forms.Select(),
}
def clean_photo(self):
photo = self.cleaned_data.get("photo")
if not photo:
raise forms.ValidationError("Choose a property photo to upload.")
if photo.size > 12 * 1024 * 1024:
raise forms.ValidationError("Please upload an image under 12MB for this MVP.")
return photo
class PropertySuggestionForm(BootstrapFormMixin, forms.ModelForm):
class Meta:
model = PropertySuggestion
fields = ["address", "phone", "email", "listing_type", "note"]
widgets = {
"address": forms.TextInput(attrs={"placeholder": "Correct or missing address"}),
"phone": forms.TextInput(attrs={"placeholder": "Correct or missing phone"}),
"email": forms.EmailInput(attrs={"placeholder": "Correct or missing email"}),
"listing_type": forms.Select(),
"note": forms.Textarea(attrs={"rows": 3, "placeholder": "What should be updated?"}),
}
def clean(self):
cleaned = super().clean()
if not any(cleaned.get(field) for field in self.fields):
raise forms.ValidationError("Add at least one suggested detail.")
return cleaned
class PropertyFlagForm(BootstrapFormMixin, forms.ModelForm):
class Meta:
model = PropertyFlag
fields = ["reason"]
widgets = {
"reason": forms.TextInput(attrs={"placeholder": "Duplicate, spam, private info, already removed..."}),
}
class PropertyIdealistaLinkForm(IdealistaUrlCleanMixin, BootstrapFormMixin, forms.ModelForm):
idealista_url = idealista_url_field(required=True, label="Exact Idealista listing URL")
class Meta:
model = PropertyEntry
fields = ["idealista_url"]