Skip to content

Commit ccc261d

Browse files
committed
2 parents c9d55f7 + 334a29c commit ccc261d

File tree

16 files changed

+241
-116
lines changed

16 files changed

+241
-116
lines changed

demo/demo/urls.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from django.conf.urls import patterns, include, url
1+
from django.conf.urls import include, url
22
from django.contrib import admin
33
from django.views.generic import TemplateView, RedirectView
44

5-
urlpatterns = patterns('',
5+
urlpatterns = [
66
url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'),
77
url(r'^signup/$', TemplateView.as_view(template_name="signup.html"),
88
name='signup'),
@@ -36,4 +36,4 @@
3636
url(r'^account/', include('allauth.urls')),
3737
url(r'^admin/', include(admin.site.urls)),
3838
url(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'),
39-
)
39+
]

docs/api_endpoints.rst

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Basic
77
- /rest-auth/login/ (POST)
88

99
- username (string)
10+
- email (string)
1011
- password (string)
1112

1213

@@ -55,16 +56,6 @@ Registration
5556
- password2
5657
- email
5758

58-
.. note:: This endpoint is based on ``allauth.account.views.SignupView`` and uses the same form as in this view. To override fields you have to create custom Signup Form and define it in django settings:
59-
60-
.. code-block:: python
61-
62-
ACCOUNT_FORMS = {
63-
'signup': 'path.to.custom.SignupForm'
64-
}
65-
66-
See allauth documentation for more details.
67-
6859
- /rest-auth/registration/verify-email/ (POST)
6960

7061
- key

docs/configuration.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ Configuration
2929
...
3030
}
3131
32+
- **REST_AUTH_REGISTRATION_SERIALIZERS**
33+
34+
You can define your custom serializers for registration endpoint.
35+
Possible key values:
36+
37+
- REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.register.serializers.RegisterSerializer``
38+
3239
- **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models``
3340

3441
- **REST_AUTH_TOKEN_CREATOR** - callable to create tokens, default value ``rest_auth.utils.default_create_token``.

docs/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ You're good to go now!
3838
Registration (optional)
3939
-----------------------
4040

41-
1. If you want to enable standard registration process you will need to install ``django-allauth`` - see this doc for installation http://django-allauth.readthedocs.org/en/latest/installation.html.
41+
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[extras]`` or ``pip install django-rest-auth[with_social]``.
4242

4343
2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
4444

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.conf import settings
2+
3+
from rest_auth.registration.serializers import (
4+
RegisterSerializer as DefaultRegisterSerializer)
5+
from ..utils import import_callable
6+
7+
8+
serializers = getattr(settings, 'REST_AUTH_REGISTER_SERIALIZERS', {})
9+
10+
RegisterSerializer = import_callable(
11+
serializers.get('REGISTER_SERIALIZER', DefaultRegisterSerializer))

rest_auth/registration/serializers.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
from django.http import HttpRequest
22
from django.conf import settings
33

4+
try:
5+
from allauth.account import app_settings as allauth_settings
6+
from allauth.utils import (email_address_exists,
7+
get_username_max_length)
8+
from allauth.account.adapter import get_adapter
9+
from allauth.account.utils import setup_user_email
10+
except ImportError:
11+
raise ImportError('allauth needs to be added to INSTALLED_APPS.')
12+
413
from rest_framework import serializers
514
from requests.exceptions import HTTPError
615
# Import is needed only if we are using social login, in which
@@ -109,3 +118,57 @@ def validate(self, attrs):
109118
attrs['user'] = login.account.user
110119

111120
return attrs
121+
122+
123+
class RegisterSerializer(serializers.Serializer):
124+
username = serializers.CharField(
125+
max_length=get_username_max_length(),
126+
min_length=allauth_settings.USERNAME_MIN_LENGTH,
127+
required=allauth_settings.USERNAME_REQUIRED
128+
)
129+
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
130+
password1 = serializers.CharField(required=True, write_only=True)
131+
password2 = serializers.CharField(required=True, write_only=True)
132+
133+
def validate_username(self, username):
134+
username = get_adapter().clean_username(username)
135+
return username
136+
137+
def validate_email(self, email):
138+
email = get_adapter().clean_email(email)
139+
if allauth_settings.UNIQUE_EMAIL:
140+
if email and email_address_exists(email):
141+
raise serializers.ValidationError(
142+
"A user is already registered with this e-mail address.")
143+
return email
144+
145+
def validate_password1(self, password):
146+
return get_adapter().clean_password(password)
147+
148+
def validate(self, data):
149+
if data['password1'] != data['password2']:
150+
raise serializers.ValidationError("The two password fields didn't match.")
151+
return data
152+
153+
def custom_signup(self, request, user):
154+
pass
155+
156+
def get_cleaned_data(self):
157+
return {
158+
'username': self.validated_data.get('username', ''),
159+
'password1': self.validated_data.get('password1', ''),
160+
'email': self.validated_data.get('email', '')
161+
}
162+
163+
def save(self, request):
164+
adapter = get_adapter()
165+
user = adapter.new_user(request)
166+
self.cleaned_data = self.get_cleaned_data()
167+
adapter.save_user(request, user, self)
168+
self.custom_signup(request, user)
169+
setup_user_email(request, user, [])
170+
return user
171+
172+
173+
class VerifyEmailSerializer(serializers.Serializer):
174+
key = serializers.CharField()

rest_auth/registration/urls.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from django.views.generic import TemplateView
2-
from django.conf.urls import patterns, url
2+
from django.conf.urls import url
33

44
from .views import RegisterView, VerifyEmailView
55

6-
urlpatterns = patterns(
7-
'',
6+
urlpatterns = [
87
url(r'^$', RegisterView.as_view(), name='rest_register'),
98
url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'),
109

@@ -21,4 +20,4 @@
2120
# djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
2221
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
2322
name='account_confirm_email'),
24-
)
23+
]

rest_auth/registration/views.py

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,44 @@
1-
from django.http import HttpRequest
21
from rest_framework.views import APIView
32
from rest_framework.response import Response
43
from rest_framework.permissions import AllowAny
4+
from rest_framework.generics import CreateAPIView
55
from rest_framework import status
6+
from rest_framework.exceptions import MethodNotAllowed
67

7-
from allauth.account.views import SignupView, ConfirmEmailView
8+
from allauth.account.views import ConfirmEmailView
89
from allauth.account.utils import complete_signup
9-
from allauth.account import app_settings
10+
from allauth.account import app_settings as allauth_settings
1011

11-
from rest_auth.app_settings import TokenSerializer
12-
from rest_auth.registration.serializers import SocialLoginSerializer
12+
from rest_auth.app_settings import (TokenSerializer,
13+
create_token)
14+
from rest_auth.registration.serializers import (SocialLoginSerializer,
15+
VerifyEmailSerializer)
1316
from rest_auth.views import LoginView
1417
from rest_auth.models import TokenModel
18+
from .app_settings import RegisterSerializer
1519

1620

17-
class RegisterView(APIView, SignupView):
18-
"""
19-
Accepts the credentials and creates a new user
20-
if user does not exist already
21-
Return the REST Token if the credentials are valid and authenticated.
22-
Calls allauth complete_signup method
23-
24-
Accept the following POST parameters: username, email, password
25-
Return the REST Framework Token Object's key.
26-
"""
27-
28-
permission_classes = (AllowAny,)
29-
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
21+
class RegisterView(CreateAPIView):
22+
serializer_class = RegisterSerializer
23+
permission_classes = (AllowAny, )
3024
token_model = TokenModel
31-
serializer_class = TokenSerializer
32-
33-
def get(self, *args, **kwargs):
34-
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
35-
36-
def put(self, *args, **kwargs):
37-
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
38-
39-
def form_valid(self, form):
40-
self.user = form.save(self.request)
41-
self.token, created = self.token_model.objects.get_or_create(
42-
user=self.user
43-
)
44-
if isinstance(self.request, HttpRequest):
45-
request = self.request
46-
else:
47-
request = self.request._request
48-
return complete_signup(request, self.user,
49-
app_settings.EMAIL_VERIFICATION,
50-
self.get_success_url())
51-
52-
def get_form_kwargs(self, *args, **kwargs):
53-
kwargs = super(RegisterView, self).get_form_kwargs(*args, **kwargs)
54-
kwargs['data'] = self.request.data
55-
return kwargs
56-
57-
def post(self, request, *args, **kwargs):
58-
self.initial = {}
59-
form_class = self.get_form_class()
60-
self.form = self.get_form(form_class)
61-
if self.form.is_valid():
62-
self.form_valid(self.form)
63-
return self.get_response()
64-
else:
65-
return self.get_response_with_errors()
6625

67-
def get_response(self):
68-
# serializer = self.user_serializer_class(instance=self.user)
69-
serializer = self.serializer_class(instance=self.token,
70-
context={'request': self.request})
71-
return Response(serializer.data, status=status.HTTP_201_CREATED)
26+
def create(self, request, *args, **kwargs):
27+
serializer = self.get_serializer(data=request.data)
28+
serializer.is_valid(raise_exception=True)
29+
user = self.perform_create(serializer)
30+
headers = self.get_success_headers(serializer.data)
31+
return Response(TokenSerializer(user.auth_token).data,
32+
status=status.HTTP_201_CREATED,
33+
headers=headers)
7234

73-
def get_response_with_errors(self):
74-
return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST)
35+
def perform_create(self, serializer):
36+
user = serializer.save(self.request)
37+
create_token(self.token_model, user, serializer)
38+
complete_signup(self.request._request, user,
39+
allauth_settings.EMAIL_VERIFICATION,
40+
None)
41+
return user
7542

7643

7744
class VerifyEmailView(APIView, ConfirmEmailView):
@@ -80,10 +47,12 @@ class VerifyEmailView(APIView, ConfirmEmailView):
8047
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
8148

8249
def get(self, *args, **kwargs):
83-
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
50+
raise MethodNotAllowed('GET')
8451

8552
def post(self, request, *args, **kwargs):
86-
self.kwargs['key'] = self.request.data.get('key', '')
53+
serializer = VerifyEmailSerializer(data=request.data)
54+
serializer.is_valid(raise_exception=True)
55+
self.kwargs['key'] = serializer.validated_data['key']
8756
confirmation = self.get_object()
8857
confirmation.confirm(self.request)
8958
return Response({'message': 'ok'}, status=status.HTTP_200_OK)

rest_auth/serializers.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,73 @@ class LoginSerializer(serializers.Serializer):
2020
email = serializers.EmailField(required=False, allow_blank=True)
2121
password = serializers.CharField(style={'input_type': 'password'})
2222

23+
def _validate_email(self, email, password):
24+
user = None
25+
26+
if email and password:
27+
user = authenticate(email=email, password=password)
28+
else:
29+
msg = _('Must include "email" and "password".')
30+
raise exceptions.ValidationError(msg)
31+
32+
return user
33+
34+
def _validate_username(self, username, password):
35+
user = None
36+
37+
if username and password:
38+
user = authenticate(username=username, password=password)
39+
else:
40+
msg = _('Must include "username" and "password".')
41+
raise exceptions.ValidationError(msg)
42+
43+
return user
44+
45+
def _validate_username_email(self, username, email, password):
46+
user = None
47+
48+
if email and password:
49+
user = authenticate(email=email, password=password)
50+
elif username and password:
51+
user = authenticate(username=username, password=password)
52+
else:
53+
msg = _('Must include either "username" or "email" and "password".')
54+
raise exceptions.ValidationError(msg)
55+
56+
return user
57+
2358
def validate(self, attrs):
2459
username = attrs.get('username')
2560
email = attrs.get('email')
2661
password = attrs.get('password')
2762

63+
user = None
64+
2865
if 'allauth' in settings.INSTALLED_APPS:
2966
from allauth.account import app_settings
67+
3068
# Authentication through email
3169
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
32-
if email and password:
33-
user = authenticate(email=email, password=password)
34-
else:
35-
msg = _('Must include "email" and "password".')
36-
raise exceptions.ValidationError(msg)
70+
user = self._validate_email(email, password)
71+
3772
# Authentication through username
38-
elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
39-
if username and password:
40-
user = authenticate(username=username, password=password)
41-
else:
42-
msg = _('Must include "username" and "password".')
43-
raise exceptions.ValidationError(msg)
73+
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
74+
user = self._validate_username(username, password)
75+
4476
# Authentication through either username or email
4577
else:
46-
if email and password:
47-
user = authenticate(email=email, password=password)
48-
elif username and password:
49-
user = authenticate(username=username, password=password)
50-
else:
51-
msg = _('Must include either "username" or "email" and "password".')
52-
raise exceptions.ValidationError(msg)
53-
54-
elif username and password:
55-
user = authenticate(username=username, password=password)
78+
user = self._validate_username_email(username, email, password)
5679

5780
else:
58-
msg = _('Must include "username" and "password".')
59-
raise exceptions.ValidationError(msg)
81+
# Authentication without using allauth
82+
if email:
83+
try:
84+
username = UserModel.objects.get(email__iexact=email).username
85+
except UserModel.DoesNotExist:
86+
pass
87+
88+
if username:
89+
user = self._validate_username_email(username, '', password)
6090

6191
# Did we get back an active user?
6292
if user:

0 commit comments

Comments
 (0)