Sure, this breaks normalization, but the scale of this break is tiny. Duplicating four fields each with a max of 30 characters for a total of 120 characters per record is nothing in terms of data when you compare to avoiding the mess of doing lots of profile-to-user joins on very large data sets.
One more thing, I've found that most users don't care about or for the division between their accounts and profiles. They are more than happy with a single form, and if they aren't, well you can still use this profile model to build both account and profile forms.
Alright, enough talking, let me show you how my Profile models tend to look:
from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext_lazy as _ class Profile(models.Model): """ Normalization breaking profile model authored by Daniel Greenfeld """ user = models.OneToOneField(User) email = models.EmailField(_("Email"), help_text=_("Never given out!"), max_length=30) first_name = models.CharField(_("First Name"), max_length=30) last_name = models.CharField(_("Last Name"), max_length=30) # username field notes: # used to improve speed, not editable! # Never changed after original auth.User and profiles.Profile creation! username = models.CharField(_("User Name"), editable=False) def save(self, **kwargs): """ Override save to always populate changes to auth.user model """ user_obj = User.objects.get(username=self.user.username) user_obj.first_name = self.first_name user_obj.last_name = self.last_name user_obj.email = self.email user_obj.is_active = self.is_active user_obj.save() super(Profile,self).save(**kwargs) def get_full_name(self): """ Convenience duplication of the auth.User method """ return "{0} {1}".format(self.first_name, self.last_name) @models.permalink def get_absolute_url(self): return ("profile_detail", (), {"username": self.username}) def __unicode__(self): return self.username
All of this is good, but you have to be careful with emails. Django doesn't let you duplicate existing emails in the django.contrib.auth.model.User model so we want to catch that early and display an elegant error message. Hence this Profile form:
from django import forms from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from profiles.models import Profile class ProfileForm(forms.ModelForm): """ Email validation form authored by Daniel Greenfeld """ def clean_email(self): """ Custom email clean method to make sure the user doesn't use the same email as someone else""" email = self.cleaned_data.get("email", "").strip() if User.objects.filter(email=email).exclude(username=self.instance.user.username): self._errors["email"] = self.error_class(["%s is already in use in the system" % email]) return "" return email class Meta: fields = ( 'first_name', 'last_name', 'email', ) model = Profile