Creating a minimal custom user model. Seems last_login is required. Should it be?

2,548 views
Skip to first unread message

Harry Percival

unread,
Oct 20, 2013, 6:25:49 PM10/20/13
to django-d...@googlegroups.com
I'm trying to create a minimal custom user model.  The only thing I care about is email.  But it seems Django really wants me to set a last_login field.  Can I avoid it somehow?

Here's the minimal repro:

https://github.com/hjwp/minimal-django-custom-user-model/commit/377a83a9c995b2346b79458dcb5d48c0caed90df

The user looks like this:


class User(models.Model):
    email = models.EmailField(primary_key=True)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ()


But unfortunately, if I every put one through the django login function (from contrib.auth), he barfs:


  File "/usr/local/lib/python3.3/dist-packages/django/contrib/auth/__init__.py", line 97, in login
    user_logged_in.send(sender=user.__class__, request=request, user=user)
  File "/usr/local/lib/python3.3/dist-packages/django/dispatch/dispatcher.py", line 170, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/usr/local/lib/python3.3/dist-packages/django/contrib/auth/models.py", line 31, in update_last_login
    user.save(update_fields=['last_login'])
  File "/usr/local/lib/python3.3/dist-packages/django/db/models/base.py", line 526, in save
    % ', '.join(non_model_fields))
ValueError: The following fields do not exist in this model or are m2m fields: last_login

If you checkout the repo, I've written a test that reproduces same.


I don't care about last_login!  Can this be circumvented?  Should that signal be optional, or gracefully handle the case where the user model has no last_login field?  Should I log this as a bug?

HP

Daniele Procida

unread,
Oct 20, 2013, 6:48:26 PM10/20/13
to Django Developers
On Sun, Oct 20, 2013, Harry Percival <harry.p...@gmail.com> wrote:

>I'm trying to create a minimal custom user model. The only thing I care
>about is email. But it seems Django really wants me to set a last_login
>field. Can I avoid it somehow?
>
>I don't care about last_login! Can this be circumvented? Should that
>signal be optional, or gracefully handle the case where the user model has
>no last_login field? Should I log this as a bug?

I can reproduce this. I can't think of a good reason why a User absolutely must have a last_login, so please raise a ticket.

You're probably aware already, but the User model as provided here also breaks the superuser creating process called by syncdb. It doesn't ask for a username, and goes straight to asking for an email address, at which point it raises an error: AttributeError: 'Manager' object has no attribute 'get_by_natural_key'. I don't know if you were expecting that.

The full traceback is <https://dpaste.de/K6JE>.

Daniele

Tino de Bruijn

unread,
Oct 20, 2013, 7:17:29 PM10/20/13
to django-d...@googlegroups.com

On Mon, Oct 21, 2013 at 12:25 AM, Harry Percival <harry.p...@gmail.com> wrote:
I don't care about last_login!  Can this be circumvented?  Should that signal be optional, or gracefully handle the case where the user model has no last_login field?  Should I log this as a bug?

No, this is not a bug, it is by design. Django needs the last_login field for generating password reset tokens [0], and I guess you do want to leave that functionality in there. It is also not going to change anytime soon. Please look at another recent thread on this list about this same subject, and some reasoning from the core devs behind it.

If you really want a 'bare' User model, you can, you just can't use other contrib.auth stuff and contrib.admin stuff, as they expect more than just and identifier (like password reset, permissions and groups). 

If you want to make it easy for yourself, have a look at django-authtools[1]. The last_login field will still be there, but it saves you quite some lines of code, and it is not like that little database column is gonna cost you. (I know, it feels impure. I had that feeling for quite a while too, but hé, practicality beats purity :).

Tino

Ramiro Morales

unread,
Oct 20, 2013, 7:18:39 PM10/20/13
to django-d...@googlegroups.com
On Sun, Oct 20, 2013 at 7:25 PM, Harry Percival
<harry.p...@gmail.com> wrote:
> I'm trying to create a minimal custom user model. The only thing I care
> about is email. But it seems Django really wants me to set a last_login
> field. Can I avoid it somehow?

Thhis has been asked/discussed a couple of times since introduction of
the custom user feature. Please see e.g.

https://groups.google.com/forum/#!msg/django-users/tSzYy8liFRQ/i6dcJh4dBSoJ

--
Ramiro Morales
@ramiromorales

Russell Keith-Magee

unread,
Oct 20, 2013, 8:13:57 PM10/20/13
to Django Developers
On Mon, Oct 21, 2013 at 7:17 AM, Tino de Bruijn <tin...@gmail.com> wrote:

On Mon, Oct 21, 2013 at 12:25 AM, Harry Percival <harry.p...@gmail.com> wrote:
I don't care about last_login!  Can this be circumvented?  Should that signal be optional, or gracefully handle the case where the user model has no last_login field?  Should I log this as a bug?

No, this is not a bug, it is by design. Django needs the last_login field for generating password reset tokens [0], and I guess you do want to leave that functionality in there. It is also not going to change anytime soon. Please look at another recent thread on this list about this same subject, and some reasoning from the core devs behind it.

If you really want a 'bare' User model, you can, you just can't use other contrib.auth stuff and contrib.admin stuff, as they expect more than just and identifier (like password reset, permissions and groups). 

Actually, until the introduction of the login signal, this was untrue. 

I looked into this at DjangoCon US specifically because of a request from Harry, and I got a passwordless login to admin working fine. Groups and permissions are also unnecessary -- you just need to implement the has_permission() family of APIs, and they can be implemented with a simple "return True" result, or by calls on external authentication APIs if they're available.

Harry's use case is an interesting one -- his authentication is being done entirely by an external process, so there's no need for a password field. Yes, he could just have the password and last_login fields and not use it, but why should he need to carry around he extra weight when Django doesn't need it.
 
Harry - to address your original question: the immediate workaround is to make sure your user model can handle receiving a save request with an update_fields argument that contains last_login:

class MyUser(Model):
    …
    def save(self, *args, **kwargs):
        fields = kwargs.pop('update_fields', [])
        if fields != ['last_login']:
            return super(MyUser, self).save(*args, **kwargs)

You could also handle this by disconnecting the 'user_logged_in' signal:

user_logged_in.disconnect(update_last_login) 

The catch here will be getting this called in the right place; as is usually the case with Django signals, guaranteeing order of execution is the problem. The "right" place will depend on the exact properties of your project.

Yours,
Russ Magee %-)

Tino de Bruijn

unread,
Oct 21, 2013, 10:04:48 AM10/21/13
to django-d...@googlegroups.com
On Mon, Oct 21, 2013 at 2:13 AM, Russell Keith-Magee <rus...@keith-magee.com> wrote:

On Mon, Oct 21, 2013 at 7:17 AM, Tino de Bruijn <tin...@gmail.com> wrote:

On Mon, Oct 21, 2013 at 12:25 AM, Harry Percival <harry.p...@gmail.com> wrote:
I don't care about last_login!  Can this be circumvented?  Should that signal be optional, or gracefully handle the case where the user model has no last_login field?  Should I log this as a bug?

No, this is not a bug, it is by design. Django needs the last_login field for generating password reset tokens [0], and I guess you do want to leave that functionality in there. It is also not going to change anytime soon. Please look at another recent thread on this list about this same subject, and some reasoning from the core devs behind it.

If you really want a 'bare' User model, you can, you just can't use other contrib.auth stuff and contrib.admin stuff, as they expect more than just and identifier (like password reset, permissions and groups). 

Actually, until the introduction of the login signal, this was untrue. 

I looked into this at DjangoCon US specifically because of a request from Harry, and I got a passwordless login to admin working fine. Groups and permissions are also unnecessary -- you just need to implement the has_permission() family of APIs, and they can be implemented with a simple "return True" result, or by calls on external authentication APIs if they're available.

Ah, I stand corrected. 

Harry's use case is an interesting one -- his authentication is being done entirely by an external process, so there's no need for a password field. Yes, he could just have the password and last_login fields and not use it, but why should he need to carry around he extra weight when Django doesn't need it.

@Harry, just out of curiosity, may I ask how you *do* authenticate your users?


Tino

Daniele Procida

unread,
Oct 21, 2013, 10:10:29 AM10/21/13
to django-d...@googlegroups.com
On Mon, Oct 21, 2013, Tino de Bruijn <tin...@gmail.com> wrote:

>@Harry, just out of curiosity, may I ask how you *do* authenticate your
>users?

I think he challenges them to a sword fight with rolled-up umbrellas.

Daniele

Xavier Ordoquy

unread,
Oct 21, 2013, 10:22:07 AM10/21/13
to django-d...@googlegroups.com
Hi,

Le 21 oct. 2013 à 16:04, Tino de Bruijn <tin...@gmail.com> a écrit :

Harry's use case is an interesting one -- his authentication is being done entirely by an external process, so there's no need for a password field. Yes, he could just have the password and last_login fields and not use it, but why should he need to carry around he extra weight when Django doesn't need it.

@Harry, just out of curiosity, may I ask how you *do* authenticate your users?

I can't speak for Harry but using the RemoteUserBackend you don't need the password nor the last_login for Django.
In my case Apache did the authentication through Kerberos.
Django's documentation explains more there: https://docs.djangoproject.com/en/dev/howto/auth-remote-user/

Regards,
Xavier,
Linovia.

Harry Percival

unread,
Dec 18, 2013, 11:01:17 AM12/18/13
to django-d...@googlegroups.com
Hi all, can't believe I missed this entire thread because googlegroups didn't auto-subscribe me to replies.  thanks for the tips.

For the curious, I'm using Mozilla Persona.  Detailed info here: http://chimera.labs.oreilly.com/books/1234000000754/ch14.html
Reply all
Reply to author
Forward
0 new messages