././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/LICENSE 0000644 0000000 0000000 00000002073 00000000000 012141 0 ustar 00 0000000 0000000 The MIT License (MIT)
Copyright (c) 2013-2023 SUNSCRAPERS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/README.rst 0000644 0000000 0000000 00000007657 00000000000 012640 0 ustar 00 0000000 0000000 ======
djoser
======
.. image:: https://img.shields.io/pypi/v/djoser.svg
:target: https://pypi.org/project/djoser
.. image:: https://github.com/sunscrapers/djoser/workflows/Tests/badge.svg
:target: https://github.com/sunscrapers/djoser/actions?query=branch%3Amaster+workflow%Tests++
:alt: Build Status
.. image:: https://codecov.io/gh/sunscrapers/djoser/branch/master/graph/badge.svg
:target: https://codecov.io/gh/sunscrapers/djoser
.. image:: https://img.shields.io/pypi/dm/djoser
:target: https://img.shields.io/pypi/dm/djoser
.. image:: https://readthedocs.org/projects/djoser/badge/?version=latest
:target: https://djoser.readthedocs.io/en/latest/
:alt: Docs
REST implementation of `Django `_ authentication
system. **djoser** library provides a set of `Django Rest Framework `_
views to handle basic actions such as registration, login, logout, password
reset and account activation. It works with
`custom user model `_.
Instead of reusing Django code (e.g. ``PasswordResetForm``), we reimplemented
few things to fit better into `Single Page App `_
architecture.
Developed by `SUNSCRAPERS `_ with passion & patience.
.. image:: https://asciinema.org/a/94J4eG2tSBD2iEfF30a6vGtXw.png
:target: https://asciinema.org/a/94J4eG2tSBD2iEfF30a6vGtXw
Requirements
============
To be able to run **djoser** you have to meet the following requirements:
- Python>=3.8
- Django>=3.0.0
- Django REST Framework>=3.12
Installation
============
Simply install using ``pip``:
.. code-block:: bash
$ pip install djoser
And continue with the steps described at
`configuration `_
guide.
Documentation
=============
Documentation is available to study at
`https://djoser.readthedocs.io `_
and in ``docs`` directory.
Contributing and development
============================
To start developing on **djoser**, clone the repository:
.. code-block:: bash
$ git clone git@github.com:sunscrapers/djoser.git
We use `poetry `_ as dependency management and packaging tool.
.. code-block:: bash
$ cd djoser
$ poetry install --all-extras
This will create a virtualenv with all development dependencies.
To run the test just type:
.. code-block:: bash
$ poetry run py.test testproject
We also prepared a convenient ``Makefile`` to automate commands above:
.. code-block:: bash
$ make init
$ make test
To activate the virtual environment run
.. code-block:: bash
$ poetry shell
Without poetry
--------------
New versions of ``pip`` can use ``pyproject.toml`` to build the package and install its dependencies.
.. code-block:: bash
$ pip install .[test]
.. code-block:: bash
$ cd testproject
$ ./manage.py test
Example project
---------------
You can also play with test project by running following commands:
.. code-block:: bash
$ make migrate
$ make runserver
Commiting your code
-------------------
Before sending patches please make sure you have `pre-commit `_ activated in your local git repository:
.. code-block:: bash
$ pre-commit install
This will ensure that your code is cleaned before you commit it.
Similar projects
================
List of projects related to Django, REST and authentication:
- `django-rest-registration `_
- `django-oauth-toolkit `_
Please, keep in mind that while using custom authentication and TokenCreateSerializer
validation, there is a path that **ignores intentional return of None** from authenticate()
and try to find User using parameters. Probably, that will be changed in the future.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/__init__.py 0000644 0000000 0000000 00000000026 00000000000 014527 0 ustar 00 0000000 0000000 __version__ = "2.2.0"
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/auth_backends.py 0000644 0000000 0000000 00000002030 00000000000 015560 0 ustar 00 0000000 0000000 from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from djoser.conf import settings
UserModel = get_user_model()
class LoginFieldBackend(ModelBackend):
"""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:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
get_kwargs = {
settings.LOGIN_FIELD: username,
}
try:
user = UserModel._default_manager.get(**get_kwargs)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/compat.py 0000644 0000000 0000000 00000000413 00000000000 014253 0 ustar 00 0000000 0000000 from djoser.conf import settings
__all__ = ["settings"]
def get_user_email(user):
email_field_name = get_user_email_field_name(user)
return getattr(user, email_field_name, None)
def get_user_email_field_name(user):
return user.get_email_field_name()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/conf.py 0000644 0000000 0000000 00000015652 00000000000 013730 0 ustar 00 0000000 0000000 # flake8: noqa E501
from django.apps import apps
from django.conf import settings as django_settings
from django.test.signals import setting_changed
from django.utils.functional import LazyObject
from django.utils.module_loading import import_string
DJOSER_SETTINGS_NAMESPACE = "DJOSER"
auth_module, user_model = django_settings.AUTH_USER_MODEL.rsplit(".", 1)
User = apps.get_model(auth_module, user_model)
class ObjDict(dict):
def __getattribute__(self, item):
try:
val = self[item]
if isinstance(val, str):
val = import_string(val)
elif isinstance(val, (list, tuple)):
val = [import_string(v) if isinstance(v, str) else v for v in val]
self[item] = val
except KeyError:
val = super().__getattribute__(item)
return val
default_settings = {
"USER_ID_FIELD": User._meta.pk.name,
"LOGIN_FIELD": User.USERNAME_FIELD,
"SEND_ACTIVATION_EMAIL": False,
"SEND_CONFIRMATION_EMAIL": False,
"USER_CREATE_PASSWORD_RETYPE": False,
"SET_PASSWORD_RETYPE": False,
"PASSWORD_RESET_CONFIRM_RETYPE": False,
"SET_USERNAME_RETYPE": False,
"USERNAME_RESET_CONFIRM_RETYPE": False,
"PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": False,
"USERNAME_RESET_SHOW_EMAIL_NOT_FOUND": False,
"PASSWORD_CHANGED_EMAIL_CONFIRMATION": False,
"USERNAME_CHANGED_EMAIL_CONFIRMATION": False,
"TOKEN_MODEL": "rest_framework.authtoken.models.Token",
"SERIALIZERS": ObjDict(
{
"activation": "djoser.serializers.ActivationSerializer",
"password_reset": "djoser.serializers.SendEmailResetSerializer",
"password_reset_confirm": "djoser.serializers.PasswordResetConfirmSerializer",
"password_reset_confirm_retype": "djoser.serializers.PasswordResetConfirmRetypeSerializer",
"set_password": "djoser.serializers.SetPasswordSerializer",
"set_password_retype": "djoser.serializers.SetPasswordRetypeSerializer",
"set_username": "djoser.serializers.SetUsernameSerializer",
"set_username_retype": "djoser.serializers.SetUsernameRetypeSerializer",
"username_reset": "djoser.serializers.SendEmailResetSerializer",
"username_reset_confirm": "djoser.serializers.UsernameResetConfirmSerializer",
"username_reset_confirm_retype": "djoser.serializers.UsernameResetConfirmRetypeSerializer",
"user_create": "djoser.serializers.UserCreateSerializer",
"user_create_password_retype": "djoser.serializers.UserCreatePasswordRetypeSerializer",
"user_delete": "djoser.serializers.UserDeleteSerializer",
"user": "djoser.serializers.UserSerializer",
"current_user": "djoser.serializers.UserSerializer",
"token": "djoser.serializers.TokenSerializer",
"token_create": "djoser.serializers.TokenCreateSerializer",
}
),
"EMAIL": ObjDict(
{
"activation": "djoser.email.ActivationEmail",
"confirmation": "djoser.email.ConfirmationEmail",
"password_reset": "djoser.email.PasswordResetEmail",
"password_changed_confirmation": "djoser.email.PasswordChangedConfirmationEmail",
"username_changed_confirmation": "djoser.email.UsernameChangedConfirmationEmail",
"username_reset": "djoser.email.UsernameResetEmail",
}
),
"EMAIL_FRONTEND_DOMAIN": None,
"EMAIL_FRONTEND_PROTOCOL": None,
"EMAIL_FRONTEND_SITE_NAME": None,
"CONSTANTS": ObjDict({"messages": "djoser.constants.Messages"}),
"LOGOUT_ON_PASSWORD_CHANGE": False,
"CREATE_SESSION_ON_LOGIN": False,
"SOCIAL_AUTH_TOKEN_STRATEGY": "djoser.social.token.jwt.TokenStrategy",
"SOCIAL_AUTH_ALLOWED_REDIRECT_URIS": [],
"HIDE_USERS": True,
"PERMISSIONS": ObjDict(
{
"activation": ["rest_framework.permissions.AllowAny"],
"password_reset": ["rest_framework.permissions.AllowAny"],
"password_reset_confirm": ["rest_framework.permissions.AllowAny"],
"set_password": ["djoser.permissions.CurrentUserOrAdmin"],
"username_reset": ["rest_framework.permissions.AllowAny"],
"username_reset_confirm": ["rest_framework.permissions.AllowAny"],
"set_username": ["djoser.permissions.CurrentUserOrAdmin"],
"user_create": ["rest_framework.permissions.AllowAny"],
"user_delete": ["djoser.permissions.CurrentUserOrAdmin"],
"user": ["djoser.permissions.CurrentUserOrAdmin"],
"user_list": ["djoser.permissions.CurrentUserOrAdmin"],
"token_create": ["rest_framework.permissions.AllowAny"],
"token_destroy": ["rest_framework.permissions.IsAuthenticated"],
}
),
"WEBAUTHN": ObjDict(
{
"RP_NAME": "localhost",
"RP_ID": "localhost",
"ORIGIN": "http://localhost:8000",
"CHALLENGE_LENGTH": 32,
"UKEY_LENGTH": 20,
"SIGNUP_SERIALIZER": "djoser.webauthn.serializers.WebauthnCreateUserSerializer",
"LOGIN_SERIALIZER": "djoser.webauthn.serializers.WebauthnLoginSerializer",
}
),
}
SETTINGS_TO_IMPORT = ["TOKEN_MODEL", "SOCIAL_AUTH_TOKEN_STRATEGY"]
class Settings:
def __init__(self, default_settings, explicit_overriden_settings: dict = None):
if explicit_overriden_settings is None:
explicit_overriden_settings = {}
overriden_settings = (
getattr(django_settings, DJOSER_SETTINGS_NAMESPACE, {})
or explicit_overriden_settings
)
self._load_default_settings()
self._override_settings(overriden_settings)
self._init_settings_to_import()
def _load_default_settings(self):
for setting_name, setting_value in default_settings.items():
if setting_name.isupper():
setattr(self, setting_name, setting_value)
def _override_settings(self, overriden_settings: dict):
for setting_name, setting_value in overriden_settings.items():
value = setting_value
if isinstance(setting_value, dict):
value = getattr(self, setting_name, {})
value.update(ObjDict(setting_value))
setattr(self, setting_name, value)
def _init_settings_to_import(self):
for setting_name in SETTINGS_TO_IMPORT:
value = getattr(self, setting_name)
if isinstance(value, str):
setattr(self, setting_name, import_string(value))
class LazySettings(LazyObject):
def _setup(self, explicit_overriden_settings=None):
self._wrapped = Settings(default_settings, explicit_overriden_settings)
settings = LazySettings()
def reload_djoser_settings(*args, **kwargs):
global settings
setting, value = kwargs["setting"], kwargs["value"]
if setting == DJOSER_SETTINGS_NAMESPACE:
settings._setup(explicit_overriden_settings=value)
setting_changed.connect(reload_djoser_settings)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/constants.py 0000644 0000000 0000000 00000001376 00000000000 015015 0 ustar 00 0000000 0000000 from django.utils.translation import gettext_lazy as _
class Messages:
INVALID_CREDENTIALS_ERROR = _("Unable to log in with provided credentials.")
INACTIVE_ACCOUNT_ERROR = _(
"User account is disabled."
) # not in use since Django 1.10
INVALID_TOKEN_ERROR = _("Invalid token for given user.")
INVALID_UID_ERROR = _("Invalid user id or user doesn't exist.")
STALE_TOKEN_ERROR = _("Stale token for given user.")
PASSWORD_MISMATCH_ERROR = _("The two password fields didn't match.")
USERNAME_MISMATCH_ERROR = _("The two {0} fields didn't match.")
INVALID_PASSWORD_ERROR = _("Invalid password.")
EMAIL_NOT_FOUND = _("User with given email does not exist.")
CANNOT_CREATE_USER_ERROR = _("Unable to create account.")
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1731171455.7909062
djoser-2.3.1/djoser/email.py 0000644 0000000 0000000 00000012764 00000000000 014073 0 ustar 00 0000000 0000000 from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from djoser import utils
from django.conf import settings as django_settings
from djoser.conf import settings
from django.core import mail
from django.template.context import make_context
from django.template.loader import get_template
from django.views.generic.base import ContextMixin
class BaseEmailMessage(mail.EmailMultiAlternatives, ContextMixin):
_node_map = {
"subject": "subject",
"text_body": "body",
"html_body": "html",
}
template_name = None
def __init__(self, request=None, context=None, template_name=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.context = {} if context is None else context
self.html = None
if template_name is not None:
self.template_name = template_name
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
context = dict(ctx, **self.context)
if self.request:
site = get_current_site(self.request)
domain = context.get("domain") or (
getattr(django_settings, "DOMAIN", "") or site.domain
)
protocol = context.get("protocol") or (
"https" if self.request.is_secure() else "http"
)
site_name = context.get("site_name") or (
getattr(django_settings, "SITE_NAME", "") or site.name
)
user = context.get("user") or self.request.user
else:
domain = context.get("domain") or getattr(django_settings, "DOMAIN", "")
protocol = context.get("protocol") or "http"
site_name = context.get("site_name") or getattr(
django_settings, "SITE_NAME", ""
)
user = context.get("user")
context.update(
{
"domain": domain,
"protocol": protocol,
"site_name": site_name,
"user": user,
}
)
return context
def render(self):
context = make_context(self.get_context_data(), request=self.request)
template = get_template(self.template_name)
with context.bind_template(template.template):
for node in template.template.nodelist:
self._process_node(node, context)
self._attach_body()
# custom interface incompatible with django, `to` is a required param
def send(self, to, fail_silently=False, **kwargs):
self.render()
self.to = to
self.cc = kwargs.pop("cc", [])
self.bcc = kwargs.pop("bcc", [])
self.reply_to = kwargs.pop("reply_to", [])
self.from_email = kwargs.pop("from_email", django_settings.DEFAULT_FROM_EMAIL)
self.request = None
super().send(fail_silently=fail_silently)
def _process_node(self, node, context):
attr = self._node_map.get(getattr(node, "name", ""))
if attr is not None:
setattr(self, attr, node.render(context).strip())
def _attach_body(self):
if self.body and self.html:
self.attach_alternative(self.html, "text/html")
elif self.html:
self.body = self.html
self.content_subtype = "html"
class BaseDjoserEmail(BaseEmailMessage):
def get_context_data(self):
context = super().get_context_data()
overridable = {
"protocol": settings.EMAIL_FRONTEND_PROTOCOL,
"domain": settings.EMAIL_FRONTEND_DOMAIN,
"site_name": settings.EMAIL_FRONTEND_SITE_NAME,
}
for context_key, context_value in overridable.items():
if context_value:
context.update({context_key: context_value})
context.pop("view", None)
return context
class ActivationEmail(BaseDjoserEmail):
template_name = "email/activation.html"
def get_context_data(self):
# ActivationEmail can be deleted
context = super().get_context_data()
user = context.get("user")
context["uid"] = utils.encode_uid(user.pk)
context["token"] = default_token_generator.make_token(user)
context["url"] = settings.ACTIVATION_URL.format(**context)
return context
class ConfirmationEmail(BaseDjoserEmail):
template_name = "email/confirmation.html"
class PasswordResetEmail(BaseDjoserEmail):
template_name = "email/password_reset.html"
def get_context_data(self):
# PasswordResetEmail can be deleted
context = super().get_context_data()
user = context.get("user")
context["uid"] = utils.encode_uid(user.pk)
context["token"] = default_token_generator.make_token(user)
context["url"] = settings.PASSWORD_RESET_CONFIRM_URL.format(**context)
return context
class PasswordChangedConfirmationEmail(BaseDjoserEmail):
template_name = "email/password_changed_confirmation.html"
class UsernameChangedConfirmationEmail(BaseDjoserEmail):
template_name = "email/username_changed_confirmation.html"
class UsernameResetEmail(BaseDjoserEmail):
template_name = "email/username_reset.html"
def get_context_data(self):
context = super().get_context_data()
user = context.get("user")
context["uid"] = utils.encode_uid(user.pk)
context["token"] = default_token_generator.make_token(user)
context["url"] = settings.USERNAME_RESET_CONFIRM_URL.format(**context)
return context
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 011451 x ustar 00 0000000 0000000 27 mtime=1731171467.683026
djoser-2.3.1/djoser/locale/ca/LC_MESSAGES/django.mo 0000644 0000000 0000000 00000007136 00000000000 017635 0 ustar 00 0000000 0000000 I <