diff --git a/README.md b/README.md index d0cd9ef..df85c6f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ INSTALLED_APPS = [ ``` - Add/modify your `AUTH_USER_MODEL` setting to ```python -AUTH_USER_MODEL = 'xauth.User' # Can also be a modified direct subclass of `xauth.models.User` +# Can also be a (modified) direct subclass of `xauth.models.AbstractUser` +AUTH_USER_MODEL = 'xauth.User' ``` - Add/modify your `REST_FRAMEWORK` setting to ```python diff --git a/docs/index.md b/docs/index.md index 077bb02..60760fb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,7 +48,8 @@ INSTALLED_APPS = [ ``` - Add/modify your `AUTH_USER_MODEL` setting to ```python -AUTH_USER_MODEL = 'xauth.User' # Can also be a modified direct subclass of `xauth.models.User` +# Can also be a (modified) direct subclass of `xauth.models.AbstractUser` +AUTH_USER_MODEL = 'xauth.User' ``` - Add/modify your `REST_FRAMEWORK` setting to ```python diff --git a/xauth/admin.py b/xauth/admin.py index 0d537b6..b68f84a 100644 --- a/xauth/admin.py +++ b/xauth/admin.py @@ -71,8 +71,8 @@ class UserAdmin(BaseUserAdmin): form = UserUpdateForm add_form = UserCreationForm - list_display = ('surname', 'first_name', 'last_name', 'date_of_birth', 'mobile_number', 'username', 'email', - 'provider', 'created_at', 'is_verified', 'is_staff', 'is_newbie',) + list_display = ('username', 'email', 'surname', 'first_name', 'last_name', 'date_of_birth', 'mobile_number', + 'provider', 'is_verified', 'is_staff', 'is_newbie', 'created_at',) list_filter = ('provider', 'is_verified', 'is_staff',) # readonly_fields = ('provider', 'is_verified', 'is_staff', 'created_at',) search_fields = ('surname', 'first_name', 'last_name', 'email', 'mobile_number',) @@ -109,6 +109,5 @@ class SecurityQuestionAdmin(admin.ModelAdmin): list_display = ('question', 'usable', 'added_on') -admin.site.register(SecurityQuestion, SecurityQuestionAdmin) - admin.site.register(get_user_model(), UserAdmin) +admin.site.register(SecurityQuestion, SecurityQuestionAdmin) diff --git a/xauth/migrations/0001_initial.py b/xauth/migrations/0001_initial.py index 1e2906b..6a15489 100644 --- a/xauth/migrations/0001_initial.py +++ b/xauth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.6 on 2020-05-29 22:20 +# Generated by Django 3.0.7 on 2020-06-12 18:56 import django.utils.timezone from django.conf import settings @@ -49,6 +49,8 @@ class Migration(migrations.Migration): ], options={ 'ordering': ('created_at', 'updated_at', 'username'), + 'abstract': False, + 'swappable': 'AUTH_USER_MODEL', }, ), migrations.CreateModel( diff --git a/xauth/models.py b/xauth/models.py index 8491869..f4fe284 100644 --- a/xauth/models.py +++ b/xauth/models.py @@ -3,7 +3,7 @@ from datetime import datetime, date import timeago -from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin, AbstractUser +from django.contrib.auth import models as dj_auth_models from django.db import models from django.utils import timezone from django.utils.datetime_safe import date as dj_date, datetime as dj_datetime @@ -20,7 +20,7 @@ def default_security_question(): return SecurityQuestion.objects.order_by('id').first() -class UserManager(BaseUserManager): +class UserManager(dj_auth_models.BaseUserManager): def create_user( self, email, username=None, @@ -66,7 +66,7 @@ def __user(self, email, username, password, surname=None, first_name=None, last_ ) -class User(AbstractBaseUser, PermissionsMixin): +class AbstractUser(dj_auth_models.AbstractBaseUser, dj_auth_models.PermissionsMixin): """ Guidelines: https://docs.djangoproject.com/en/3.0/topics/auth/customizing/ """ @@ -112,6 +112,7 @@ class User(AbstractBaseUser, PermissionsMixin): class Meta: ordering = ('created_at', 'updated_at', 'username',) + abstract = True def __str__(self): return self.get_full_name() @@ -132,7 +133,7 @@ def save(self, *args, **kwargs): self.set_unusable_password() self.is_verified = self.__get_ascertained_verification_status() reset_empty_nullable_to_null(self, self.NULLABLE_FIELDS) - super(User, self).save(*args, **kwargs) + super(AbstractUser, self).save(*args, **kwargs) def get_full_name(self): """ @@ -512,8 +513,7 @@ def update_signin_attempts(self, failed: bool): attempt.refresh_from_db() return self.__remaining_attempts(attempt) if failed else attempt.attempt_count - 1 - @staticmethod - def get_random_code(alpha_numeric: bool = True, length=None): + def get_random_code(self, alpha_numeric: bool = True, length=None): """ Generates and returns random code of `length` :param alpha_numeric: if `True`, include letters and numbers in the generated code @@ -526,9 +526,9 @@ def get_random_code(alpha_numeric: bool = True, length=None): length = random.randint(8, 10) if length is None or not isinstance(length, int) else length rand = None if alpha_numeric: - rand = User.objects.make_random_password(length=length) + rand = self.__class__.objects.make_random_password(length=length) else: - rand = User.objects.make_random_password(length=length, allowed_chars='23456789') + rand = self.__class__.objects.make_random_password(length=length, allowed_chars='23456789') return rand def token_payload(self) -> dict: @@ -583,6 +583,11 @@ def __remaining_attempts(attempt_log): return -1 if attempt_log is None else attempt_log.remaining +class User(AbstractUser): + class Meta(AbstractUser.Meta): + swappable = 'AUTH_USER_MODEL' + + class SecurityQuestion(models.Model): question = models.CharField(max_length=255, blank=False, null=False, unique=True, ) added_on = models.DateTimeField(auto_now_add=True, ) diff --git a/xauth/tests/test_authentication_backend.py b/xauth/tests/test_authentication_backend.py index c28d09a..5ea777a 100644 --- a/xauth/tests/test_authentication_backend.py +++ b/xauth/tests/test_authentication_backend.py @@ -1,8 +1,6 @@ from django.contrib.auth import get_user_model -from django.urls import reverse -from rest_framework import status from rest_framework.exceptions import AuthenticationFailed -from rest_framework.test import APITestCase, APIRequestFactory +from rest_framework.test import APITestCase from xauth import authentication @@ -34,22 +32,6 @@ def test_get_user_with_None_username_and_None_password(self): user = self.backend.get_user_with_username_and_password(username=None, password=None) self.assertIsNone(user) - def test_get_post_request_username_and_password(self): - _username = 'user' - _password = 'password' - factory = APIRequestFactory() - from xauth import views - view = views.SignInView.as_view() - request = factory.post(reverse('xauth:signin'), data={ - 'username': _username, - 'password': _password, - }) - response = view(request) - self.assertEqual(response.status_code, status.HTTP_200_OK) - # username, password = self.backend.get_post_request_username_and_password(request) - # self.assertEqual(username, _username) - # self.assertEqual(password, _password) - def test_get_basic_auth_username_and_password(self): from requests.auth import _basic_auth_str _username, _password = 'user', 'password' diff --git a/xauth/tests/test_signin_view.py b/xauth/tests/test_signin_view.py index 97e5321..deb868e 100644 --- a/xauth/tests/test_signin_view.py +++ b/xauth/tests/test_signin_view.py @@ -12,10 +12,9 @@ def assert_post_request_auth(self, _username, _password, expect_status_code, exp }) response_data = response.data self.assertEqual(response.status_code, expect_status_code) - if expect_null_payload: - self.assertIsNotNone(get_response_data_message(response)) - else: - self.assertIsNotNone(get_response_data_payload(response)) + self.assertIsNotNone( + get_response_data_message(response) if expect_null_payload else get_response_data_payload(response)) + return response def assert_basic_auth(self, _username, _password, expect_status_code, expect_null_payload: bool = True): from requests.auth import _basic_auth_str diff --git a/xauth/views.py b/xauth/views.py index b0d844f..0055a18 100644 --- a/xauth/views.py +++ b/xauth/views.py @@ -1,6 +1,6 @@ import re -from django.contrib.auth import logout +from django.contrib.auth import logout, login from django.contrib.auth.models import AnonymousUser from django.db.models import Q from rest_framework import permissions, generics, views, status, viewsets @@ -67,6 +67,7 @@ class SignInView(views.APIView): def post(self, request, format=None): # authentication logic is by default handled by the auth-backend user = request.user + login(request, user) user.update_or_create_access_log(force_create=True) serializer = self.serializer_class(user, context={'request': request}, ) return get_wrapped_response(Response(serializer.data, status=status.HTTP_200_OK))