diff --git a/django/contrib/auth/migrations/0005_alter_user_last_login_null.py b/django/contrib/auth/migrations/0005_alter_user_last_login_null.py new file mode 100644 index 00000000000..128a4e49628 --- /dev/null +++ b/django/contrib/auth/migrations/0005_alter_user_last_login_null.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0004_alter_user_username_opts'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='last_login', + field=models.DateTimeField(null=True, verbose_name='last login', blank=True), + ), + ] diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index daa71b9fc9d..304095488da 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -172,7 +172,7 @@ class UserManager(BaseUserManager): email = self.normalize_email(email) user = self.model(username=username, email=email, is_staff=is_staff, is_active=True, - is_superuser=is_superuser, last_login=now, + is_superuser=is_superuser, date_joined=now, **extra_fields) user.set_password(password) user.save(using=self._db) @@ -190,7 +190,7 @@ class UserManager(BaseUserManager): @python_2_unicode_compatible class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) - last_login = models.DateTimeField(_('last login'), default=timezone.now) + last_login = models.DateTimeField(_('last login'), blank=True, null=True) is_active = True diff --git a/django/contrib/auth/tests/test_models.py b/django/contrib/auth/tests/test_models.py index ea0cfb5528f..56448f4ece9 100644 --- a/django/contrib/auth/tests/test_models.py +++ b/django/contrib/auth/tests/test_models.py @@ -156,6 +156,13 @@ class AbstractUserTestCase(TestCase): self.assertEqual(message.from_email, "from@domain.com") self.assertEqual(message.to, [abstract_user.email]) + def test_last_login_default(self): + user1 = User.objects.create(username='user1') + self.assertIsNone(user1.last_login) + + user2 = User.objects.create_user(username='user2') + self.assertIsNone(user2.last_login) + class IsActiveTestCase(TestCase): """ diff --git a/django/contrib/auth/tokens.py b/django/contrib/auth/tokens.py index 6e5bfe7d9da..89100daeb2f 100644 --- a/django/contrib/auth/tokens.py +++ b/django/contrib/auth/tokens.py @@ -56,7 +56,7 @@ class PasswordResetTokenGenerator(object): key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator" # Ensure results are consistent across DB backends - login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None) + login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None) value = (six.text_type(user.pk) + user.password + six.text_type(login_timestamp) + six.text_type(timestamp)) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 621538420d2..3c96fc835a2 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -81,8 +81,12 @@ Fields .. attribute:: last_login - A datetime of the user's last login. Is set to the current date/time by - default. + A datetime of the user's last login. + + .. versionchanged:: 1.8 + + This field will be ``null`` if the user has never logged in. + Previously it was set to the current date/time by default. .. attribute:: date_joined diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 1623cd1af6e..858d02c4bff 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -407,6 +407,26 @@ officially supports. This also includes dropping support for PostGIS 1.3 and 1.4 as these versions are not supported on versions of PostgreSQL later than 8.4. +``AbstractUser.last_login`` allows null values +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :attr:`AbstractUser.last_login ` +field now allows null values. Previously, it defaulted to the time when the user +was created which was misleading if the user never logged in. Please run the +database migration. If your custom user inherits from ``AbstractUser`` and you +wish to set ``last_login`` to ``NULL`` for users who haven't logged in, you can +run this query:: + + from django.db import models + from django.contrib.auth import get_user_model + from django.contrib.auth.models import AbstractBaseUser + + UserModel = get_user_model() + if issubclass(UserModel, AbstractBaseUser): + UserModel._default_manager.filter( + last_login=models.F('date_joined') + ).update(last_login=None) + Miscellaneous ~~~~~~~~~~~~~