Tag Archive : Django

/ Django

Django: Extending the User Model

June 22, 2019 | Coding | No Comments

programming code

I love Django. It’s a wonderful framework and ORM. Whether you’re building a full website or a restful api, its got you covered. But if you are like me, you HATE the default user model. This generally comes down to it having both a username & e-mail field; and the e-mail field isn’t even unique.

In my world, the e-mail address is the perfect, unique username. So how do you make this happen?

If your app is out, live in the world all ready, you’re out of luck. But if you are firing up a new one, there are a couple of options. Just DON’T RUN THE MIGRATIONS until the new user model is ready.

First thing after starting the new project is starting the users app.

(ENV) $: python manage.py startapp users

Next we are going to create our new manager and user model in users. DO NOT RUN MIGRATIONS! Did you get that? I’ll tell you when.


from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _

class UserManager(BaseUserManager):
    Our custom user model that uses email as the unique username
    def create_user(self, email, password, **extra_fields):
        Create user.
        if not email:
            raise ValueError(_('The Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        return user

    def create_superuser(self, email, password, **extra_fields):
        Create superuser.
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(_('Superuser must have is_superuser=True.'))
        return self.create_user(email, password, **extra_fields)

When we create our user model, we are going to extend AbstractUser. We can extend BaseAbstractUser as well, but that is a lot more work and I really don’t see a need to ever do that at all.

# users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _

from .managers import UserManager

class User(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'

    objects = UserManager()

    def __str__(self):
        return self.email

Notice the username = None and USERNAME_FIELD = ’email’. We can also add any other fields we want here now. Instead of adding a separate 1 to 1 profile model, we can put things right on the user. Such as phone number, or company name, customer id #, etc.

Now we can add it to our settings.py



AUTH_USER_MODEL = 'users.User'

You can go ahead and create and run the migrations now and run python manage.py createsuperuser to set yourself up.

Now we need Forms and Admin integration.

# users/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import User

class UserCreationForm(UserCreationForm):
    class Meta(UserCreationForm):
        model = User
        fields = ('email',)

class UserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = ('email',)
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import UserCreationForm, UserChangeForm
from .models import User

class UserAdmin(UserAdmin):
    add_form = UserCreationForm
    form = UserChangeForm
    model = User
    list_display = ('email', 'is_staff', 'is_active',)
    list_filter = ('email', 'is_staff', 'is_active',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
    search_fields = ('email',)
    ordering = ('email',)

admin.site.register(User, UserAdmin)

That’s it. You have your own customized user model that you can extend further at will.

Referencing the User model:

There are two ways to get the user model now. You can’t simply import AbstractUser anymore. The preferred method is to use get_user_model() but you can also use settings.AUTH_USER_MODEL. For instance, if you want a foreign key relationship to the user.

# Example
User = get_user_model()
## or User = settings.AUTH_USER_MODEL ## but get_user_model() is preferred

class OurTestModel(models.Model):
     customer = models.ForeignKey(User, on_delete=models.CASCADE)


To make this a simpler process I’ve created a GitHub repo that you can drop into your Django project and get started right away.

Django: Unique Slug for Urls

June 21, 2019 | Coding | No Comments

programming code

Let’s imagine a situation where you want to create, lets say, a blog post or a news article in Django. You want to use take the Title and turn it into a slug that can be used in the URL. Meaning, it needs to be unique, or at least, unique for the date. Depending on the URL parameters you with you use.

You could make it a editable field with a unique property, but its probably better to abstract it away and check recursively for existing matches and increment a number on the end. Simplifying the process.

First, lets define the model as such.

class Article(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(unique=True, blank=True)
    body = models.TextField()
    published = models.BooleanField(default=True)
    published_on = models.DateTimeField(blank=True)
    updated_on = models.DateTimeField(blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

Next we will need to override the models save method.

def save(self, *args, **kwargs):
        self.updated_on = timezone.now()
        if not self.id:
            self.published_on = timezone.now()

            # find unique slug
            cnt = 0
            while not self.slug:
                if cnt > 0:
                    # Create a new slug string with the counter
                    slug = "{}-{}".format(slugify(self.title), cnt)
                    slug = slugify(self.title)

                # check to see if a article exists with that slug
                a = Article.objects.filter(slug=slug)
                if not a:
                    # Assign the slug, this will end the loop.
                    self.slug = slug
                    cnt += 1
        super(Article, self).save(*args, **kwargs)

You could easily abstract that loop out into a function.