Skip to content

Commit

Permalink
Merge pull request #4 from anexia/SIANXKE-330_isAnonymousLogin
Browse files Browse the repository at this point in the history
SIANXKE-330: add anonymous login relevant properties to User model
  • Loading branch information
nezhar authored Nov 20, 2023
2 parents 254e044 + c73123d commit 51f082e
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Mixin for User to provide properties `is_anonymous_login` and `anonymous_login`

## [1.1.0]

### Added
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ OR
2. Directly add the `AnonymousLoginAuthentication` and `IsAuthenticated` to your ViewSet's `authentication_classes` and
`permission_classes` as implemented in the [AnonymousLoginAuthenticationModelViewSet](drf_anonymous_login/views.py).

3. Optionally add the `AnonymousLoginUserMixin` to your app's User model in order to access its `is_anonymous_login`
and `anonymous_login` properties:
```
# myapp.models.py
class User(AnonymousLoginUserMixin, AbstractUser):
pass
```

```
# settings.py
AUTH_USER_MODEL = "myapp.User"
```


#### Configure token expiration
The tokens will not expire by default (expiration_datetime remains `None`). You can configure the
`ANONYMOUS_LOGIN_EXPIRATION` in your application's `settings.py` to define a default expiration in minutes, e.g.
Expand Down
17 changes: 17 additions & 0 deletions drf_anonymous_login/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,20 @@ def set_default_expiration_datetime():
default_expiration = getattr(settings, "ANONYMOUS_LOGIN_EXPIRATION", None)
if default_expiration:
return timezone.now() + timedelta(minutes=default_expiration)


class AnonymousLoginUserMixin(object):
@property
def is_anonymous_login(self):
return AnonymousLogin.objects.filter(token=self.username).exists()

@property
def anonymous_login(self):
"""
Returns the "longest" (with the latest expiration) AnonymousLogin element matching the user's username
"""
return (
AnonymousLogin.objects.filter(token=self.username)
.order_by("-expiration_datetime")
.first()
)
2 changes: 2 additions & 0 deletions tests/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,5 @@
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = "/static/"

AUTH_USER_MODEL = "testapp.User"
126 changes: 124 additions & 2 deletions tests/testapp/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Generated by Django 3.2.18 on 2023-04-03 13:20
# Generated by Django 4.2.6 on 2023-10-13 09:18

import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models

import drf_anonymous_login.models


class Migration(migrations.Migration):
initial = True

dependencies = []
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
]

operations = [
migrations.CreateModel(
Expand All @@ -27,4 +34,119 @@ class Migration(migrations.Migration):
),
],
),
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
bases=(drf_anonymous_login.models.AnonymousLoginUserMixin, models.Model),
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
]
7 changes: 7 additions & 0 deletions tests/testapp/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.contrib.auth.models import AbstractUser
from django.db import models

from drf_anonymous_login.models import AnonymousLoginUserMixin


class PublicModel(models.Model):
"""
Expand All @@ -15,3 +18,7 @@ class PrivateModel(models.Model):
"""

name = models.CharField(max_length=50, primary_key=True)


class User(AnonymousLoginUserMixin, AbstractUser):
pass
38 changes: 37 additions & 1 deletion tests/testapp/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.urls import reverse
from django.utils import timezone
from rest_framework.status import HTTP_200_OK, HTTP_403_FORBIDDEN
from testapp.models import PrivateModel, PublicModel
from testapp.models import PrivateModel, PublicModel, User

from drf_anonymous_login.authentication import AUTH_HEADER, AUTH_KEYWORD
from drf_anonymous_login.management.commands.cleanup_tokens import Command
Expand Down Expand Up @@ -103,3 +103,39 @@ def test_anonymous_login_token_cleanup(self):
# make sure the token gets deleted
cleanup_tokens.handle_tick()
self.assertEqual(AnonymousLogin.objects.count(), 0)

def test_user_is_anonymous_login(self):
"""
Assert that User is correctly identified as AnonymousLogin
:return:
"""
user = User.objects.create(
username=self.anonymous_login.token, password="password"
)
self.assertTrue(user.is_anonymous_login)

def test_user_is_not_anonymous_login(self):
"""
Assert that User is correctly identified as no AnonymousLogin
:return:
"""
user = User.objects.create(username="user", password="password")
self.assertFalse(user.is_anonymous_login)

def test_user_get_anonymous_login(self):
"""
Assert that User can access their AnonymousLogin
:return:
"""
user = User.objects.create(
username=self.anonymous_login.token, password="password"
)
self.assertEqual(user.anonymous_login, self.anonymous_login)

def test_user_get_no_anonymous_login(self):
"""
Assert that User can not access an AnonymousLogin
:return:
"""
user = User.objects.create(username="user", password="password")
self.assertIsNone(user.anonymous_login)

0 comments on commit 51f082e

Please sign in to comment.