mirror of
https://github.com/lemeow125/DRF_Template.git
synced 2025-04-28 10:41:15 +08:00
Overhauled entire project config, added notifications, email templates, optimized stripe subscriptions, redis caching, and webdriver utilities
This commit is contained in:
parent
7cbe8fd720
commit
99dfcef67b
84 changed files with 4300 additions and 867 deletions
|
@ -6,7 +6,7 @@ from .models import CustomUser
|
|||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
model = CustomUser
|
||||
list_display = ('id',) + UserAdmin.list_display
|
||||
list_display = ('id', 'is_active', 'user_group',) + UserAdmin.list_display
|
||||
# Editable fields per instance
|
||||
fieldsets = UserAdmin.fieldsets + (
|
||||
(None, {'fields': ('avatar',)}),
|
||||
|
|
|
@ -4,3 +4,6 @@ from django.apps import AppConfig
|
|||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
||||
|
||||
def ready(self):
|
||||
import accounts.signals
|
||||
|
|
|
@ -1,46 +1,50 @@
|
|||
# Generated by Django 5.0.1 on 2024-01-06 04:34
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
import django_resized.forms
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(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')),
|
||||
('avatar', django_resized.forms.ResizedImageField(crop=None, force_format='WEBP', keep_meta=True, null=True, quality=100, scale=None, size=[1920, 1080], upload_to='avatars/')),
|
||||
('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,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
# Generated by Django 5.0.6 on 2024-05-10 06:37
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import django_resized.forms
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('user_groups', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(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')),
|
||||
('avatar', django_resized.forms.ResizedImageField(crop=None, force_format='WEBP', keep_meta=True, null=True, quality=100, scale=None, size=[1920, 1080], upload_to='avatars/')),
|
||||
('onboarding', models.BooleanField(default=True)),
|
||||
('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_group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='user_groups.usergroup')),
|
||||
('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,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.urls import reverse
|
||||
from django.db.models.signals import post_migrate
|
||||
from django.dispatch import receiver
|
||||
from django_resized import ResizedImageField
|
||||
import os
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
|
@ -18,65 +17,44 @@ class CustomUser(AbstractUser):
|
|||
avatar = ResizedImageField(
|
||||
null=True, force_format="WEBP", quality=100, upload_to='avatars/')
|
||||
|
||||
def avatar_url(self):
|
||||
return f'/api/v1/media/avatars/{self.avatar.field.storage.name(self.avatar.path)}'
|
||||
# Used for onboarding processes
|
||||
# Set this to False later on once the user makes actions
|
||||
onboarding = models.BooleanField(default=True)
|
||||
|
||||
user_group = models.ForeignKey(
|
||||
'user_groups.UserGroup', on_delete=models.SET_NULL, null=True)
|
||||
|
||||
@property
|
||||
def group_member(self):
|
||||
if self.user_group:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# Can be used to show tooltips for newer users
|
||||
@property
|
||||
def is_new(self):
|
||||
current_date = timezone.now()
|
||||
return self.date_joined + timedelta(days=1) < current_date
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
@property
|
||||
def group_member(self):
|
||||
if self.user_group:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def group_owner(self):
|
||||
if self.user_group and self == self.user_group.owner:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def admin_url(self):
|
||||
return reverse('admin:users_customuser_change', args=(self.pk,))
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def create_superuser(sender, **kwargs):
|
||||
if sender.name == 'accounts':
|
||||
# Add test users here if needed
|
||||
# They will automatically be created after migrating the db
|
||||
users = [
|
||||
# Superadmin Account
|
||||
{
|
||||
'username': os.getenv('DJANGO_ADMIN_USERNAME'),
|
||||
'email': os.getenv('DJANGO_ADMIN_EMAIL'),
|
||||
'password': os.getenv('DJANGO_ADMIN_PASSWORD'),
|
||||
'is_staff': True,
|
||||
'is_superuser': True,
|
||||
'first_name': 'Super',
|
||||
'last_name': 'Admin'
|
||||
},
|
||||
# Debug User
|
||||
{
|
||||
'username': 'debug-user',
|
||||
'email': os.getenv('DJANGO_ADMIN_EMAIL'),
|
||||
'password': os.getenv('DJANGO_ADMIN_PASSWORD'),
|
||||
'is_staff': False,
|
||||
'is_superuser': False,
|
||||
'first_name': "Test",
|
||||
'last_name': "User"
|
||||
},
|
||||
]
|
||||
|
||||
for user in users:
|
||||
if not CustomUser.objects.filter(username=user['username']).exists():
|
||||
if (user['is_superuser']):
|
||||
USER = CustomUser.objects.create_superuser(
|
||||
username=user['username'],
|
||||
password=user['password'],
|
||||
email=user['email'],
|
||||
)
|
||||
print('Created Superuser:', user['username'])
|
||||
else:
|
||||
USER = CustomUser.objects.create_user(
|
||||
username=user['username'],
|
||||
password=user['password'],
|
||||
email=user['email'],
|
||||
)
|
||||
print('Created User:', user['username'])
|
||||
USER.first_name = user['first_name']
|
||||
USER.last_name = user['last_name']
|
||||
USER.is_active = True
|
||||
USER.save()
|
||||
|
|
|
@ -1,52 +1,81 @@
|
|||
from djoser.serializers import UserCreateSerializer as BaseUserRegistrationSerializer
|
||||
from djoser.serializers import UserSerializer as BaseUserSerializer
|
||||
from django.core import exceptions as django_exceptions
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
from accounts.models import CustomUser
|
||||
from drf_extra_fields.fields import Base64ImageField
|
||||
from user_groups.serializers import SimpleUserGroupSerializer
|
||||
from django.core.cache import cache
|
||||
from django.core import exceptions as django_exceptions
|
||||
from rest_framework.settings import api_settings
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.utils.encoding import smart_str
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_extra_fields.fields import Base64ImageField
|
||||
|
||||
# There can be multiple subject instances with the same name, only differing in course, year level, and semester. We filter them here
|
||||
|
||||
|
||||
class SimpleCustomUserSerializer(ModelSerializer):
|
||||
class Meta(BaseUserSerializer.Meta):
|
||||
model = CustomUser
|
||||
fields = ('id', 'username', 'email', 'full_name')
|
||||
|
||||
|
||||
class CustomUserSerializer(BaseUserSerializer):
|
||||
avatar = Base64ImageField()
|
||||
|
||||
class Meta(BaseUserSerializer.Meta):
|
||||
model = CustomUser
|
||||
fields = ('id', 'username', 'email', 'avatar', 'first_name',
|
||||
'last_name')
|
||||
'last_name', 'user_group', 'group_member', 'group_owner')
|
||||
read_only_fields = ('id', 'username', 'email', 'user_group',
|
||||
'group_member', 'group_owner')
|
||||
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
representation['user_group'] = SimpleUserGroupSerializer(
|
||||
instance.user_group, many=False).data
|
||||
return representation
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
cache.delete(f'user:{instance.id}')
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
email = serializers.EmailField(required=True)
|
||||
username = serializers.CharField(required=True)
|
||||
password = serializers.CharField(
|
||||
write_only=True, style={'input_type': 'password', 'placeholder': 'Password'})
|
||||
first_name = serializers.CharField(
|
||||
required=True, allow_blank=False, allow_null=False)
|
||||
last_name = serializers.CharField(
|
||||
required=True, allow_blank=False, allow_null=False)
|
||||
|
||||
class Meta:
|
||||
model = CustomUser # Use your custom user model here
|
||||
fields = ('username', 'email', 'password', 'avatar',
|
||||
'first_name', 'last_name')
|
||||
model = CustomUser
|
||||
fields = ['email', 'username', 'password',
|
||||
'first_name', 'last_name']
|
||||
|
||||
def validate(self, attrs):
|
||||
user = self.Meta.model(**attrs)
|
||||
user_attrs = attrs.copy()
|
||||
user = self.Meta.model(**user_attrs)
|
||||
password = attrs.get("password")
|
||||
|
||||
try:
|
||||
validate_password(password, user)
|
||||
except django_exceptions.ValidationError as e:
|
||||
serializer_error = serializers.as_serializer_error(e)
|
||||
errors = serializer_error[api_settings.NON_FIELD_ERRORS_KEY]
|
||||
if len(errors) > 1:
|
||||
raise serializers.ValidationError({"password": errors[0]})
|
||||
else:
|
||||
raise serializers.ValidationError({"password": errors})
|
||||
if self.Meta.model.objects.filter(username=attrs.get("username")).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"password": serializer_error[api_settings.NON_FIELD_ERRORS_KEY]}
|
||||
)
|
||||
|
||||
"A user with that username already exists.")
|
||||
return super().validate(attrs)
|
||||
|
||||
def create(self, validated_data):
|
||||
user = self.Meta.model(**validated_data)
|
||||
user.username = validated_data['username']
|
||||
user.is_active = False
|
||||
user.set_password(validated_data['password'])
|
||||
user.save()
|
||||
|
||||
|
|
103
backend/accounts/signals.py
Normal file
103
backend/accounts/signals.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
from django.db.models.signals import post_migrate
|
||||
from django.dispatch import receiver
|
||||
from config.settings import SEED_DATA, ROOT_DIR, get_secret
|
||||
from django_celery_beat.models import PeriodicTask, CrontabSchedule
|
||||
from .models import CustomUser
|
||||
import os
|
||||
import json
|
||||
|
||||
# Function to fill in users table with test data on dev/staging
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def create_users(sender, **kwargs):
|
||||
if sender.name == "accounts":
|
||||
with open(os.path.join(ROOT_DIR, 'seed_data.json'), "r") as f:
|
||||
seed_data = json.loads(f.read())
|
||||
for user in seed_data['users']:
|
||||
USER = CustomUser.objects.filter(
|
||||
email=user['email']).first()
|
||||
if not USER:
|
||||
if user['password'] == 'USE_REGULAR':
|
||||
password = get_secret('SEED_DATA_PASSWORD')
|
||||
elif user['password'] == 'USE_ADMIN':
|
||||
password = get_secret('DJANGO_ADMIN_PASSWORD')
|
||||
else:
|
||||
password = user['password']
|
||||
if (user['is_superuser'] == True):
|
||||
# Admin users are created regardless of SEED_DATA value
|
||||
USER = CustomUser.objects.create_superuser(
|
||||
username=user['username'],
|
||||
email=user['email'],
|
||||
password=password,
|
||||
)
|
||||
print('Created Superuser:', user['email'])
|
||||
else:
|
||||
# Only create non-admin users if SEED_DATA=True
|
||||
if SEED_DATA:
|
||||
USER = CustomUser.objects.create_user(
|
||||
username=user['email'],
|
||||
email=user['email'],
|
||||
password=password,
|
||||
)
|
||||
print('Created User:', user['email'])
|
||||
|
||||
USER.first_name = user['first_name']
|
||||
USER.last_name = user['last_name']
|
||||
USER.is_active = True
|
||||
USER.save()
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def create_celery_beat_schedules(sender, **kwargs):
|
||||
if sender.name == "django_celery_beat":
|
||||
with open(os.path.join(ROOT_DIR, 'seed_data.json'), "r") as f:
|
||||
seed_data = json.loads(f.read())
|
||||
# Creating Schedules
|
||||
for schedule in seed_data['schedules']:
|
||||
if schedule['type'] == 'crontab':
|
||||
# Check if Schedule already exists
|
||||
SCHEDULE = CrontabSchedule.objects.filter(minute=schedule['minute'],
|
||||
hour=schedule['hour'],
|
||||
day_of_week=schedule['day_of_week'],
|
||||
day_of_month=schedule['day_of_month'],
|
||||
month_of_year=schedule['month_of_year'],
|
||||
timezone=schedule['timezone']
|
||||
).first()
|
||||
# If it does not exist, create a new Schedule
|
||||
if not SCHEDULE:
|
||||
SCHEDULE = CrontabSchedule.objects.create(
|
||||
minute=schedule['minute'],
|
||||
hour=schedule['hour'],
|
||||
day_of_week=schedule['day_of_week'],
|
||||
day_of_month=schedule['day_of_month'],
|
||||
month_of_year=schedule['month_of_year'],
|
||||
timezone=schedule['timezone']
|
||||
)
|
||||
print(
|
||||
f'Created Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute}')
|
||||
else:
|
||||
print(
|
||||
f'Crontab Schedule for Hour:{SCHEDULE.hour},Minute:{SCHEDULE.minute} already exists')
|
||||
for task in seed_data['scheduled_tasks']:
|
||||
TASK = PeriodicTask.objects.filter(name=task['name']).first()
|
||||
if not TASK:
|
||||
if task['schedule']['type'] == 'crontab':
|
||||
SCHEDULE = CrontabSchedule.objects.filter(minute=task['schedule']['minute'],
|
||||
hour=task['schedule']['hour'],
|
||||
day_of_week=task['schedule']['day_of_week'],
|
||||
day_of_month=task['schedule']['day_of_month'],
|
||||
month_of_year=task['schedule']['month_of_year'],
|
||||
timezone=task['schedule']['timezone']
|
||||
).first()
|
||||
TASK = PeriodicTask.objects.create(
|
||||
crontab=SCHEDULE,
|
||||
name=task['name'],
|
||||
task=task['task'],
|
||||
enabled=task['enabled']
|
||||
)
|
||||
print(f'Created Periodic Task: {TASK.name}')
|
||||
else:
|
||||
raise Exception('Schedule for Periodic Task not found')
|
||||
else:
|
||||
print(f'Periodic Task: {TASK.name} already exists')
|
23
backend/accounts/tasks.py
Normal file
23
backend/accounts/tasks.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from celery import shared_task
|
||||
|
||||
|
||||
@shared_task
|
||||
def get_paying_users():
|
||||
from subscriptions.models import UserSubscription
|
||||
# Get a list of user subscriptions
|
||||
active_subscriptions = UserSubscription.objects.filter(
|
||||
valid=True).distinct('user')
|
||||
|
||||
# Get paying users
|
||||
active_users = []
|
||||
|
||||
# Paying regular users
|
||||
active_users += [
|
||||
subscription.user.id for subscription in active_subscriptions if subscription.user is not None and subscription.user.user_group is None]
|
||||
|
||||
# Paying users within groups
|
||||
active_users += [
|
||||
subscription.user_group.members for subscription in active_subscriptions if subscription.user_group is not None and subscription.user is None]
|
||||
|
||||
# Return paying users
|
||||
return active_users
|
|
@ -1,7 +1,12 @@
|
|||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from accounts import views
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'users', views.CustomUserViewSet, basename='users')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
path('', include('djoser.urls')),
|
||||
path('', include('djoser.urls.jwt')),
|
||||
]
|
||||
|
|
44
backend/accounts/validators.py
Normal file
44
backend/accounts/validators.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext as _
|
||||
import re
|
||||
|
||||
|
||||
class UppercaseValidator(object):
|
||||
def validate(self, password, user=None):
|
||||
if not re.findall('[A-Z]', password):
|
||||
raise ValidationError(
|
||||
_("The password must contain at least 1 uppercase letter (A-Z)."))
|
||||
|
||||
def get_help_text(self):
|
||||
return _("Your password must contain at least 1 uppercase letter (A-Z).")
|
||||
|
||||
|
||||
class LowercaseValidator(object):
|
||||
def validate(self, password, user=None):
|
||||
if not re.findall('[a-z]', password):
|
||||
raise ValidationError(
|
||||
_("The password must contain at least 1 lowercase letter (a-z)."))
|
||||
|
||||
def get_help_text(self):
|
||||
return _("Your password must contain at least 1 lowercase letter (a-z).")
|
||||
|
||||
|
||||
class SpecialCharacterValidator(object):
|
||||
def validate(self, password, user=None):
|
||||
if not re.findall('[@#$%^&*()_+/\<>;:!?]', password):
|
||||
raise ValidationError(
|
||||
_("The password must contain at least 1 special character (@, #, $, etc.)."))
|
||||
|
||||
def get_help_text(self):
|
||||
return _("Your password must contain at least 1 special character (@, #, $, etc.).")
|
||||
|
||||
|
||||
class NumberValidator(object):
|
||||
def validate(self, password, user=None):
|
||||
if not any(char.isdigit() for char in password):
|
||||
raise ValidationError(
|
||||
_("The password must contain at least one numerical digit (0-9)."))
|
||||
|
||||
def get_help_text(self):
|
||||
return _("Your password must contain at least numerical digit (0-9).")
|
|
@ -1,5 +1,118 @@
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework import generics
|
||||
from accounts.serializers import CustomUserSerializer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from accounts.models import CustomUser
|
||||
from accounts import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from djoser.conf import settings
|
||||
from djoser.views import UserViewSet as DjoserUserViewSet
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from djoser import signals
|
||||
from djoser.compat import get_user_email
|
||||
from django.core.cache import cache
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
|
||||
class CustomUserViewSet(DjoserUserViewSet):
|
||||
queryset = CustomUser.objects.all()
|
||||
serializer_class = serializers.CustomUserSerializer
|
||||
permission_classes = settings.PERMISSIONS.activation
|
||||
token_generator = default_token_generator
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
# If user is admin, show all active users
|
||||
if user.is_superuser:
|
||||
key = 'users'
|
||||
# Get cache
|
||||
queryset = cache.get(key)
|
||||
# Set cache if stale or does not exist
|
||||
if not queryset:
|
||||
queryset = CustomUser.objects.filter(is_active=True)
|
||||
cache.set(key, queryset, 60*60)
|
||||
return queryset
|
||||
elif not user.user_group:
|
||||
key = f'user:{user.id}'
|
||||
queryset = cache.get(key)
|
||||
if not queryset:
|
||||
queryset = CustomUser.objects.filter(is_active=True)
|
||||
cache.set(key, queryset, 60*60)
|
||||
return queryset
|
||||
elif user.user_group:
|
||||
key = f'usergroup_users:{user.user_group.id}'
|
||||
queryset = cache.get(key)
|
||||
if not queryset:
|
||||
queryset = CustomUser.objects.filter(
|
||||
user_group=user.user_group)
|
||||
cache.set(key, queryset, 60*60)
|
||||
return queryset
|
||||
else:
|
||||
return CustomUser.objects.none()
|
||||
|
||||
def perform_update(self, serializer, *args, **kwargs):
|
||||
user = self.request.user
|
||||
|
||||
# Clear cache
|
||||
cache.delete(f'users')
|
||||
cache.delete(f'user:{user.id}')
|
||||
if user.user_group:
|
||||
cache.delete(f'usergroup_users:{user.user_group.id}')
|
||||
|
||||
super().perform_update(serializer, *args, **kwargs)
|
||||
user = serializer.instance
|
||||
signals.user_updated.send(
|
||||
sender=self.__class__, user=user, request=self.request
|
||||
)
|
||||
if settings.SEND_ACTIVATION_EMAIL and not user.is_active:
|
||||
context = {"user": user}
|
||||
to = [get_user_email(user)]
|
||||
settings.EMAIL.activation(self.request, context).send(to)
|
||||
|
||||
def perform_create(self, serializer, *args, **kwargs):
|
||||
user = serializer.save(*args, **kwargs)
|
||||
|
||||
# Silently error out if email sending fails
|
||||
try:
|
||||
signals.user_registered.send(
|
||||
sender=self.__class__, user=user, request=self.request
|
||||
)
|
||||
context = {"user": user}
|
||||
to = [get_user_email(user)]
|
||||
|
||||
if settings.SEND_ACTIVATION_EMAIL:
|
||||
settings.EMAIL.activation(self.request, context).send(to)
|
||||
elif settings.SEND_CONFIRMATION_EMAIL:
|
||||
settings.EMAIL.confirmation(self.request, context).send(to)
|
||||
|
||||
# Clear cache
|
||||
cache.delete('users')
|
||||
cache.delete(f'user:{user.id}')
|
||||
if user.user_group:
|
||||
cache.delete(f'usergroup_users:{user.user_group.id}')
|
||||
|
||||
except Exception as e:
|
||||
print('Warning: Unable to send email')
|
||||
print(e)
|
||||
|
||||
@action(methods=['post'], detail=False, url_path='activation', url_name='activation')
|
||||
def activation(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.user
|
||||
user.is_active = True
|
||||
user.save()
|
||||
|
||||
# Construct a response with user's first name, last name, and email
|
||||
user_data = {
|
||||
'first_name': user.first_name,
|
||||
'last_name': user.last_name,
|
||||
'email': user.email,
|
||||
'username': user.username
|
||||
}
|
||||
|
||||
# Clear cache
|
||||
cache.delete('users')
|
||||
cache.delete(f'user:{user.id}')
|
||||
if user.user_group:
|
||||
cache.delete(f'usergroup_users:{user.user_group.id}')
|
||||
|
||||
return Response(user_data, status=status.HTTP_200_OK)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue