Updated users model for removing username with more tests
This commit is contained in:
parent
ca8248431f
commit
42cd79662d
12 changed files with 186 additions and 155 deletions
|
|
@ -1,30 +1,23 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
||||||
from .models import UserProfile, User
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
@admin.register(UserProfile)
|
|
||||||
class UserProfileAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'display_name', 'created_at')
|
|
||||||
search_fields = ('user__username', 'display_name')
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(DjangoUserAdmin):
|
class UserAdmin(DjangoUserAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': ('username', 'password')}),
|
(None, {'fields': ('email_address', 'password')}),
|
||||||
('Personal info', {'fields': ('first_name', 'last_name', 'email_address')}),
|
('Personal info', {'fields': ('first_name', 'last_name')}),
|
||||||
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
|
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
|
||||||
('Important dates', {'fields': ('last_login', 'password_reset_at')}),
|
('Dates', {'fields': ('last_login',)}),
|
||||||
)
|
)
|
||||||
|
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
'fields': ('username', 'email_address', 'first_name', 'last_name', 'password1', 'password2'),
|
'fields': ('email_address', 'first_name', 'last_name', 'password1', 'password2'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
list_display = ('username', 'email_address', 'first_name', 'last_name', 'is_staff')
|
list_display = ('email_address', 'first_name', 'last_name', 'is_staff')
|
||||||
search_fields = ('username', 'email_address', 'first_name', 'last_name')
|
search_fields = ('email_address', 'first_name', 'last_name')
|
||||||
ordering = ('username',)
|
ordering = ('email_address',)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class UsersConfig(AppConfig):
|
class UsersConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.users'
|
name = 'apps.users'
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import BaseUserManager
|
from django.contrib.auth.models import BaseUserManager
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from apps.users.models import User # noqa: F401
|
from apps.users.models import User # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager["User"]):
|
class UserManager(BaseUserManager["User"]):
|
||||||
"""Custom manager for the User model."""
|
|
||||||
|
|
||||||
def _create_user(self, email_address: str, password: str | None, **extra_fields):
|
def _create_user(self, email_address: str, password: str | None, **extra_fields):
|
||||||
"""
|
|
||||||
Create and save a user with the given email and password.
|
|
||||||
"""
|
|
||||||
if not email_address:
|
if not email_address:
|
||||||
msg = "The given email must be set"
|
raise ValueError("The given email must be set")
|
||||||
raise ValueError(msg)
|
|
||||||
email_address = self.normalize_email(email_address)
|
email_address = self.normalize_email(email_address)
|
||||||
user: User = self.model(email_address=email_address, **extra_fields)
|
user: User = self.model(email_address=email_address, **extra_fields)
|
||||||
user.password = make_password(password)
|
user.password = make_password(password)
|
||||||
|
|
@ -24,18 +17,11 @@ class UserManager(BaseUserManager["User"]):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_user(self, email_address: str, password: str | None = None, **extra_fields): # type: ignore[override]
|
def create_user(self, email_address: str, password: str | None = None, **extra_fields): # type: ignore[override]
|
||||||
"""
|
|
||||||
Create and save a regular user with the given email and password.
|
|
||||||
"""
|
|
||||||
extra_fields.setdefault("is_staff", False)
|
extra_fields.setdefault("is_staff", False)
|
||||||
return self._create_user(email_address, password, **extra_fields)
|
return self._create_user(email_address, password, **extra_fields)
|
||||||
|
|
||||||
def create_superuser(self, email_address: str, password: str | None = None, **extra_fields): # type: ignore[override]
|
def create_superuser(self, email_address: str, password: str | None = None, **extra_fields): # type: ignore[override]
|
||||||
"""
|
|
||||||
Create and save a superuser with the given email and password.
|
|
||||||
"""
|
|
||||||
extra_fields.setdefault("is_staff", True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
if extra_fields.get("is_staff") is not True:
|
if extra_fields.get("is_staff") is not True:
|
||||||
msg = "Superuser must have is_staff=True."
|
raise ValueError("Superuser must have is_staff=True.")
|
||||||
raise ValueError(msg)
|
|
||||||
return self._create_user(email_address, password, **extra_fields)
|
return self._create_user(email_address, password, **extra_fields)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Generated by Django 5.2.8 on 2025-11-19 14:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='user',
|
||||||
|
),
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='user',
|
||||||
|
name='unique_username',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='user',
|
||||||
|
old_name='user_uuid',
|
||||||
|
new_name='uuid',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='is_verified',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='username',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='avatar_url',
|
||||||
|
field=models.URLField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='bio',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='timezone',
|
||||||
|
field=models.CharField(blank=True, default='UTC', max_length=16),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(primary_key=True, serialize=False, verbose_name='User ID'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='UserProfile',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,30 +1,24 @@
|
||||||
from typing import ClassVar
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
AutoField,
|
AutoField,
|
||||||
BooleanField,
|
BooleanField,
|
||||||
CASCADE,
|
|
||||||
CharField,
|
CharField,
|
||||||
DateField,
|
DateField,
|
||||||
DateTimeField,
|
DateTimeField,
|
||||||
EmailField,
|
EmailField,
|
||||||
UUIDField,
|
UUIDField,
|
||||||
Model,
|
Model,
|
||||||
OneToOneField,
|
|
||||||
TextField,
|
TextField,
|
||||||
UniqueConstraint,
|
|
||||||
URLField,
|
URLField,
|
||||||
)
|
)
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from typing import ClassVar
|
||||||
|
from uuid import uuid4
|
||||||
from apps.users.managers import UserManager
|
from apps.users.managers import UserManager
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class TimeStampMixin(Model):
|
class TimeStampMixin(Model):
|
||||||
"""Abstract model that provides created_at and updated_at fields."""
|
|
||||||
|
|
||||||
created_at = DateTimeField(verbose_name="Created At", auto_now_add=True)
|
created_at = DateTimeField(verbose_name="Created At", auto_now_add=True)
|
||||||
updated_at = DateTimeField(verbose_name="Updated At", auto_now=True)
|
updated_at = DateTimeField(verbose_name="Updated At", auto_now=True)
|
||||||
|
|
@ -34,30 +28,25 @@ class TimeStampMixin(Model):
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser, TimeStampMixin, PermissionsMixin):
|
class User(AbstractBaseUser, TimeStampMixin, PermissionsMixin):
|
||||||
"""Default custom user model for viswamedha.com (adapted).
|
|
||||||
|
|
||||||
Uses username as the USERNAME_FIELD and maintains an email_address
|
id = AutoField(verbose_name = _("User ID"), primary_key = True)
|
||||||
field used as the primary contact address.
|
uuid = UUIDField(verbose_name = _("User UUID"), default = uuid4, editable = False)
|
||||||
"""
|
|
||||||
|
|
||||||
id = AutoField(primary_key=True)
|
|
||||||
user_uuid = UUIDField(verbose_name=_("User UUID"), default=uuid4, editable=False)
|
|
||||||
|
|
||||||
# Required fields
|
|
||||||
email_address = EmailField(verbose_name = _("Email Address"), max_length = 255, unique = True)
|
email_address = EmailField(verbose_name = _("Email Address"), max_length = 255, unique = True)
|
||||||
username = CharField(verbose_name=_("Username"), max_length=25, unique=True)
|
|
||||||
first_name = CharField(verbose_name = _("First Name"), max_length = 255)
|
first_name = CharField(verbose_name = _("First Name"), max_length = 255)
|
||||||
last_name = CharField(verbose_name = _("Last Name"), max_length = 255)
|
last_name = CharField(verbose_name = _("Last Name"), max_length = 255)
|
||||||
date_of_birth = DateField(verbose_name = _("Date of Birth"), null = True, blank = True)
|
date_of_birth = DateField(verbose_name = _("Date of Birth"), null = True, blank = True)
|
||||||
|
|
||||||
|
bio = TextField(default = "", blank = True)
|
||||||
|
timezone = CharField(default = settings.TIME_ZONE, max_length = 16, blank = True)
|
||||||
|
avatar_url = URLField(blank = True)
|
||||||
|
|
||||||
is_active = BooleanField(verbose_name = _("Account Active"), default = True)
|
is_active = BooleanField(verbose_name = _("Account Active"), default = True)
|
||||||
is_staff = BooleanField(verbose_name = _("Account Admin"), default = False)
|
is_staff = BooleanField(verbose_name = _("Account Admin"), default = False)
|
||||||
is_verified = BooleanField(verbose_name=_("Account Verified"), default=False)
|
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'email_address'
|
||||||
USERNAME_FIELD = 'username'
|
|
||||||
EMAIL_FIELD = 'email_address'
|
EMAIL_FIELD = 'email_address'
|
||||||
REQUIRED_FIELDS = ['email_address', 'first_name', 'last_name', 'date_of_birth']
|
REQUIRED_FIELDS = ['first_name', 'last_name', 'date_of_birth']
|
||||||
|
|
||||||
objects: ClassVar[UserManager] = UserManager()
|
objects: ClassVar[UserManager] = UserManager()
|
||||||
|
|
||||||
|
|
@ -70,39 +59,11 @@ class User(AbstractBaseUser, TimeStampMixin, PermissionsMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('User')
|
verbose_name = _('User')
|
||||||
verbose_name_plural = _('Users')
|
verbose_name_plural = _('Users')
|
||||||
constraints = [
|
|
||||||
UniqueConstraint(fields=['username'], name='unique_username'),
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
return f"{self.first_name} {self.last_name}"
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
@property
|
|
||||||
def is_student(self):
|
|
||||||
return hasattr(self, 'student')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_name
|
return self.full_name
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if self.username:
|
|
||||||
self.username = self.username.lower()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(Model):
|
|
||||||
"""A lightweight profile attached to the project's `AUTH_USER_MODEL`."""
|
|
||||||
|
|
||||||
user = OneToOneField(
|
|
||||||
settings.AUTH_USER_MODEL, on_delete=CASCADE, related_name='profile'
|
|
||||||
)
|
|
||||||
display_name = CharField(max_length=150, blank=True)
|
|
||||||
bio = TextField(blank=True)
|
|
||||||
timezone = CharField(max_length=64, blank=True)
|
|
||||||
avatar_url = URLField(blank=True)
|
|
||||||
created_at = DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
def __str__(self) -> str: # pragma: no cover - trivial
|
|
||||||
return self.display_name or getattr(self.user, 'username', str(self.user))
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,8 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from apps.users.models import User
|
||||||
from apps.users.models import UserProfile
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = UserProfile
|
|
||||||
fields = ['display_name', 'bio', 'timezone', 'avatar_url']
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
profile = UserProfileSerializer(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', 'user_uuid', 'username', 'email_address', 'first_name', 'last_name', 'profile']
|
fields = ['id', 'uuid', 'email_address', 'first_name', 'last_name', 'bio', 'timezone', 'avatar_url']
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.urls import reverse
|
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import IntegrityError
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
@ -12,7 +14,6 @@ class UserModelTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_data = {
|
self.user_data = {
|
||||||
'email_address': 'Test@Example.com',
|
'email_address': 'Test@Example.com',
|
||||||
'username': 'TestUser',
|
|
||||||
'first_name': 'Test',
|
'first_name': 'Test',
|
||||||
'last_name': 'User',
|
'last_name': 'User',
|
||||||
'date_of_birth': '1990-01-01',
|
'date_of_birth': '1990-01-01',
|
||||||
|
|
@ -21,10 +22,8 @@ class UserModelTests(TestCase):
|
||||||
def test_create_user_and_properties(self):
|
def test_create_user_and_properties(self):
|
||||||
user = User.objects.create_user(password='pass1234', **self.user_data)
|
user = User.objects.create_user(password='pass1234', **self.user_data)
|
||||||
self.assertIsNotNone(user.pk)
|
self.assertIsNotNone(user.pk)
|
||||||
# email should be normalized by the manager
|
# email should be normalized by the manager (domain lowercased)
|
||||||
self.assertEqual(user.email_address.lower(), 'test@example.com')
|
self.assertEqual(user.email_address.lower(), 'test@example.com')
|
||||||
# username should be saved lowercase by model.save()
|
|
||||||
self.assertEqual(user.username, 'testuser')
|
|
||||||
# full_name property
|
# full_name property
|
||||||
self.assertEqual(user.full_name, 'Test User')
|
self.assertEqual(user.full_name, 'Test User')
|
||||||
|
|
||||||
|
|
@ -40,7 +39,6 @@ class UserAPITests(TestCase):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
password='pass1234',
|
password='pass1234',
|
||||||
email_address='apiuser@example.com',
|
email_address='apiuser@example.com',
|
||||||
username='apiuser',
|
|
||||||
first_name='API',
|
first_name='API',
|
||||||
last_name='User',
|
last_name='User',
|
||||||
date_of_birth='1995-05-05',
|
date_of_birth='1995-05-05',
|
||||||
|
|
@ -50,7 +48,61 @@ class UserAPITests(TestCase):
|
||||||
url = '/api/users/'
|
url = '/api/users/'
|
||||||
resp = self.client.get(url)
|
resp = self.client.get(url)
|
||||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||||
# Should contain at least the created user
|
emails = [u.get('email_address') for u in resp.json()]
|
||||||
usernames = [u.get('username') for u in resp.json()]
|
self.assertIn(self.user.email_address, emails)
|
||||||
self.assertIn(self.user.username, usernames)
|
|
||||||
|
def test_api_response_contains_expected_fields(self):
|
||||||
|
url = '/api/users/'
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||||
|
data = resp.json()
|
||||||
|
self.assertTrue(len(data) >= 1)
|
||||||
|
sample = data[0]
|
||||||
|
expected_keys = {'id', 'uuid', 'email_address', 'first_name', 'last_name', 'bio', 'timezone', 'avatar_url'}
|
||||||
|
self.assertTrue(expected_keys.issubset(set(sample.keys())))
|
||||||
|
|
||||||
|
|
||||||
|
class UserModelExtraTests(TestCase):
|
||||||
|
def test_password_hashed_and_check(self):
|
||||||
|
user = User.objects.create_user(email_address='hashme@example.com', password='secret123')
|
||||||
|
self.assertNotEqual(user.password, 'secret123')
|
||||||
|
self.assertTrue(user.check_password('secret123'))
|
||||||
|
|
||||||
|
def test_uuid_and_id_auto_populated(self):
|
||||||
|
u1 = User.objects.create_user(email_address='one@example.com', password='p')
|
||||||
|
u2 = User.objects.create_user(email_address='two@example.com', password='p')
|
||||||
|
self.assertIsNotNone(u1.uuid)
|
||||||
|
self.assertIsInstance(u1.uuid, uuid.UUID)
|
||||||
|
self.assertNotEqual(u1.uuid, u2.uuid)
|
||||||
|
self.assertIsNotNone(u1.id)
|
||||||
|
self.assertIsNotNone(u2.id)
|
||||||
|
|
||||||
|
def test_default_fields(self):
|
||||||
|
u = User.objects.create_user(email_address='defaults@example.com', password='p')
|
||||||
|
self.assertEqual(u.bio, "")
|
||||||
|
self.assertEqual(u.timezone, settings.TIME_ZONE)
|
||||||
|
self.assertEqual(u.avatar_url, "")
|
||||||
|
|
||||||
|
def test_unique_email_constraint(self):
|
||||||
|
User.objects.create_user(email_address='dup@example.com', password='p')
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
User.objects.create_user(email_address='dup@example.com', password='p')
|
||||||
|
|
||||||
|
def test_create_user_without_email_raises(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
User.objects.create_user(email_address='', password='p')
|
||||||
|
|
||||||
|
def test_date_of_birth_optional(self):
|
||||||
|
u = User.objects.create_user(email_address='nodob@example.com', password='p')
|
||||||
|
self.assertIsNone(u.date_of_birth)
|
||||||
|
|
||||||
|
def test_str_and_full_name(self):
|
||||||
|
u = User.objects.create_user(email_address='name@example.com', password='p', first_name='A', last_name='B')
|
||||||
|
self.assertEqual(u.full_name, 'A B')
|
||||||
|
self.assertEqual(str(u), 'A B')
|
||||||
|
|
||||||
|
def test_create_superuser_defaults(self):
|
||||||
|
su = User.objects.create_superuser(email_address='admin@example.com', password='admin')
|
||||||
|
self.assertTrue(su.is_staff)
|
||||||
|
self.assertTrue(su.is_active)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework import permissions
|
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||||
|
from apps.users.models import User
|
||||||
from django.contrib.auth import get_user_model
|
from apps.users.serializers import UserSerializer
|
||||||
|
|
||||||
from .serializers import UserSerializer
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Read-only viewset exposing users for the POC."""
|
|
||||||
|
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -21,6 +21,9 @@ MEDIA_URL = os.getenv('DJANGO_MEDIA_URL', '/media/')
|
||||||
STATIC_ROOT = os.getenv('DJANGO_STATIC_ROOT', BASE_DIR / 'static')
|
STATIC_ROOT = os.getenv('DJANGO_STATIC_ROOT', BASE_DIR / 'static')
|
||||||
MEDIA_ROOT = os.getenv('DJANGO_MEDIA_ROOT', BASE_DIR / 'media')
|
MEDIA_ROOT = os.getenv('DJANGO_MEDIA_ROOT', BASE_DIR / 'media')
|
||||||
|
|
||||||
|
OVERRIDE_APPS = [
|
||||||
|
'jazzmin',
|
||||||
|
]
|
||||||
DJANGO_APPS = [
|
DJANGO_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
|
@ -37,7 +40,7 @@ LOCAL_APPS = [
|
||||||
'apps.domains',
|
'apps.domains',
|
||||||
'apps.agents',
|
'apps.agents',
|
||||||
]
|
]
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = OVERRIDE_APPS + DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = 'users.User'
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ ddgs==9.9.0
|
||||||
debugpy==1.8.17
|
debugpy==1.8.17
|
||||||
decorator==5.2.1
|
decorator==5.2.1
|
||||||
Django==5.2.8
|
Django==5.2.8
|
||||||
|
django-jazzmin==3.0.1
|
||||||
djangorestframework==3.16.1
|
djangorestframework==3.16.1
|
||||||
duckduckgo_search==8.1.1
|
duckduckgo_search==8.1.1
|
||||||
executing==2.2.1
|
executing==2.2.1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue