CHAPTER 10

image

Coordinating Applications

Writing software for a business is hard work. There is no single rule book that outlines which applications to write, how they should be written, how they should interact with each other, or how customizable they should be. The answers to all these concerns are best left to developers on each project, but the examples shown throughout this chapter and Chapter 11 may help you decide the best approach for your project.

Much of a site’s functionality is outward-facing, providing features to users outside the organization. Many times, more functionality is focused inward, intended to help employees perform their daily tasks more effectively. Consider a basic real estate web site that needs to keep track of its clients and available properties. In addition to just displaying properties to the outside world, agents also need to manage those properties and the people who help the process move along.

Rather than building one large application geared toward a specific need, it’s more valuable to try to pull those needs apart, having multiple applications that work together to achieve the final goal. Doing so will require a bit more work in the beginning, but as new features keep getting added, clean separation of applications will help determine what should go where and how everything should work together.

Contacts

While it may seem like everything in the real estate world revolves around property, people are still the most fundamental piece of the puzzle. For example, a given property could have an owner, a real estate agent, and several prospective buyers. These people each fill a different role in the real estate process, but the data necessary to represent them is the same, regardless of the roles they play. They can all be generalized into a “contact” that simply contains the data necessary to identify and communicate with them.

This abstraction provides us a simple model that can be used for people related to a specific property, others who haven’t yet expressed interest in a property, employees within our fictional real estate office itself, and even third-party contacts like quality inspectors and value assessors. What roles each person plays can be defined later by relating them to another model, such as a property.

contacts.models.Contact

Contact information typically consists of things like a person’s name, address, phone number, and email address, some of which can already be captured by Django. The User model from django.contrib.auth contains fields for a person’s first and last names as well as an email address, so all that’s left is to include some of the more real-world contact information. Relating it to User allows a single contact to contain both types of data, while also opening up the possibility of contacts who can log in later.

Because our real estate company will operate in the United States, there are some specific fields available for contact information that will need to validate data according to local customs. To provide support for regional data types, there are a variety of local flavor packages available. Each package, including the django-localflavor-us that we’ll be using, contains a selection of fields for models and forms that are specific to that region’s common data types. Our Contact model can take advantage of PhoneNumberField and USStateField in particular.

from django.db import models
from django.contrib.auth.models import User
from django_localflavor_us import models as us_models
 
class Contact(models.Model):
    user = models.OneToOneField(User)
    phone_number = us_models.PhoneNumberField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField()
    zip_code = models.CharField(max_length=255)
 
    class Meta:
        ordering = ('user__last_name', 'user__first_name')
 
    def __unicode__(self):
        return self.user.get_full_name()

WHY NOT MODEL INHERITANCE?

Chapter 3 explained how one Django model can directly inherit from another, automatically creating a reference similar to the one used here. Because that also adds some extra ease-of-use options, you may be wondering why Contact doesn’t just inherit from User directly.

Model inheritance is best suited for situations where you won’t be using the base model directly because Django doesn’t provide a way to add an inherited instance to existing models. In our case, that means if a User already exists in the database, we wouldn’t be able to create a new Contact based on it. Since there are many other applications, including Django’s admin application, that might create users directly, we need to be able to create contacts for either new or existing users without any trouble.

By using a OneToOneField explicitly, we’re defining the exact same relationship that model inheritance would use, but without the different syntax that restricts us in this case. We lose a few of the syntax benefits that true inheritance provides, but those can be accommodated another way.

Because a contact is essentially just a user with some added attributes, it’s useful to have all the attributes available on a single object. Otherwise, template authors would have to know not only which model a given attribute comes from, but also how to refer to the other model to retrieve those attributes. For example, given a Contact object named contact, the following list shows many of its attributes and methods:

  • contact.user.username
  • contact.user.get_full_name()
  • contact.user.email
  • contact.phone_number
  • contact.address
  • contact.zip_code

This introduces an unnecessary burden on template authors who shouldn’t need to know what type of relationship exists between contacts and users. Model inheritance alleviates this directly by placing all attributes on the contact directly. This same behavior can be achieved here by simply using a set of properties that map various attributes to the related user object behind the scenes.

@property
def first_name(self):
    return self.user.first_name
 
@property
def last_name(self):
    return self.user.last_name
 
def get_full_name(self):
    return self.user.get_full_name()

Not all methods of User make sense on Contact. For instance, the is_anonymous() and is_authenticated() methods are best left on User. Views and templates won’t be using a contact to determine authentication or permissions, so a contact will instead serve as a central location for all aspects of a person’s identity information.

contacts.forms.UserEditorForm

Rather than requiring users to manage their contacts through the admin interface, it’s more useful to have a separate form that can be devoted solely to contact management. This is even more important for contacts than most other models because contacts actually comprise two separate models. Django’s provided ModelForm helper1 maps one form to one model, requiring the contacts application to use two separate forms to manage a single person.

A single form could contain all the fields necessary for both models, but that wouldn’t work with ModelForm because the form would have to contain all the necessary logic for populating and saving the models manually. Instead, two independent forms can be used, with a view tying them together. See the description of contacts.views.EditContact for details.

Because Django provides the User model on its own, it would seem logical to reuse whatever Django uses for managing users. Unfortunately, the forms provided for user management are designed for very different use cases than contact management. There are two forms available, both living at django.contrib.auth.forms, each with a different purpose:

  • UserCreationForm—Intended for the most basic user creation possible, this form accepts only a username and two copies of the password (for verification). The fields needed for contacts—name and email—are unavailable.
  • UserChangeForm—Used for the admin interface, this form contains every field available on the User model. Although this does include name and email, it also includes a host of fields intended for authentication and authorization.

Because neither of these forms really fits the use case for contact management, it makes more sense to simply create a new one for this application. ModelForm makes this easy, allowing a form to just specify those things that differ from the defaults. For contact management, that means only including fields like username, first name, last name, and email address.

from django import forms
from django.contrib.auth.models import User
 
class UserEditorForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email')

With that information, ModelForm can manage the rest of the form’s behavior, based on details provided on the underlying model. All that’s left is to supply a complementary form for managing the new contact-level details.

contacts.forms.ContactEditorForm

The form for managing contacts works very similarly to the one for users, using ModelForm to handle most of the details. The only difference is that the fields used for contacts have much more specific validation requirements than were set out in the Contact model already defined. Phone numbers, for example, are stored in the model as plain text, but they follow a specific format that can be validated by the form. These validations are already provided by the same django-localflavor-us package as used in the model. There are four classes that this ContactEditorForm can use:

  • USStateField for validating a two-letter code against current states
  • USStateSelect to display a list box containing all valid states
  • USPhoneNumberField to validate ten-digit phone numbers, including dashes
  • USZIPCodeField that validates five-digit or nine-digit ZIP codes

image Note  The USStateField also includes the US territories: American Samoa, the District of Columbia, Guam, the Northern Marianas Islands, Puerto Rico, and the U.S. Virgin Islands.

There are others as well, but those four classes will suffice for customizing the validation of a contact. The only remaining editable fields, address and city, don’t have established formats that can be verified programmatically. Applying these overrides, ContactEditorForm looks like this:

from django import forms
from django_localflavor_us import forms as us_forms
 
from contacts.models import Contact
 
class ContactEditorForm(forms.ModelForm):
    phone_number = us_forms.USPhoneNumberField(required=False)
    state = us_forms.USStateField(widget=us_forms.USStateSelect, required=False)
    zip_code = us_forms.USZipCodeField(label="ZIP Code", required=False)
 
    class Meta:
        model = Contact
        exclude = ('user',)

Note the use of exclude here instead of fields, as was used in UserEditorForm. This tells ModelForm to use all fields in the model except those explicitly listed. Since the user will be provided by UserEditorForm, there’s no need to include that as a separate selection here. Address and city don’t need to be provided as explicit field declarations because ModelForm will use standard text fields for those automatically.

contacts.views.EditContact

Contacts are made up of two models—and thus, two forms—but the users who manage those contacts should need to deal with only one form that contains all the appropriate fields. Django’s form-specific generic views don’t really help us here because they’re only designed for one form per view. We’re combining two forms, which would require either some pretty heavy modification to UpdateView or a new class that combines two forms. Neither of these are pleasant options, so we’ll go with a more practical approach with a slightly more generic view, assembling the form handling behavior manually.

The first choice to make is what arguments to accept. Since this view will render the forms in a template, it’s best to accept a template name in the URL configuration, so we’ll rely on Django’s TemplateView, which takes care of that on its own. By simply subclassing django.views.generic.TemplateView, our new view will automatically accept a template_name argument in its configuration and provide a method for rendering the template to the response.

In order to pull up a single contact for editing, the view must also have a way of identifying which contact should be used. This identifier must be unique and should be reasonably understandable for users to look at. Since every contact relates to a user and every user has a unique username, that username will serve the purpose quite well.

from django.views.generic import TemplateView
 
class EditContact(TemplateView):
    def get(self, request, username=None):
        pass

Notice that the username is optional. Having an optional identifier allows this same view to be used for adding new contacts as well as editing existing ones. Both situations require essentially the same behavior: accept contact details from a user, check them for valid data and save them in the database. The only difference between adding and editing is whether a Contact object already exists.

With this goal in mind, the view must be prepared to create a new Contact object and possibly even a new User, should either of them not exist. There are four distinct situations that must be handled:

  • A username is provided and both a User and a Contact exist for that username. The view should proceed to edit both existing records.
  • A username is provided and a User exists, but no Contact is associated with it. A new Contact should be created and associated with the User, so that both can be edited.
  • A username is provided and no User exists for it, which also means no Contact exists. Requesting a username implies an existing user, so requesting one that doesn’t exist should be considered an error. In this case, this is an appropriate use of the HTTP 404 (Not Found) error code.
  • No username is provided, meaning that existing users and contacts are irrelevant. New User and Contact objects should be created, ignoring any that might already exist. The form will ask for a new username to be provided.

image Tip  Using a 404 error code doesn’t always mean you have to serve a generic “Page Not Found” page. You can supply whatever content you like to the HttpResponseNotFound class instead of the default HttpResponse class. These examples simply rely on the standard 404 error page for simplicity, but it may make more sense for your site to show a 404 page that says something like, “The contact you requested doesn’t exist yet.” This allows you to take advantage of a known HTTP status code, while still displaying more useful messages to your users.

These situations can be handled rather easily in a get_objects() method. It’s factored out into its own method because we’ll end up needing it from both the get() and post() methods.

from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.contrib.auth.models import User
 
from contacts.models import Contact
 
class EditContact(TemplateView):
    def get_objects(self, username):
        # Set up some default objects if none were defined.
        if username:
            user = get_object_or_404(User, username=username)
            try:
                # Return the contact directly if it already exists
                contact = user.contact
            except Contact.DoesNotExist:
                # Create a contact for the user
                contact = Contact(user=user)
        else:
            # Create both the user and an associated contact
            user = User()
            contact = Contact(user=user)
 
        return user, contact
 
    def get(self, request, username=None):
        pass

Once both objects are known to exist, the view can then display the forms for both of those objects, so the user can fill out the information. This is handled through the get() method.

from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.contrib.auth.models import User
 
from contacts.models import Contact
from contacts import forms
 
class EditContact(TemplateView):
    def get_objects(self, username):
        # Set up some default objects if none were defined.
        if username:
            user = get_object_or_404(User, username=username)
            try:
                # Return the contact directly if it already exists
                contact = user.contact
            except Contact.DoesNotExist:
                # Create a contact for the user
                contact = Contact(user=user)
        else:
            # Create both the user and an associated contact
            user = User()
            contact = Contact(user=user)
 
        return user, contact
 
    def get(self, request, username=None):
        user, contact = self.get_objects()
        return self.render_to_response({
            'username': username,
            'user_form': forms.UserEditorForm(instance=user),
            'contact_form': forms.ContactEditorForm(instance=contact),
        })

The view can then proceed to process the form and populate those objects with the appropriate information. It must instantiate, validate, and save each form independently of the other. This way, each form needs to know only about the data it’s designed to manage, while the view can tie the two together.

If both forms were saved correctly, the view should redirect to a new URL where the edited contact information can be viewed. This is especially useful for new contacts, which wouldn’t have a URL assigned to them prior to processing the form. In any other case, including when the form is first viewed—that is, no data has been submitted yet—and when the submitted data fails to validate, the view should return a rendered template that can display the appropriate form.

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.contrib.auth.models import User
 
from contacts.models import User
from contacts import forms
 
class EditContact(TemplateView):
    def get_objects(self, username):
        # Set up some default objects if none were defined.
        if username:
            user = get_object_or_404(User, username=username)
            try:
                # Return the contact directly if it already exists
                contact = user.contact
            except Contact.DoesNotExist:
                # Create a contact for the user
                contact = Contact(user=user)
        else:
            # Create both the user and an associated contact
            user = User()
            contact = Contact(user=user)
 
        return user, contact
 
    def get(self, request):
        user, contact = self.get_objects()
        return self.render_to_response({
            'username': user.username,
            'user_form': forms.UserEditorForm(instance=user),
            'contact_form': forms.ContactEditorForm(instance=contact),
        })
 
    def post(self, request):
        user, contact = self.get_objects()
 
        user_form = forms.UserEditorForm(request.POST, instance=user)
        contact_form = forms.ContactEditorForm(request.POST, instance=contact)
        if user_form.is_valid() and contact_form.is_valid():
            user = user_form.save()
            # Attach the user to the form before saving
            contact = contact_form.save(commit=False)
            contact.user = user
            contact.save()
            return HttpResponseRedirect(reverse('contact_detail',
                                        kwargs={'slug': user.username}))
 
        return self.render_to_response(self.template_name, {
                'username': user.username,
                'user_form': user_form,
                'contact_form': contact_form,
        })

Admin Configuration

Because this application has its own views for adding and editing contacts, there isn’t much need to work with the admin interface. But since the Property model described later will both relate to Contact and make heavy use of the admin, it’s a good idea to configure a basic interface for managing contacts.

from django.contrib import admin
 
from contacts import models
 
class ContactAdmin(admin.ModelAdmin):
    pass

admin.site.register(models.Contact, ContactAdmin)

It doesn’t offer the convenience of editing the User and Contact models at the same time, but does offer value for related models that are managed through the admin.

URL Configuration

In addition to adding and editing contacts, this application must also supply a way to view all existing contacts and details about any specific contact. These features in turn require four distinct URL patterns to be accounted for in the contact application’s URL configuration. Two of these will map to the edit_contact view described in the previous section, while two more will be mapped to Django’s own generic views.

  • /contacts/—The list of all existing contacts, with links to individual contact details
  • /contacts/add/—An empty form where a new contact can be added
  • /contacts/{username}/—A simple view of all the contact information for a given user
  • /contacts/{username}/edit/—A form populated with any existing data, where that data can be changed and new data can be added

The /contacts/ portion at the beginning of these URLs isn’t integral to any of the contact views themselves; it’s a site-level distinction, pointing to the contacts application as a whole. Therefore, it won’t be included in the URL configuration for the application, but in the configuration for the site. What remains is a set of URL patterns that can be made portable across whatever URL structure the site requires.

The first pattern, the list of all existing contacts, is quite simple on the surface. Once /contacts/ is removed from the URL, there’s nothing left—rather, all that’s left is an empty string. An empty string is indeed easy to match with a regular expression, but the view that we’ll use for it, django.views.generic.ListView, requires some additional customization to behave properly.

To start, it requires both a queryset and a template_name, controlling where it can find the objects and how they should be displayed. For the purposes of this application, all contacts are available, without any filtering. The template name could be whatever works best according to your style; I’ll call it "contacts/contact_list.html".

By showing all contacts, the list could get quite long, so it would be more useful to be able to split up the results across multiple pages, if necessary. ListView provides this as well, by way of its paginate_by argument. If provided, it supplies the maximum number of results that should be shown on a single page before spilling over to the next. The template can then control how page information and links to related pages are displayed.

from django.conf.urls.defaults import *
from django.views.generic import ListView
 
from contacts import models
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Contact.objects.all(),
                         template_name='contacts/list.html',
                         paginate_by=25),
        name='contact_list'),
)

Next is the URL for adding new contacts, using the custom EditContact view. Like the contact list, the regular expression for this URL pattern is quite simple, as it doesn’t contain any variables to capture. In addition to matching the add/ portion of the URL, this pattern just needs to point to the correct view and pass along a template name.

from django.conf.urls.defaults import *
from django.views.generic import ListView

from contacts import models, views
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Contact.objects.all(),
                         template_name='contacts/list.html',
                         paginate_by=25),
        name='contact_list'),
    url(r'^add/$', views.EditContact.as_view(
            template_name='contacts/editor_form.html',
        ), name='contact_add_form'),
)

The remaining URL patterns both require a username to be captured from the URL itself, which is then passed to the associated view. Usernames follow a fairly simple format, allowing letters, numbers, dashes and underscores. This can be represented by the regular expression [w-]+, a pattern often used for recognizing textual identifiers known commonly as “slugs.”

image Note  Slugs have their roots in the news industry, just like Django itself. A slug is the name an article is given for internal communications within a news organization, prior to going to print. Just before being printed, a proper title is given to the article, but the slug remains a way to uniquely reference a specific article, whether it’s available for public viewing or not.

The first of these views to write, the basic contact detail page, will use another of Django’s provided generic views, django.views.generic.DetailView, so some care has to be taken with the name of the variable the username is assigned to. The custom EditContact view calls it username, but DetailView doesn’t know to look for something with that name. Instead, it allows a URL pattern to capture a slug variable, which functions the same way. Another requirement is to supply a slug_field argument that contains the name of the field to match the slug against.

Ordinarily, this slug_field argument would be the name of the field on the model where the slug value can be found. Like most generic views, though, DetailView requires a queryset argument to be given a valid QuerySet, from which an object can be retrieved. The view then adds a get() call to the QuerySet, using the slug_field/slug combination to locate a specific object.

This implementation detail is important, because it allows a URL pattern additional flexibility that wouldn’t be available if the view matched the slug_field to an actual field on the model. More specifically, slug_field can contain a lookup that spans related models, which is important given the fact that contacts are made up of two different models. The URL pattern should retrieve a Contact object by querying the username of its related User object. To do this, we can set slug_field to "user__username".

from django.conf.urls.defaults import *
from django.views.generic import ListView, DetailView
 
from contacts import models, views
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Contact.objects.all(),
                         template_name='contacts/list.html',
                         paginate_by=25),
        name='contact_list'),
    url(r'^add/$', views.EditContact.as_view(
            template_name='contacts/editor_form.html',
        ), name='contact_add_form'),
    url(r'^(?P<slug>[w-]+)/$',
        DetailView.as_view(queryset=models.Contact.objects.all(),
                           slug_field='user__username',
                           template_name='contacts/list.html'),
        name='contact_detail'),
)

The last URL pattern, editing an individual contact, closely follows the pattern used to add a new contact. The only difference between the two is the regular expression used to match the URL. The previous pattern didn’t capture any variables from the URL, but this one will need to capture a username in order to populate the form’s fields. The expression used to capture the username will use the same format as the one from the detail view, but will use the name of username instead of slug.

from django.conf.urls.defaults import *
from django.views.generic import ListView, DetailView
 
from contacts import models, views
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Contact.objects.all(),
                         template_name='contacts/list.html',
                         paginate_by=25),
        name='contact_list'),
    url(r'^add/$', views.EditContact.as_view(
            template_name='contacts/editor_form.html',
        ), name='contact_add_form'),
    url(r'^(?P<slug>[w-]+)/$',
        DetailView.as_view(queryset=models.Contact.objects.all(),
                           slug_field='user__username',
                           template_name='contacts/list.html'),
        name='contact_detail'),
    url(r'^(?P<username>[w-]+)/edit/$', views.EditContact.as_view(
            template_name='contacts/editor_form.html',
        ), name='contact_edit_form'),
)

The only things missing from this application now are the four templates mentioned in the URL patterns. Since this book is targeted at development, rather than design, those are left as an exercise for the reader.

Real Estate Properties

The meat and potatoes of a real estate company is, of course, real estate. Individual buildings or pieces of land are typically called properties, but that term shouldn’t be confused with Python’s notion of properties, described in Chapter 2. This name clash is unfortunate, but not unexpected; it’s quite common for entirely different groups of people to use the same terms with different meanings.

When this situation arises in general, it’s often best to use whatever term is most widely understood by your audience. When meeting with real estate agents, you should be able to use “property” to refer to a piece of real estate, without any confusion or explanation. When talking to programmers, “property” might refer to a model, an object, or a built-in function.

Python’s property decorator is useful for many situations, but the majority of this chapter will be focusing on other Python techniques. In light of this, the term “property” will refer to a real estate property unless otherwise specified.

properties.models.Property

The most basic item in a property management application is a Property. In real estate terms, a property is simply a piece of land, often with one or more buildings attached. This includes things like houses, retail stores, industrial complexes and undeveloped land. Although that covers a wide range of options, there are a number of things that are shared across the spectrum. The most basic of these shared features is that all properties have an address, which is made up of a few components:

from django.db import models
from django_localflavor_us import models as us_models
 
class Property(models.Model):
    slug = models.SlugField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField()
    zip = models.CharField(max_length=255)

 
    class Meta:
        verbose_name_plural = 'properties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.address, self.city)

This model also includes a slug, which will be used to identify the property in a URL.

image Note  This model uses just one field for the address, while many address forms use two. Two address lines are always appropriate for mailing addresses, because they allow for divisions within a building, such as apartments or office suites. Real estate is often focused on the building itself and the land it sits on, rather than how the building is divided, so one field will suffice. Condominiums are subdivisions of a building that are sold individually, so in markets that deal in condos, an extra address field would be necessary to uniquely identify properties within a building.

In addition to being able to locate the property, more fields can be added to describe the size of the property and the building that occupies it. There are a number of ways to contain this information, and Property will make use of more than one, all of which are optional. Typically, all of these would be filled before a listing is made public, but the database should support managing properties with incomplete information, so agents can populate it as information becomes available.

from django.db import models
from django_localflavor_us import models as us_models
 
class Property(models.Model):
    slug = models.SlugField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField()
    zip = models.CharField(max_length=255)
    square_feet = models.PositiveIntegerField(null=True, blank=True)
    acreage = models.FloatField(null=True, blank=True)
 
    class Meta:
        verbose_name_plural = 'properties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.address, self.city)

The square_feet field refers to the available area inside the building. When designing or remodeling a building, it’s necessary to break this down into individual room dimensions, but for the task of buying and selling property, the total amount works just fine on its own. The acreage field represents the total land area occupied by the property, as measured in acres—a unit equal to 43,560 square feet.

image Tip  If an agent does obtain the sizes of individual rooms within the property, those can be included as individual property features using the Feature model described in the “properties.models.Feature” section later in this chapter.

So far, most aspects of the Property model have been focused on describing the property itself, but there are also aspects of the sales process that can be included. Price is perhaps the most important aspect of a property listing, and even though it’s not a physical attribute, each property can have only one price at a time, so it still makes sense to have it as a field here. The next chapter will explain how we’ll keep track of past prices, but this model will just store the current price.

Another such attribute is the property’s status—where in the sales process it currently is. For new entries in the database, there may not be any status at all. Perhaps some property information is being recorded for a home owner who is considering selling but hasn’t yet decided to list it on the market. Once the owner decides to sell, it can be listed for public consideration and the rest of the process begins.

from django.db import models
from django_localflavor_us import models as us_models
 
class Property(models.Model):
    LISTED, PENDING, SOLD = range(3)
    STATUS_CHOICES = (
        (LISTED, 'Listed'),
        (PENDING, 'Pending Sale'),
        (SOLD, 'Sold'),
    )

    slug = models.SlugField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField(max_length=2)
    zip = models.CharField(max_length=255)
    square_feet = models.PositiveIntegerField(null=True, blank=True)
    acreage = models.FloatField(null=True, blank=True)
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              null=True, blank=True)
    price = models.PositiveIntegerField(null=True, blank=True)
 
    class Meta:
        verbose_name_plural = 'properties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.address, self.city)

In addition to the attributes that can be stored once on a model, there are other features of properties that may occur more than once or in many varying combinations. These amenities, such as fireplaces, basements, garages, attics and appliances, aren’t part of a checklist of features that every property either does or doesn’t have. This makes it difficult—if not impossible—to create a field for each feature, without having to modify the model’s structure every time a new property comes along that doesn’t fit with prior assumptions.

Instead, features should be stored in another model, indicating which features are present on the property and describing them in detail. Another model can step in to generalize these features into common types, so they can be browsed and searched. For instance, a user might be interested in finding all properties with a fireplace. Having a model dedicated to define a fireplace, with a related model describing individual fireplaces, helps enable this type of behavior. See the “properties.models.Feature” and “properties.models.PropertyFeature” sections later for more details on how this works.

Properties also have a number of people associated with them, such as an owner, real estate agent, architect, builder and possibly several prospective buyers. These all qualify as contacts and are stored using the Contact model already defined. For the purposes of making it as generic as possible, they will be called “interested parties,” since each person has some stake in the dealings regarding the property.

from django.db import models
from django_localflavor_us import models as us_models
 
class Property(models.Model):
    LISTED, PENDING, SOLD = range(3)
    STATUS_CHOICES = (
        (LISTED, 'Listed'),
        (PENDING, 'Pending Sale'),
        (SOLD, 'Sold'),
    )
 
    slug = models.SlugField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField()
    zip = models.CharField(max_length=255)
    square_feet = models.PositiveIntegerField(null=True, blank=True)
    acreage = models.FloatField(null=True, blank=True)
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              null=True, blank=True)
    price = models.PositiveIntegerField(null=True, blank=True)
 
    features = models.ManyToManyField('Feature', through='PropertyFeature')
    interested_parties = models.ManyToManyField(Contact,
                                                through='InterestedParty')
 
    class Meta:
        verbose_name_plural = 'properties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.address, self.city)

Not all properties should be listed publicly. Until a property is listed, and after it is sold, it should be hidden from the general public, available only to staff members to manage. Rather than typing a query for this every time a property is needed for public display, it’s easy to create a custom manager with a method to narrow down the list.

class PropertyManager(models.Manager):
    def listed(self):
        qs = super(PropertyManager, self).get_query_set()
        return qs.filter(models.Q(status=Property.LISTED) |
                         models.Q(status=Property.PENDING))

This can be attached to a model through a simple assignment; any name can be used, but the convention is to call the standard manager objects, so this will do so.

from django.db import models
from django_localflavor_us import models as us_models
 
class Property(models.Model):
    LISTED, PENDING, SOLD = range(3)
    STATUS_CHOICES = (
        (LISTED, 'Listed'),
        (PENDING, 'Pending Sale'),
        (SOLD, 'Sold'),
    )
 
    slug = models.SlugField()
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=255)
    state = us_models.USStateField()
    zip = models.CharField(max_length=255)
    square_feet = models.PositiveIntegerField(null=True, blank=True)
    acreage = models.FloatField(null=True, blank=True)
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              null=True, blank=True)
    price = models.PositiveIntegerField(null=True, blank=True)

    features = models.ManyToManyField('Feature', through='PropertyFeature')
    interested_parties = models.ManyToManyField(Contact,
                                                through='InterestedParty')
 
    objects = PropertyManager()
 
    class Meta:
        verbose_name_plural = 'properties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.address, self.city)

properties.models.Feature

A feature is simply something noteworthy that the property offers. It could be a common necessity, such as a basement or a laundry room, but it could also be very distinct, like a fireplace or a sun room. These features are often listed in an attempt to distinguish one property from another, since buyers often have a list of features they’d like to have.

The Feature model contains just the information necessary to define a particular type of feature. Rather than describing a specific fireplace, a Feature simply defines what a fireplace is, offering an anchor point for individual fireplaces to relate to. That way, properties can be searched by feature, using this model as a starting point.

class Feature(models.Model):
    slug = models.SlugField()
    title = models.CharField(max_length=255)
    definition = models.TextField()
 
    def __unicode__(self):
        return self.title

properties.models.PropertyFeature

Instead of defining a feature at a high level, specific details are far more useful when viewing a specific property. The PropertyFeature model forms a bridge between Property and Feature, providing a way to describe the individual features of a specific property.

class PropertyFeature(models.Model):
    property = models.ForeignKey(Property)
    feature = models.ForeignKey(Feature)
    description = models.TextField(blank=True)
 
    def __unicode__(self):
        return unicode(self.feature)

properties.models.InterestedParty

Contacts that have an interest in a particular property come in many varieties, from owners and buyers to real estate agents and safety inspectors. Each of these people can be connected to a specific property by way of a relationship that includes some detail about the nature of the relationship.

from contacts.models import Contact
 
class InterestedParty(models.Model):
    BUILDER, OWNER, BUYER, AGENT, INSPECTOR = range(5)
    INTEREST_CHOICES = (
        (BUILDER, 'Builder'),
        (OWNER, 'Owner'),
        (BUYER, 'Buyer'),
        (AGENT, 'Agent'),
        (INSPECTOR, 'Inspector'),
    )
 
    property = models.ForeignKey(Property)
    contact = models.ForeignKey(Contact)
    interest = models.PositiveSmallIntegerField(choices=INTEREST_CHOICES)
 
    class Meta:
        verbose_name_plural = 'interested parties'
 
    def __unicode__(self):
        return u'%s, %s' % (self.contact, self.get_interest_display())

image Note  These roles can overlap, such as an owner who is also the builder and the real estate agent. Some databases allow for a field to be used as a bitmask, where you can toggle individual bits to indicate which roles a contact fulfills. Since Django doesn’t support creating or searching on those types of fields, we instead store just one role per row; a contact with multiple roles would simply use multiple rows to describe the situation.

Admin Configuration

Property listings are meant to be viewed by the general public, but only edited by employees of the real estate agency who have extensive training and experience in the field and can be trusted with this task. That description is the same as the intended audience for Django’s built-in admin application.

With the features available from the admin, it’s easy to put together interfaces for users to be able to edit and maintain all the various models in the properties application. No separate editor views are required, and only minor changes are necessary to customize the admin to work with these models in a user-friendly way.

“THE ADMIN IS NOT YOUR APP”

If you spend much time in the Django community, you’ll likely run across the phrase, “The admin is not your app.” The general sentiment being conveyed here is that the admin has a fairly limited focus, far more limited than most sites. It’s expected to be used by trusted staff members who can work with a more rudimentary data-entry interface. When you find yourself struggling to find ways to get the admin to do what you want, chances are you need to start writing your own views and stop relying on the admin.

That doesn’t mean that the admin is only ever useful during development. If a basic editing interface is suitable for staff members to work with, it can save both time and energy. With a few simple customizations, the admin can perform most of the common tasks that such editing interfaces require. The contacts application described earlier in this chapter couldn’t rely on the admin because it required two forms to be combined, which is outside the scope the admin was intended for.

For properties, the admin is quite capable of generating an adequate interface. Since only staff members will need to edit property data, there’s no need to create custom views that integrate with the rest of the site. More of your time can be focused on building out the public-facing aspects of the application.

The first model to set up is Property, but due to the workings of related models, some configurations for PropertyFeature and InterestedParty need to be in place first. These are each configured using a simple class that tells the admin to add them to the property editor as tables at the end of the page. In addition to any existing relationships, the admin should show one empty record that can be used to add a new relationship.

from django.contrib import admin

from properties import models
 
class InterestedPartyInline(admin.TabularInline):
    model = models.InterestedParty
    extra = 1
 
class PropertyFeatureInline(admin.TabularInline):
    model = models.PropertyFeature
    extra = 1

In order to customize some of the more specialized fields on the Property model’s admin page, a custom ModelForm subclass is required. This allows the form to specify what widgets should be used for its state and zip fields, since they adhere to a more specific format than just a free-form text field. All the other fields can remain as they were, so they don’t need to be specified on this form.

from django.contrib import admin
from django import forms
from django_localflavor_us import forms as us_forms
 
from properties import models
 
class InterestedPartyInline(admin.TabularInline):
    model = models.InterestedParty
    extra = 1
 
class PropertyFeatureInline(admin.TabularInline):
    model = models.PropertyFeature
    extra = 1
 
class PropertyForm(forms.ModelForm):
    state = us_forms.USStateField(widget=us_forms.USStateSelect)
    zip = us_forms.USZipCodeField(widget=forms.TextInput(attrs={'size': 10}))
 
    class Meta:
        model = models.Property

Now we can finally configure the admin interface for Property itself. The first customization is to use the PropertyForm instead of the plain ModelForm that would be used normally.

class PropertyAdmin(admin.ModelAdmin):
    form = PropertyForm
 
admin.site.register(models.Property, PropertyAdmin)

Within that form, not all the fields should display in a simple list from top to bottom. The full address can be displayed in a more familiar format by putting the city, state, and zip fields in a tuple so they all end up on the same line. The slug is placed next to the address, since it will be populated based on that information. The sales fields can be placed in a separate grouping, as can the fields related to size, with a heading to set each group apart.

class PropertyAdmin(admin.ModelAdmin):
    form = PropertyForm
    fieldsets = (
        (None, {'fields': (('address', 'slug'),
                           ('city', 'state', 'zip'))}),
        ('Sales Information', {'fields': ('status',
                                           'price')}),
        ('Size', {'fields': ('square_feet',
                             'acreage')}),
    )

The related models are added to a tuple called inlines, which controls how other models are attached to an existing admin interface. Because they were already configured in their own classes, all we need to do here is add them to the PropertyAdmin.

class PropertyAdmin(admin.ModelAdmin):
    form = PropertyForm
    fieldsets = (
        (None, {'fields': (('address', 'slug'),
                           ('city', 'state', 'zip'))}),
        ('Sales Information', {'fields': ('status',
                                           'price')}),
        ('Size', {'fields': ('square_feet',
                             'acreage')}),
    )
    inlines = (
        PropertyFeatureInline,
        InterestedPartyInline,
    )

Lastly, the declaration for generating the slug requires a dictionary assigned to the prepopulated_fields attribute. The key in this dictionary is the name of the SlugField to generate automatically. The associated value is a tuple of field names where the slug’s value should be pulled from. All properties should be unique according to their address and ZIP, so those two fields can be combined to form a slug for the property.

class PropertyAdmin(admin.ModelAdmin):
    form = PropertyForm
    fieldsets = (
        (None, {'fields': (('address', 'slug'),
                           ('city', 'state', 'zip'))}),
        ('Sales Information', {'fields': ('status',
                                           'price')}),
        ('Size', {'fields': ('square_feet',
                             'acreage')}),
    )
    inlines = (
        PropertyFeatureInline,
        InterestedPartyInline,
    )
    prepopulated_fields = {'slug': ('address', 'zip')}

image Note  Slug fields are prepopulated using JavaScript while editing the model instance in the admin application. This is a useful convenience, saving the time and trouble of having to visit a separate field as long as the default slug is suitable. When creating objects in Python, the only way a field gets populated without an explicit value is through a function passed in as its default argument or through the field’s pre_save() method.

With that in place, the only model left to set up is Feature. Since it’s a simpler model than Property, the admin declaration is considerably simpler as well. There are three fields to arrange and a SlugField to configure.

class FeatureAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': (('title', 'slug'), 'definition'),
        }),
    )
    prepopulated_fields = {'slug': ('title',)}
 
admin.site.register(models.Feature, FeatureAdmin)

URL Configuration

Since the actual management of properties is handled by the admin interface, the only URLs to configure are for users to view property listings. These types of read-only views are best handled by Django’s own generic views, configured to work with the models in question. Specifically, these URLs will use the same generic list and detail views that we used for contacts earlier in the chapter.

A view for the property listings can be set up using the ListView. This view requires a QuerySet to locate items, which is where the PropertyManager proves useful. Its listed() method narrows down the query to the items that should be displayed for the general public.

from django.conf.urls.defaults import *
from django.views.generic import ListView, DetailView
 
from properties import models
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Property.objects.listed(),
                         template_name='properties/list.html',
                         paginate_by=25),
        name='property_list'),
)

Although the detail view requires fewer configuration options—since it doesn’t need the paginate_by argument—the regular expression gets a bit more complicated. Looking up a property in a URL is best handled by a slug, but slugs can be typically made up of any combination of letters, numbers, and basic punctuation. The slug for a property is a more specific format, starting with the street number from the address and ending with a ZIP code. The street name in the middle could still be anything, but it’s always surrounded by numbers.

This simple fact helps shape the regular expression used to capture the slug from the URL. The idea here is to be as specific as reasonably possible, so that one URL pattern doesn’t interfere with others that might look for similar patterns. The URL configuration for the detail view of a single Property object looks like this:

from django.conf.urls.defaults import *
from django.views.generic import ListView, DetailView
 
from properties import models
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(queryset=models.Property.objects.listed(),
                         template_name='properties/list.html',
                         paginate_by=25),
        name='property_list'),
    url(r'^(?P<slug>d+-[w-]+-d+)/$',
        DetailView.as_view(queryset=models.Property.objects.listed(),
                           slug_field='slug',
                           template_name='properties/detail.html'),
        name='property_detail'),
)

This regular expression adds explicit rules for digits at the beginning and end of the slug, separate from the middle portion by dashes. This will match property slugs just as well as the usual [w-]+, but with an important added bonus: These URLs can now be placed at the site’s root. Having a more specific regular expression allows for smaller URLs like http://example.com/123-main-st-12345/ . This is a great way to keep URLs small and tidy, while not impeding on other URL configurations that might also use slugs.

Now What?

With a few applications in place and ready to work together, a basic site takes shape. The next chapter will show how to bring together all the tools you’ve learned so far to add significant new features to applications like these.

1 http://prodjango.com/modelform/

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset