4
votes

In django.contrib.auth.models.User, both first_name and last_name fields have blank=True. How can I make them blank=False, null=False in my own model?

Here is my implementation, which basically follows the instructions of Extending the existing User model:

models.py

class Fellow(models.Model):

    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    first_name = models.CharField(
        _("first_name"),
        max_length=30,
    )
    last_name = models.CharField(
        _("last_name"),
        max_length=30,
    )

    # other fields omitted

from . import signals

signals.py

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

from .models import Fellow


@receiver(post_save, sender=User)
def create_fellow_on_user_create(sender, instance, created, **kwargs):
    if created:
        Fellow.objects.create(user=instance)

However, I got an error when testing in python manage.py shell:

>>> f = Fellow.objects.create(username='username', password='passwd')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/sunqingyao/Envs/django_tutorial/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/sunqingyao/Envs/django_tutorial/lib/python3.6/site-packages/django/db/models/query.py", line 392, in create
    obj = self.model(**kwargs)
  File "/Users/sunqingyao/Envs/django_tutorial/lib/python3.6/site-packages/django/db/models/base.py", line 571, in __init__
    raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
TypeError: 'username' is an invalid keyword argument for this function
2
You should derive your model from the original User class.Klaus D.
@KlausD. You mean subclassing User directly? But the example code from the documentation writes class Employee(models.Model): instead of class Employee(User): ...nalzok
@KlausD. Doing this gives me an error: django.core.exceptions.FieldError: Local field 'first_name' in class 'Fellow' clashes with field of the same name from base class 'User'.nalzok

2 Answers

1
votes

You have misunderstood the tutorial. The employee example you are referring to explains how to add to the User this is done by way of a OneToOneField that leaves the original User model intact but for all practical purposes it's as if the User had a new field called department. This is implemented by creating two separate tables in the database.

There is one caveat, in the Employee and the Fellow you can't directly create new instances in the way you have tried without first creating a User instance. That's because neither of those models directly own a username field. So do it in two steps

user = User(username='Sun')   
user.set_password('123')
user.save()

f = Fellow(user=user)

But this approach is still less than desirable. you have a first_name and last_name field in both models. It will always lead to confusion. The proper method is to subclass AbstractBaseUser

class Fellow(AbstractBaseUser):

   first_name = models.CharField(max_length=254)

AbstractBaseUser is a bare bones User (see the code: https://github.com/django/django/blob/master/django/contrib/auth/base_user.py#L47) it does not have a first_name and last_name so you can add them with the behaviour that you want.

0
votes

The very first line of documentation you are referring to says

There are two ways to extend the default User model without substituting your own model

The keyword is without substituting

I see 3 options here:

1) Derive you User class from AbstractBaseUser and define all required fields. You can take AbstractUser as an example.

2) Add validation either to UserSerializer or to pre_save signal on user save model and validate first_name and last_name fields. The simplest way, but not a good option for a long term project.

3) Do the way you've chosen by adding a reference to a User model as a user. Define first_name and last_name but ensure than there is no duplication of those fields between Fellow.first_name and Fellow.user.first_name. This could be hard as other developers may misunderstood the idea.
An error you get happens because User model has not been created and the right way to initiate a Fellow instance would be:

user = User.objects.create(username='test', email='[email protected]')
fellow = Fellow.objects.create(user=user, first_name='firstname', last_name='last_name')