Commit 442e5bcc authored by Michael Fladischer's avatar Michael Fladischer

New upstream version 1.7.0

parent f120eb42
include README.md
include LICENSE
recursive-include djoser/templates *.html
recursive-include djoser/locale *.mo
include requirements.txt
Metadata-Version: 1.1
Name: djoser
Version: 1.4.0
Version: 1.7.0
Summary: REST version of Django authentication system.
Home-page: https://github.com/sunscrapers/djoser
Author: SUNSCRAPERS
......@@ -43,7 +43,7 @@ Description: ======
To be able to run **djoser** you have to meet following requirements:
- Python (2.7, 3.4, 3.5, 3.6)
- Django (1.11, 2.0, 2.1)
- Django (1.11, 2.0, 2.1, 2.2)
- Django REST Framework (3.7, 3.8, 3.9)
Bear in mind that for Django-2.x you will need at least Python 3.5
......@@ -132,6 +132,7 @@ Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.11
Classifier: Framework :: Django :: 2.0
Classifier: Framework :: Django :: 2.1
Classifier: Framework :: Django :: 2.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
......
......@@ -35,7 +35,7 @@ Requirements
To be able to run **djoser** you have to meet following requirements:
- Python (2.7, 3.4, 3.5, 3.6)
- Django (1.11, 2.0, 2.1)
- Django (1.11, 2.0, 2.1, 2.2)
- Django REST Framework (3.7, 3.8, 3.9)
Bear in mind that for Django-2.x you will need at least Python 3.5
......
Metadata-Version: 1.1
Name: djoser
Version: 1.4.0
Version: 1.7.0
Summary: REST version of Django authentication system.
Home-page: https://github.com/sunscrapers/djoser
Author: SUNSCRAPERS
......@@ -43,7 +43,7 @@ Description: ======
To be able to run **djoser** you have to meet following requirements:
- Python (2.7, 3.4, 3.5, 3.6)
- Django (1.11, 2.0, 2.1)
- Django (1.11, 2.0, 2.1, 2.2)
- Django REST Framework (3.7, 3.8, 3.9)
Bear in mind that for Django-2.x you will need at least Python 3.5
......@@ -132,6 +132,7 @@ Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.11
Classifier: Framework :: Django :: 2.0
Classifier: Framework :: Django :: 2.1
Classifier: Framework :: Django :: 2.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
......
__version__ = "1.4.0"
__version__ = "1.7.0"
import warnings
from django.apps import apps
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.test.signals import setting_changed
......@@ -9,6 +10,10 @@ 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):
......@@ -26,8 +31,10 @@ class ObjDict(dict):
default_settings = {
'LOGIN_FIELD': User.USERNAME_FIELD,
'SEND_ACTIVATION_EMAIL': False,
'SEND_CONFIRMATION_EMAIL': False,
'USER_CREATE_PASSWORD_RETYPE': False,
'SET_PASSWORD_RETYPE': False,
'SET_USERNAME_RETYPE': False,
'PASSWORD_RESET_CONFIRM_RETYPE': False,
......@@ -53,6 +60,8 @@ default_settings = {
'djoser.serializers.SetUsernameRetypeSerializer',
'user_create':
'djoser.serializers.UserCreateSerializer',
'user_create_password_retype':
'djoser.serializers.UserCreatePasswordRetypeSerializer',
'user_delete':
'djoser.serializers.UserDeleteSerializer',
'user':
......@@ -69,6 +78,9 @@ default_settings = {
'confirmation': 'djoser.email.ConfirmationEmail',
'password_reset': 'djoser.email.PasswordResetEmail',
}),
'CONSTANTS': ObjDict({
'messages': 'djoser.constants.Messages',
}),
'LOGOUT_ON_PASSWORD_CHANGE': False,
'CREATE_SESSION_ON_LOGIN': False,
'USER_EMAIL_FIELD_NAME': 'email',
......
from django.utils.translation import ugettext_lazy as _
INVALID_CREDENTIALS_ERROR = _('Unable to login with provided credentials.')
INACTIVE_ACCOUNT_ERROR = _('User account is disabled.')
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.')
USER_WITHOUT_EMAIL_FIELD_ERROR = _(
'User model does not contain specified email field. '
'Please see http://djoser.readthedocs.io/en/latest/settings.html#'
'USER_EMAIL_FIELD_NAME for more details.'
)
class Messages(object):
INVALID_CREDENTIALS_ERROR = _('Unable to log in with provided credentials.')
INACTIVE_ACCOUNT_ERROR = _('User account is disabled.')
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.')
USER_WITHOUT_EMAIL_FIELD_ERROR = _(
'User model does not contain specified email field. '
'Please see http://djoser.readthedocs.io/en/latest/settings.html#'
'USER_EMAIL_FIELD_NAME for more details.'
)
......@@ -4,10 +4,10 @@ from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.password_validation import validate_password
from django.core import exceptions as django_exceptions
from django.db import IntegrityError, transaction
from rest_framework import exceptions, serializers
from rest_framework.exceptions import ValidationError
from djoser import constants, utils
from djoser import utils
from djoser.compat import get_user_email, get_user_email_field_name
from djoser.conf import settings
......@@ -19,9 +19,9 @@ class UserSerializer(serializers.ModelSerializer):
model = User
fields = tuple(User.REQUIRED_FIELDS) + (
User._meta.pk.name,
User.USERNAME_FIELD,
settings.LOGIN_FIELD,
)
read_only_fields = (User.USERNAME_FIELD,)
read_only_fields = (settings.LOGIN_FIELD,)
def update(self, instance, validated_data):
email_field = get_user_email_field_name(User)
......@@ -57,13 +57,13 @@ class UserCreateSerializer(serializers.ModelSerializer):
)
default_error_messages = {
'cannot_create_user': constants.CANNOT_CREATE_USER_ERROR,
'cannot_create_user': settings.CONSTANTS.messages.CANNOT_CREATE_USER_ERROR,
}
class Meta:
model = User
fields = tuple(User.REQUIRED_FIELDS) + (
User.USERNAME_FIELD, User._meta.pk.name, 'password',
settings.LOGIN_FIELD, User._meta.pk.name, 'password',
)
def validate(self, attrs):
......@@ -73,7 +73,10 @@ class UserCreateSerializer(serializers.ModelSerializer):
try:
validate_password(password, user)
except django_exceptions.ValidationError as e:
raise serializers.ValidationError({'password': list(e.messages)})
serializer_error = serializers.as_serializer_error(e)
raise serializers.ValidationError({
'password': serializer_error['non_field_errors']
})
return attrs
......@@ -94,53 +97,73 @@ class UserCreateSerializer(serializers.ModelSerializer):
return user
class UserCreatePasswordRetypeSerializer(UserCreateSerializer):
default_error_messages = {
'password_mismatch': settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR,
}
def __init__(self, *args, **kwargs):
super(UserCreatePasswordRetypeSerializer, self).__init__(*args, **kwargs)
self.fields['re_password'] = serializers.CharField(style={'input_type': 'password'})
def validate(self, attrs):
re_password = attrs.pop('re_password')
attrs = super(UserCreatePasswordRetypeSerializer, self).validate(attrs)
if attrs['password'] == re_password:
return attrs
else:
self.fail('password_mismatch')
class TokenCreateSerializer(serializers.Serializer):
password = serializers.CharField(
required=False, style={'input_type': 'password'}
)
default_error_messages = {
'invalid_credentials': constants.INVALID_CREDENTIALS_ERROR,
'inactive_account': constants.INACTIVE_ACCOUNT_ERROR,
'invalid_credentials': settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR,
'inactive_account': settings.CONSTANTS.messages.INACTIVE_ACCOUNT_ERROR,
}
def __init__(self, *args, **kwargs):
super(TokenCreateSerializer, self).__init__(*args, **kwargs)
self.user = None
self.fields[User.USERNAME_FIELD] = serializers.CharField(
self.fields[settings.LOGIN_FIELD] = serializers.CharField(
required=False
)
def validate(self, attrs):
self.user = authenticate(
username=attrs.get(User.USERNAME_FIELD),
username=attrs.get(settings.LOGIN_FIELD),
password=attrs.get('password')
)
self._validate_user_exists(self.user)
self._validate_user_is_active(self.user)
return attrs
def _validate_user_exists(self, user):
if not user:
self.fail('invalid_credentials')
def _validate_user_is_active(self, user):
if not user.is_active:
self.fail('inactive_account')
class PasswordResetSerializer(serializers.Serializer):
email = serializers.EmailField()
default_error_messages = {'email_not_found': settings.CONSTANTS.messages.EMAIL_NOT_FOUND}
default_error_messages = {'email_not_found': constants.EMAIL_NOT_FOUND}
def __init__(self, *args, **kwargs):
super(PasswordResetSerializer, self).__init__(*args, **kwargs)
def validate_email(self, value):
users = self.context['view'].get_users(value)
if settings.PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND and not users:
self.fail('email_not_found')
else:
return value
email_field = get_user_email_field_name(User)
self.fields[email_field] = serializers.EmailField()
validate_email_fn_name = 'validate_' + email_field
def validate_email_fn(self, value):
users = self.context['view'].get_users(value)
if settings.PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND and not users:
self.fail('email_not_found')
else:
return value
setattr(PasswordResetSerializer, validate_email_fn_name, validate_email_fn)
class UidAndTokenSerializer(serializers.Serializer):
......@@ -148,8 +171,8 @@ class UidAndTokenSerializer(serializers.Serializer):
token = serializers.CharField()
default_error_messages = {
'invalid_token': constants.INVALID_TOKEN_ERROR,
'invalid_uid': constants.INVALID_UID_ERROR,
'invalid_token': settings.CONSTANTS.messages.INVALID_TOKEN_ERROR,
'invalid_uid': settings.CONSTANTS.messages.INVALID_UID_ERROR,
}
def validate_uid(self, value):
......@@ -169,11 +192,12 @@ class UidAndTokenSerializer(serializers.Serializer):
if is_token_valid:
return attrs
else:
self.fail('invalid_token')
key_error = 'invalid_token'
raise ValidationError({'token': [self.error_messages[key_error]]}, code=key_error)
class ActivationSerializer(UidAndTokenSerializer):
default_error_messages = {'stale_token': constants.STALE_TOKEN_ERROR}
default_error_messages = {'stale_token': settings.CONSTANTS.messages.STALE_TOKEN_ERROR}
def validate(self, attrs):
attrs = super(ActivationSerializer, self).validate(attrs)
......@@ -202,7 +226,7 @@ class PasswordRetypeSerializer(PasswordSerializer):
re_new_password = serializers.CharField(style={'input_type': 'password'})
default_error_messages = {
'password_mismatch': constants.PASSWORD_MISMATCH_ERROR,
'password_mismatch': settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR,
}
def validate(self, attrs):
......@@ -217,7 +241,7 @@ class CurrentPasswordSerializer(serializers.Serializer):
current_password = serializers.CharField(style={'input_type': 'password'})
default_error_messages = {
'invalid_password': constants.INVALID_PASSWORD_ERROR,
'invalid_password': settings.CONSTANTS.messages.INVALID_PASSWORD_ERROR,
}
def validate_current_password(self, value):
......@@ -253,37 +277,37 @@ class UserDeleteSerializer(CurrentPasswordSerializer):
class SetUsernameSerializer(serializers.ModelSerializer,
CurrentPasswordSerializer):
class Meta(object):
model = User
fields = (User.USERNAME_FIELD, 'current_password')
fields = (settings.LOGIN_FIELD, 'current_password')
def __init__(self, *args, **kwargs):
"""
This method should probably be replaced by a better solution.
Its purpose is to replace USERNAME_FIELD with 'new_' + USERNAME_FIELD
so that the new field is being assigned a field for USERNAME_FIELD
"""
super(SetUsernameSerializer, self).__init__(*args, **kwargs)
username_field = User.USERNAME_FIELD
self.fields['new_' + username_field] = self.fields.pop(username_field)
self.username_field = settings.LOGIN_FIELD
self._default_username_field = self.Meta.model.USERNAME_FIELD
self.fields['new_' + self.username_field] = self.fields.pop(self.username_field)
def save(self, **kwargs):
if self.username_field != self._default_username_field:
kwargs[User.USERNAME_FIELD] = self.validated_data.get('new_' + self.username_field)
return super(SetUsernameSerializer, self).save(**kwargs)
class SetUsernameRetypeSerializer(SetUsernameSerializer):
default_error_messages = {
'username_mismatch': constants.USERNAME_MISMATCH_ERROR.format(
User.USERNAME_FIELD
'username_mismatch': settings.CONSTANTS.messages.USERNAME_MISMATCH_ERROR.format(
settings.LOGIN_FIELD
),
}
def __init__(self, *args, **kwargs):
super(SetUsernameRetypeSerializer, self).__init__(*args, **kwargs)
self.fields['re_new_' + User.USERNAME_FIELD] = serializers.CharField()
self.fields['re_new_' + settings.LOGIN_FIELD] = serializers.CharField()
def validate(self, attrs):
attrs = super(SetUsernameRetypeSerializer, self).validate(attrs)
new_username = attrs[User.USERNAME_FIELD]
if new_username != attrs['re_new_' + User.USERNAME_FIELD]:
new_username = attrs[settings.LOGIN_FIELD]
if new_username != attrs['re_new_' + settings.LOGIN_FIELD]:
self.fail('username_mismatch')
else:
return attrs
......
......@@ -8,7 +8,7 @@
{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page to activate account:" %}
{{ protocol }}://{{ domain }}/{{ url }}
{{ protocol }}://{{ domain }}/{{ url|safe }}
{% trans "Thanks for using our site!" %}
......@@ -16,11 +16,10 @@
{% endblock text_body %}
{% block html_body %}
{% blocktrans %}
<p>You're receiving this email because you need to finish activation process on {{ site_name }}.</p>{% endblocktrans %}
<p>{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}</p>
<p>{% trans "Please go to the following page to activate account:" %}</p>
<p><a href="{{ protocol }}://{{ domain }}/{{ url }}">{{ protocol }}://{{ domain }}/{{ url }}</a></p>
<p><a href="{{ protocol }}://{{ domain }}/{{ url|safe }}">{{ protocol }}://{{ domain }}/{{ url|safe }}</a></p>
<p>{% trans "Thanks for using our site!" %}</p>
......
......@@ -27,6 +27,11 @@ urlpatterns = [
views.ActivationView.as_view(),
name='user-activate'
),
url(
r'^users/resend/?$',
views.ResendActivationView.as_view(),
name='user-activate-resend'
),
url(
r'^{0}/?$'.format(User.USERNAME_FIELD),
views.SetUsernameView.as_view(),
......
......@@ -6,7 +6,7 @@ from djoser.conf import settings
def encode_uid(pk):
return urlsafe_base64_encode(force_bytes(pk)).decode()
return force_text(urlsafe_base64_encode(force_bytes(pk)))
def decode_uid(pk):
......@@ -32,7 +32,7 @@ def logout_user(request):
class ActionViewMixin(object):
def post(self, request):
def post(self, request, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return self._action(serializer)
......@@ -2,7 +2,13 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.urls.exceptions import NoReverseMatch
from django.utils.timezone import now
from rest_framework import generics, permissions, status, views, viewsets
from rest_framework import (
generics,
permissions,
response,
status, views,
viewsets,
)
from rest_framework.decorators import list_route
from rest_framework.response import Response
from rest_framework.reverse import reverse
......@@ -10,7 +16,7 @@ from rest_framework.reverse import reverse
from djoser import utils, signals
from djoser.compat import get_user_email, get_user_email_field_name
from djoser.conf import settings
from djoser.utils import ActionViewMixin
User = get_user_model()
......@@ -61,13 +67,7 @@ class RootView(views.APIView):
return []
class UserCreateView(generics.CreateAPIView):
"""
Use this endpoint to register new user.
"""
serializer_class = settings.SERIALIZERS.user_create
permission_classes = settings.PERMISSIONS.user_create
class UserCreateMixin:
def perform_create(self, serializer):
user = serializer.save()
signals.user_registered.send(
......@@ -82,6 +82,51 @@ class UserCreateView(generics.CreateAPIView):
settings.EMAIL.confirmation(self.request, context).send(to)
class UserCreateView(UserCreateMixin, generics.CreateAPIView):
"""
Use this endpoint to register new user.
"""
permission_classes = settings.PERMISSIONS.user_create
def get_serializer_class(self):
if settings.USER_CREATE_PASSWORD_RETYPE:
return settings.SERIALIZERS.user_create_password_retype
return settings.SERIALIZERS.user_create
class ResendActivationView(ActionViewMixin, generics.GenericAPIView):
"""
Use this endpoint to resend user activation email.
"""
serializer_class = settings.SERIALIZERS.password_reset
permission_classes = [permissions.AllowAny]
_users = None
def _action(self, serializer):
if not settings.SEND_ACTIVATION_EMAIL:
return response.Response(status=status.HTTP_400_BAD_REQUEST)
for user in self.get_users(serializer.data[get_user_email_field_name(User)]):
self.send_activation_email(user)
return response.Response(status=status.HTTP_204_NO_CONTENT)
def get_users(self, email):
if self._users is None:
email_field_name = get_user_email_field_name(User)
users = User._default_manager.filter(**{
email_field_name + '__iexact': email
})
self._users = [
u for u in users if not u.is_active and u.has_usable_password()
]
return self._users
def send_activation_email(self, user):
context = {'user': user}
to = [get_user_email(user)]
settings.EMAIL.activation(self.request, context).send(to)
class UserDeleteView(generics.CreateAPIView):
"""
Use this endpoint to remove actually authenticated user
......@@ -140,7 +185,7 @@ class PasswordResetView(utils.ActionViewMixin, generics.GenericAPIView):
_users = None
def _action(self, serializer):
for user in self.get_users(serializer.data['email']):
for user in self.get_users(serializer.data[get_user_email_field_name(User)]):
self.send_password_reset_email(user)
return Response(status=status.HTTP_204_NO_CONTENT)
......@@ -253,7 +298,18 @@ class SetUsernameView(utils.ActionViewMixin, generics.GenericAPIView):
return Response(status=status.HTTP_204_NO_CONTENT)
class UserView(generics.RetrieveUpdateAPIView):
class UserUpdateMixin(object):
def perform_update(self, serializer):
super(UserUpdateMixin, self).perform_update(serializer)
user = serializer.instance
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)
class UserView(UserUpdateMixin,
generics.RetrieveUpdateAPIView):
"""
Use this endpoint to retrieve/update user.
"""
......@@ -264,21 +320,22 @@ class UserView(generics.RetrieveUpdateAPIView):
def get_object(self, *args, **kwargs):
return self.request.user
def perform_update(self, serializer):
super(UserView, self).perform_update(serializer)
user = serializer.instance
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)
class UserViewSet(UserCreateView, viewsets.ModelViewSet):
class UserViewSet(UserCreateMixin,
UserUpdateMixin,
viewsets.ModelViewSet):
serializer_class = settings.SERIALIZERS.user
queryset = User.objects.all()
permission_classes = settings.PERMISSIONS.user
token_generator = default_token_generator
def get_queryset(self):
qs = super(UserViewSet, self).get_queryset()
user = self.request.user
if not (user.is_staff or user.is_superuser):
qs = qs.filter(pk=user.pk)
return qs
def get_permissions(self):
if self.action == 'create':
self.permission_classes = settings.PERMISSIONS.user_create
......@@ -289,11 +346,9 @@ class UserViewSet(UserCreateView, viewsets.ModelViewSet):
return super(UserViewSet, self).get_permissions()
def get_serializer_class(self):
if self.action == 'me':
# Use the current user serializer on 'me' endpoints
self.serializer_class = settings.SERIALIZERS.current_user
if self.action == 'create':
if settings.USER_CREATE_PASSWORD_RETYPE:
return settings.SERIALIZERS.user_create_password_retype
return settings.SERIALIZERS.user_create
elif self.action == 'remove' or (
......@@ -311,19 +366,15 @@ class UserViewSet(UserCreateView, viewsets.ModelViewSet):
return settings.SERIALIZERS.set_username
elif self.action == 'me':
# Use the current user serializer on 'me' endpoints
return settings.SERIALIZERS.current_user
return self.serializer_class
def get_instance(self):
return self.request.user
def perform_update(self, serializer):
super(UserViewSet, self).perform_update(serializer)
user = serializer.instance
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_destroy(self, instance):
utils.logout_user(self.request)
super(UserViewSet, self).perform_destroy(instance)
......
[compile_catalog]
domain = django
directory = djoser/locale
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
......@@ -17,7 +17,7 @@ def get_packages(package):
setup(
name='djoser',
version='1.4.0',
version='1.7.0',
packages=get_packages('djoser'),
license='MIT',
author='SUNSCRAPERS',
......@@ -25,6 +25,7 @@ setup(
author_email='info@sunscrapers.com',
long_description=readme,
install_requires=['django-templated-mail'],
setup_requires=['Babel>=2.6.0'],
include_package_data=True,
url='https://github.com/sunscrapers/djoser',
classifiers=[
......@@ -33,6 +34,7 @@ setup(
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Framework :: Django :: 2.1',
'Framework :: Django :: 2.2',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment