Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ repos:
require_serial: true
types: [ python ]

# broken?
# - repo: https://github.com/myint/docformatter
# rev: v1.5.1
# hooks:
# - id: docformatter
# language: python
- repo: local
hooks:
- id: docformatter
name: docformatter
entry: docformatter
language: system
types: [python]
args: [--in-place, --wrap-summaries=88, --wrap-descriptions=88, --make-summary-multi-line]

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.9.4'
Expand Down
5 changes: 3 additions & 2 deletions djoser/auth_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@


class LoginFieldBackend(ModelBackend):
"""Allows to log in by a different value than the default Django
USERNAME_FIELD."""
"""
Allows to log in by a different value than the default Django USERNAME_FIELD.
"""

def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
Expand Down
24 changes: 24 additions & 0 deletions djoser/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ def __getattribute__(self, item):
"token_destroy": ["rest_framework.permissions.IsAuthenticated"],
}
),
"VIEWS": ObjDict(
{
"activation": "djoser.views.activation.activate.UserActivationAPIView",
"resend_activation": "djoser.views.activation.resend.UserResendActivationAPIView",
"password_reset": "djoser.views.password.reset.ResetPasswordViewAPIView",
"password_reset_confirm": "djoser.views.password.reset_confirm.ResetPasswordConfirmViewAPIView",
"set_password": "djoser.views.password.set.SetPasswordViewAPIView",
"username_reset": "djoser.views.username.reset.ResetUsernameAPIView",
"username_reset_confirm": "djoser.views.username.reset_confirm.ResetUsernameConfirmAPIView",
"set_username": "djoser.views.username.set.SetUsernameAPIView",
"user_create": "djoser.views.user.create.UserCreateView",
"user_delete": "djoser.views.user.delete.UserDeleteView",
"user_list": "djoser.views.user.list.UserListView",
"user_detail": "djoser.views.user.retrieve.UserRetrieveView",
"user_update_put": "djoser.views.user.update_put.UserPutView",
"user_update_patch": "djoser.views.user.update_patch.UserPatchView",
"user_me_get": "djoser.views.me.retrieve.UserMeRetrieveView",
"user_me_put": "djoser.views.me.update.UserMeUpdateView",
"user_me_patch": "djoser.views.me.update.UserMeUpdateView",
"user_me_delete": "djoser.views.me.delete.UserMeDeleteView",
"token_create": "djoser.views.token.create.TokenCreateView",
"token_destroy": "djoser.views.token.destroy.TokenDestroyView",
}
),
"WEBAUTHN": ObjDict(
{
"RP_NAME": "localhost",
Expand Down
23 changes: 23 additions & 0 deletions djoser/urls/activation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.urls import path

from djoser.conf import settings

urlpatterns = []

# Configure activation endpoint
activation_view = getattr(settings.VIEWS, "activation", None)
if activation_view:
user_activation = path(
"users/activation/", activation_view.as_view(), name="user-activation"
)
urlpatterns.append(user_activation)

# Configure resend activation endpoint
resend_activation_view = getattr(settings.VIEWS, "resend_activation", None)
if resend_activation_view:
user_resend_activation = path(
"users/resend_activation/",
resend_activation_view.as_view(),
name="user-resend-activation",
)
urlpatterns.append(user_resend_activation)
12 changes: 8 additions & 4 deletions djoser/urls/authtoken.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from django.urls import re_path
from django.urls import path

from djoser import views
from djoser.conf import settings
from djoser.urls.utils import AppendSlashRedirectView

urlpatterns = [
re_path(r"^token/login/?$", views.TokenCreateView.as_view(), name="login"),
re_path(r"^token/logout/?$", views.TokenDestroyView.as_view(), name="logout"),
path("token/login/", settings.VIEWS.token_create.as_view(), name="login"),
path("token/logout/", settings.VIEWS.token_destroy.as_view(), name="logout"),
# djoser 2.x accepted these without the trailing slash.
path("token/login", AppendSlashRedirectView.as_view()),
path("token/logout", AppendSlashRedirectView.as_view()),
]
45 changes: 37 additions & 8 deletions djoser/urls/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
from django.contrib.auth import get_user_model
from rest_framework.routers import DefaultRouter
from rest_framework.urlpatterns import format_suffix_patterns

from djoser import views
from djoser.urls.activation import (
user_activation,
user_resend_activation,
)
from djoser.urls.me import me_list
from djoser.urls.password import (
user_password_reset_confirm,
user_reset_password,
user_set_password,
)
from djoser.urls.user import (
user_detail_update_delete,
user_list_create,
)
from djoser.urls.username import (
user_reset_username,
user_reset_username_confirm,
user_set_username,
)

router = DefaultRouter()
router.register("users", views.UserViewSet)

User = get_user_model()

urlpatterns = router.urls
# format_suffix_patterns restores the .json/.api routes DRF's DefaultRouter used to
# generate, e.g. /users/1.json. It relies on the lookup converter excluding dots.
urlpatterns = format_suffix_patterns(
[
user_resend_activation,
user_activation,
user_password_reset_confirm,
user_reset_username_confirm,
user_reset_password,
user_set_password,
user_set_username,
user_reset_username,
me_list,
user_detail_update_delete,
user_list_create,
]
)
16 changes: 16 additions & 0 deletions djoser/urls/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class UserLookupConverter:
"""
Matches a user lookup value, excluding dots.

Mirrors the regex DRF's SimpleRouter used before the URLs were written by hand.
Excluding dots is what keeps ``/users/1.json`` routable as a format suffix rather
than being swallowed as a lookup value of ``"1.json"``.
"""

regex = r"[^/.]+"

def to_python(self, value):
return value

def to_url(self, value):
return str(value)
14 changes: 10 additions & 4 deletions djoser/urls/jwt.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
from django.urls import re_path
from django.urls import path
from rest_framework_simplejwt import views

from djoser.urls.utils import AppendSlashRedirectView

urlpatterns = [
re_path(r"^jwt/create/?", views.TokenObtainPairView.as_view(), name="jwt-create"),
re_path(r"^jwt/refresh/?", views.TokenRefreshView.as_view(), name="jwt-refresh"),
re_path(r"^jwt/verify/?", views.TokenVerifyView.as_view(), name="jwt-verify"),
path("jwt/create/", views.TokenObtainPairView.as_view(), name="jwt-create"),
path("jwt/refresh/", views.TokenRefreshView.as_view(), name="jwt-refresh"),
path("jwt/verify/", views.TokenVerifyView.as_view(), name="jwt-verify"),
# djoser 2.x accepted these without the trailing slash.
path("jwt/create", AppendSlashRedirectView.as_view()),
path("jwt/refresh", AppendSlashRedirectView.as_view()),
path("jwt/verify", AppendSlashRedirectView.as_view()),
]
23 changes: 23 additions & 0 deletions djoser/urls/me.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.urls import path

from djoser.urls.utils import create_configurable_dispatcher

# Create configurable dispatcher for user "me" endpoint
me_dispatcher = create_configurable_dispatcher(
{
"GET": "user_me_get",
"PUT": "user_me_put",
"PATCH": "user_me_patch",
"DELETE": "user_me_delete",
}
)

urlpatterns = []

if me_dispatcher:
me_list = path(
"users/me/",
me_dispatcher,
name="user-me",
)
urlpatterns.append(me_list)
33 changes: 33 additions & 0 deletions djoser/urls/password.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from django.urls import path

from djoser.conf import settings

urlpatterns = []

# Configure password reset confirm endpoint
password_reset_confirm_view = getattr(settings.VIEWS, "password_reset_confirm", None)
if password_reset_confirm_view:
user_password_reset_confirm = path(
"users/reset_password_confirm/",
password_reset_confirm_view.as_view(),
name="user-reset-password-confirm",
)
urlpatterns.append(user_password_reset_confirm)

# Configure password reset endpoint
password_reset_view = getattr(settings.VIEWS, "password_reset", None)
if password_reset_view:
user_reset_password = path(
"users/reset_password/",
password_reset_view.as_view(),
name="user-reset-password",
)
urlpatterns.append(user_reset_password)

# Configure set password endpoint
set_password_view = getattr(settings.VIEWS, "set_password", None)
if set_password_view:
user_set_password = path(
"users/set_password/", set_password_view.as_view(), name="user-set-password"
)
urlpatterns.append(user_set_password)
44 changes: 44 additions & 0 deletions djoser/urls/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.urls import path, register_converter

from djoser.urls.converters import UserLookupConverter
from djoser.urls.utils import create_configurable_dispatcher

register_converter(UserLookupConverter, "djoser_lookup")

# Create configurable dispatchers for user endpoints
user_list_create_dispatcher = create_configurable_dispatcher(
{
"GET": "user_list",
"POST": "user_create",
}
)

user_detail_dispatcher = create_configurable_dispatcher(
{
"GET": "user_detail",
"PUT": "user_update_put",
"PATCH": "user_update_patch",
"DELETE": "user_delete",
}
)

urlpatterns = []

if user_list_create_dispatcher:
user_list_create = path(
"users/",
user_list_create_dispatcher,
name="user-list",
)
urlpatterns.append(user_list_create)

if user_detail_dispatcher:
# Import to get lookup field - only if needed
from djoser.views.user.retrieve import UserRetrieveView

user_detail_update_delete = path(
f"users/<djoser_lookup:{UserRetrieveView.lookup_field}>/",
user_detail_dispatcher,
name="user-detail",
)
urlpatterns.append(user_detail_update_delete)
38 changes: 38 additions & 0 deletions djoser/urls/username.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from django.contrib.auth import get_user_model
from django.urls import path

from djoser.conf import settings

User = get_user_model()

urlpatterns = []

# Configure username reset endpoint
username_reset_view = getattr(settings.VIEWS, "username_reset", None)
if username_reset_view:
user_reset_username = path(
f"users/reset_{User.USERNAME_FIELD}/",
username_reset_view.as_view(),
name="user-reset-username",
)
urlpatterns.append(user_reset_username)

# Configure username reset confirm endpoint
username_reset_confirm_view = getattr(settings.VIEWS, "username_reset_confirm", None)
if username_reset_confirm_view:
user_reset_username_confirm = path(
f"users/reset_{User.USERNAME_FIELD}_confirm/",
username_reset_confirm_view.as_view(),
name="user-reset-username-confirm",
)
urlpatterns.append(user_reset_username_confirm)

# Configure set username endpoint
set_username_view = getattr(settings.VIEWS, "set_username", None)
if set_username_view:
user_set_username = path(
f"users/set_{User.USERNAME_FIELD}/",
set_username_view.as_view(),
name="user-set-username",
)
urlpatterns.append(user_set_username)
Loading