Recently, in our large client project, we had need of fields, in a model form, that accepted multiple types of input, but sanitized the data for the model. For example, the rent field, on the form, needs to handle a rent range (e.g. 900-1200), a single amount, or be overridden or extended by other bits of information, like "call for details" or "on approved credit". Obviously we don't want to have to parse this out every time we read the data. So, enter our fields that tear data apart and put it together every time it passes through.

Model Let's go over our Rent model first. It's an abstract model so we can use it in multiple places (we have more than one logical model in the system that needs to deal with rent, this way we can use it multiple places without having to hold on to a huge amount of joins). We have several other abstract models that perform the same actions as our Rent model, but I won't show them here. from django.core.exceptions import ValidationError from django.db import models class Rent(models.Model): rent_low = models.PositiveIntegerField() rent_high = models.PositiveIntegerField(blank=True, null=True) rent_percent_income = models.FloatField(blank=True, null=True) rent_oac = models.BooleanField(default=False) rent_call_for_details = models.BooleanField(default=False) rent_up_to = models.PositiveIntegerField(blank=True, null=True) class Meta: abstract = True def clean(self): super(Rent, self).clean() if self.rent_high and self.rent_high <= self.rent_low: raise ValidationError("Invalid rent range.") if self.rent_percent_income or self.rent_call_for_details or \ self.rent_up_to: self.rent_low = 0 self.rent_high = None @property def rent(self): if self.rent_call_for_details: return u"Call for details." if self.rent_up_to: return u"Up to $%d" % self.rent_up_to response = "" if self.rent_percent_income: response += u"%g%% of income." % self.rent_percent_income if self.rent_high: response += u"%d-%d" % (self.rent_low, self.rent_high) if self.rent_low > 0 and not self.rent_high: response += u"%d" % self.rent_low if self.rent_oac: response += " On approved credit." return response The one "gotcha" here, that you may not get right away, is the super(Rent, self).clean() at the top of the clean() . We explicitly call it here to make sure the cleaning continues up the chain in our models that extend Rent and the other extended models (as mentioned, we have several models created and used this way). You'll notice in the model we have a field for each of our states, the low and high values of rent, and the other fields that override the rent output value. We also have a class property of rent that we can call on the extending models to get the computed rent value. That property doesn't do anything really interesting except return a value based on the field values. The clean is a little more interesting for how it sets rent_low to 0 and empties out rent_high when their values no longer matter.

Form from django.core.validators import RegexValidator [...] integer_range = RegexValidator( regex=re.compile(r"^[0-9]*(-[0-9]*)?$"), message="Please enter a valid number, or a range in the format: 100-200", code="invalid" ) class FloorplanBaseForm(CommunityKwargModelFormMixin, UserKwargModelFormMixin, forms.ModelForm): rent = forms.CharField(max_length=75, required=False, validators=[integer_range]) class Meta: model = Floorplan def __init__(self, *args, **kwargs): super(FloorplanBaseForm, self).__init__(*args, **kwargs) [...] if self.instance.pk: set_custom_fields(self, ["rent", "deposit", "promo", "sq_ft"]) def clean(self): super(FloorplanBaseForm, self).clean() data = self.cleaned_data [...] if data.get("rent", None) and not data["rent_call_for_details"] and not\ data["rent_percent_income"] and not data["rent_up_to"]: split_ranges(self, "rent") clean_custom_fields(self, data, ["rent", "rent_call_for_details", "rent_up_to", "rent_percent_income"], "You must enter a value for rent.", "rent") return data I've removed bits of the form that deal with other fields like rent since I'm not showing anything about them. This is, more or less, an abstract form. We never render it, but we extend it to support our specific floorplan types. In those extending forms, we tell rent_low and rent_high to be excluded. In this form, though, we provide a single rent field that has a regular expression validator on it to ensure that it contains an interger or two integers separated by a hyphen. This lets the users enter data as more-or-less natural text instead of having to tab through a bunch of fields or enter the data in a weird format. You'll notice three custom methods being called, set_custom_fields , split_ranges , and clean_custom_fields . We'll cover them next.