django-registration replacement

- remove django-registration references
- integrate with django-allauth
- move all registration stuff to separated app
- update unit tests
This commit is contained in:
Mateusz Sikora 2014-10-01 14:13:21 +02:00
parent 640ce1b970
commit f14b3b03f7
11 changed files with 103 additions and 257 deletions

View File

View File

@ -0,0 +1,17 @@
from rest_framework import serializers
from django.contrib.auth import get_user_model
class UserRegistrationSerializer(serializers.ModelSerializer):
"""
Serializer for Django User model and most of its fields.
"""
class Meta:
model = get_user_model()
fields = ('username', 'password', 'email', 'first_name', 'last_name')
class VerifyEmailSerializer(serializers.Serializer):
pass

View File

@ -0,0 +1,10 @@
from django.conf.urls import patterns, url
from .views import Register, VerifyEmail
urlpatterns = patterns('',
url(r'^$', Register.as_view(), name='rest_register'),
url(r'^verify-email/(?P<activation_key>\w+)/$', VerifyEmail.as_view(),
name='verify_email'),
)

View File

@ -0,0 +1,44 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import status
from allauth.account.views import SignupView
from allauth.account.utils import complete_signup
from allauth.account import app_settings
from rest_auth.serializers import UserDetailsSerializer
class Register(APIView, SignupView):
permission_classes = (AllowAny,)
user_serializer_class = UserDetailsSerializer
def form_valid(self, form):
self.user = form.save(self.request)
return complete_signup(self.request, self.user,
app_settings.EMAIL_VERIFICATION,
self.get_success_url())
def post(self, request, *args, **kwargs):
self.initial = {}
self.request.POST = self.request.DATA.copy()
form_class = self.get_form_class()
self.form = self.get_form(form_class)
if self.form.is_valid():
self.form_valid(self.form)
return self.get_response()
else:
return self.get_response_with_errors()
def get_response(self):
serializer = self.user_serializer_class(instance=self.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def get_response_with_errors(self):
return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST)
class VerifyEmail(APIView):
pass

View File

@ -1,55 +1,13 @@
#This file mainly exists to allow python setup.py test to work. #This file mainly exists to allow python setup.py test to work.
import os, sys import os
import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings'
test_dir = os.path.dirname(__file__) test_dir = os.path.dirname(__file__)
sys.path.insert(0, test_dir) sys.path.insert(0, test_dir)
from django.test.utils import get_runner from django.test.utils import get_runner
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import RequestSite
from django.contrib.sites.models import Site
from django.db import models
from rest_framework.serializers import _resolve_model
from registration.models import RegistrationProfile
from registration.backends.default.views import RegistrationView as BaseRegistrationView
from registration import signals
"""
create user profile model
"""
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
newsletter_subscribe = models.BooleanField(default=False)
class Meta:
app_label = 'rest_auth'
"""
overwrite register to avoid sending email
"""
class RegistrationView(BaseRegistrationView):
def register(self, request, **cleaned_data):
username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
new_user = RegistrationProfile.objects.create_inactive_user(username, email,
password, site, send_email=False)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
# create user profile
profile_model_path = getattr(settings, 'REST_PROFILE_MODULE', None)
if profile_model_path:
user_profile_model = _resolve_model(profile_model_path)
user_profile_model.objects.create(user=new_user)
return new_user
def runtests(): def runtests():

View File

@ -34,17 +34,6 @@ class UserDetailsSerializer(serializers.ModelSerializer):
fields = ('username', 'email', 'first_name', 'last_name') fields = ('username', 'email', 'first_name', 'last_name')
class UserRegistrationSerializer(serializers.ModelSerializer):
"""
Serializer for Django User model and most of its fields.
"""
class Meta:
model = get_user_model()
fields = ('username', 'password', 'email', 'first_name', 'last_name')
class DynamicFieldsModelSerializer(serializers.ModelSerializer): class DynamicFieldsModelSerializer(serializers.ModelSerializer):
""" """

View File

@ -1,5 +1,6 @@
import django import django
import os, sys import os
import sys
PROJECT_ROOT = os.path.abspath(os.path.split(os.path.split(__file__)[0])[0]) PROJECT_ROOT = os.path.abspath(os.path.split(os.path.split(__file__)[0])[0])
ROOT_URLCONF = 'urls' ROOT_URLCONF = 'urls'
@ -37,11 +38,14 @@ INSTALLED_APPS = [
'django.contrib.sitemaps', 'django.contrib.sitemaps',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'allauth',
'allauth.account',
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'registration',
'rest_auth', 'rest_auth',
'rest_auth.registration'
] ]
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd" SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"

View File

@ -9,7 +9,6 @@ from django.contrib.auth.models import User
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core import mail from django.core import mail
from registration.models import RegistrationProfile
from rest_framework.serializers import _resolve_model from rest_framework.serializers import _resolve_model
@ -113,7 +112,6 @@ class BaseAPITestCase(object):
# ----------------------- # -----------------------
class APITestCase1(TestCase, BaseAPITestCase): class APITestCase1(TestCase, BaseAPITestCase):
""" """
Case #1: Case #1:
@ -129,16 +127,12 @@ class APITestCase1(TestCase, BaseAPITestCase):
REGISTRATION_VIEW = 'rest_auth.runtests.RegistrationView' REGISTRATION_VIEW = 'rest_auth.runtests.RegistrationView'
# data without user profile # data without user profile
BASIC_REGISTRATION_DATA = { REGISTRATION_DATA = {
"username": USERNAME, "username": USERNAME,
"password": PASS, "password1": PASS,
"email": EMAIL "password2": PASS
} }
# data with user profile
REGISTRATION_DATA = BASIC_REGISTRATION_DATA.copy()
REGISTRATION_DATA['newsletter_subscribe'] = False
BASIC_USER_DATA = { BASIC_USER_DATA = {
'first_name': "John", 'first_name': "John",
'last_name': 'Smith', 'last_name': 'Smith',
@ -191,7 +185,7 @@ class APITestCase1(TestCase, BaseAPITestCase):
# test wrong username/password # test wrong username/password
payload = { payload = {
"username": self.USERNAME+'?', "username": self.USERNAME + '?',
"password": self.PASS "password": self.PASS
} }
self.post(self.login_url, data=payload, status_code=401) self.post(self.login_url, data=payload, status_code=401)
@ -199,13 +193,12 @@ class APITestCase1(TestCase, BaseAPITestCase):
# test empty payload # test empty payload
self.post(self.login_url, data={}, status_code=400) self.post(self.login_url, data={}, status_code=400)
def test_password_change(self): def test_password_change(self):
login_payload = { login_payload = {
"username": self.USERNAME, "username": self.USERNAME,
"password": self.PASS "password": self.PASS
} }
user = User.objects.create_user(self.USERNAME, '', self.PASS) User.objects.create_user(self.USERNAME, '', self.PASS)
self.post(self.login_url, data=login_payload, status_code=200) self.post(self.login_url, data=login_payload, status_code=200)
self.token = self.response.json['key'] self.token = self.response.json['key']
@ -234,61 +227,6 @@ class APITestCase1(TestCase, BaseAPITestCase):
# send empty payload # send empty payload
self.post(self.password_change_url, data={}, status_code=400) self.post(self.password_change_url, data={}, status_code=400)
def test_registration(self):
user_count = User.objects.all().count()
# test empty payload
self.post(self.register_url, data={}, status_code=400)
self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
self.assertEqual(User.objects.all().count(), user_count+1)
new_user = get_user_model().objects.latest('id')
if self.REGISTRATION_VIEW:
activation_key = RegistrationProfile.objects.latest('id').activation_key
verify_url = reverse('verify_email',
kwargs={'activation_key': activation_key})
# new user at this point shouldn't be active
self.assertEqual(new_user.is_active, False)
# let's active new user and check is_active flag
self.get(verify_url)
new_user = get_user_model().objects.latest('id')
self.assertEqual(new_user.is_active, True)
if self.user_profile_model:
user_profile = self.user_profile_model.objects.get(user=new_user)
self.assertIsNotNone(user_profile)
else:
self.assertEqual(new_user.is_active, True)
def test_registration_without_profile_data(self):
user_count = User.objects.all().count()
self.post(self.register_url, data=self.BASIC_REGISTRATION_DATA,
status_code=201)
self.assertEqual(User.objects.all().count(), user_count+1)
new_user = get_user_model().objects.latest('id')
if self.REGISTRATION_VIEW:
activation_key = RegistrationProfile.objects.latest('id').activation_key
verify_url = reverse('verify_email',
kwargs={'activation_key': activation_key})
# new user at this point shouldn't be active
self.assertEqual(new_user.is_active, False)
# let's active new user and check is_active flag
self.get(verify_url)
new_user = get_user_model().objects.latest('id')
self.assertEqual(new_user.is_active, True)
if self.user_profile_model:
user_profile = self.user_profile_model.objects.get(user=new_user)
self.assertIsNotNone(user_profile)
else:
self.assertEqual(new_user.is_active, True)
def test_password_reset(self): def test_password_reset(self):
user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS) user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
@ -296,7 +234,7 @@ class APITestCase1(TestCase, BaseAPITestCase):
mail_count = len(mail.outbox) mail_count = len(mail.outbox)
payload = {'email': self.EMAIL} payload = {'email': self.EMAIL}
self.post(self.password_reset_url, data=payload) self.post(self.password_reset_url, data=payload)
self.assertEqual(len(mail.outbox), mail_count+1) self.assertEqual(len(mail.outbox), mail_count + 1)
url_kwargs = self.generate_uid_and_token(user) url_kwargs = self.generate_uid_and_token(user)
@ -340,7 +278,6 @@ class APITestCase1(TestCase, BaseAPITestCase):
self.assertEqual(user.last_name, self.response.json['last_name']) self.assertEqual(user.last_name, self.response.json['last_name'])
self.assertEqual(user.email, self.response.json['email']) self.assertEqual(user.email, self.response.json['email'])
def generate_uid_and_token(self, user): def generate_uid_and_token(self, user):
result = {} result = {}
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
@ -355,30 +292,13 @@ class APITestCase1(TestCase, BaseAPITestCase):
result['token'] = default_token_generator.make_token(user) result['token'] = default_token_generator.make_token(user)
return result return result
def test_registration(self):
user_count = User.objects.all().count()
class APITestCase2(APITestCase1): # test empty payload
""" self.post(self.register_url, data={}, status_code=400)
Case #2:
- user profile: not defined
- custom registration backend: not defined
"""
PROFILE_MODEL = None
self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
class APITestCase3(APITestCase1): self.assertEqual(User.objects.all().count(), user_count + 1)
""" new_user = get_user_model().objects.latest('id')
Case #3: self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
- user profile: defined
- custom registration backend: not defined
"""
REGISTRATION_VIEW = None
class APITestCase4(APITestCase1):
"""
Case #4:
- user profile: not defined
- custom registration backend: not defined
"""
PROFILE_MODEL = None
REGISTRATION_VIEW = None

View File

@ -1,22 +1,18 @@
from django.conf import settings from django.conf import settings
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
from rest_auth.views import Login, Logout, Register, UserDetails, \ from rest_auth.views import Login, Logout, UserDetails, \
PasswordChange, PasswordReset, VerifyEmail, PasswordResetConfirm PasswordChange, PasswordReset, PasswordResetConfirm
urlpatterns = patterns('rest_auth.views', urlpatterns = patterns('rest_auth.views',
# URLs that do not require a session or valid token # URLs that do not require a session or valid token
url(r'^register/$', Register.as_view(),
name='rest_register'),
url(r'^password/reset/$', PasswordReset.as_view(), url(r'^password/reset/$', PasswordReset.as_view(),
name='rest_password_reset'), name='rest_password_reset'),
url(r'^password/reset/confirm/(?P<uid>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', url(r'^password/reset/confirm/(?P<uid>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
PasswordResetConfirm.as_view( PasswordResetConfirm.as_view(
), name='rest_password_reset_confirm'), ), name='rest_password_reset_confirm'),
url(r'^login/$', Login.as_view(), name='rest_login'), url(r'^login/$', Login.as_view(), name='rest_login'),
url(r'^verify-email/(?P<activation_key>\w+)/$',
VerifyEmail.as_view(), name='verify_email'),
# URLs that require a user to be logged in with a valid # URLs that require a user to be logged in with a valid
# session / token. # session / token.
@ -29,4 +25,7 @@ urlpatterns = patterns('rest_auth.views',
if getattr(settings, 'IS_TEST', False): if getattr(settings, 'IS_TEST', False):
from django.contrib.auth.tests import urls from django.contrib.auth.tests import urls
urlpatterns += patterns('', url(r'^test-admin/', include(urls))) urlpatterns += patterns('',
url(r'^rest-registration/', include('registration.urls')),
url(r'^test-admin/', include(urls))
)

View File

@ -7,7 +7,6 @@ except:
# make compatible with django 1.5 # make compatible with django 1.5
from django.utils.http import base36_to_int as uid_decoder from django.utils.http import base36_to_int as uid_decoder
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status from rest_framework import status
from rest_framework.views import APIView from rest_framework.views import APIView
@ -19,17 +18,11 @@ from rest_framework.authentication import SessionAuthentication, \
TokenAuthentication TokenAuthentication
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from registration.models import RegistrationProfile
from registration import signals
from registration.views import ActivationView
from rest_auth.utils import construct_modules_and_import from rest_auth.utils import construct_modules_and_import
from rest_auth.models import *
from rest_auth.serializers import (TokenSerializer, UserDetailsSerializer, from rest_auth.serializers import (TokenSerializer, UserDetailsSerializer,
LoginSerializer, UserRegistrationSerializer, LoginSerializer,
SetPasswordSerializer, PasswordResetSerializer, UserUpdateSerializer, SetPasswordSerializer, PasswordResetSerializer, UserUpdateSerializer,
get_user_registration_profile_serializer, get_user_profile_serializer, get_user_profile_serializer, get_user_profile_update_serializer)
get_user_profile_update_serializer)
def get_user_profile_model(): def get_user_profile_model():
@ -40,16 +33,6 @@ def get_user_profile_model():
return _resolve_model(user_profile_path) return _resolve_model(user_profile_path)
def get_registration_backend():
# Get the REST Registration Backend for django-registration
registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND',
'registration.backends.simple.views.RegistrationView')
# Get the REST REGISTRATION BACKEND class from the setting value via above
# method
return construct_modules_and_import(registration_backend)
class LoggedInRESTAPIView(APIView): class LoggedInRESTAPIView(APIView):
authentication_classes = ((SessionAuthentication, TokenAuthentication)) authentication_classes = ((SessionAuthentication, TokenAuthentication))
permission_classes = ((IsAuthenticated,)) permission_classes = ((IsAuthenticated,))
@ -125,53 +108,6 @@ class Logout(LoggedInRESTAPIView):
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
class Register(LoggedOutRESTAPIView, GenericAPIView):
"""
Registers a new Django User object by accepting required field values.
Accepts the following POST parameters:
Required: username, password, email
Optional: first_name & last_name for User object and UserProfile fields
Returns the newly created User object including REST Framework Token key.
"""
serializer_class = UserRegistrationSerializer
def get_profile_serializer_class(self):
return get_user_registration_profile_serializer()
def post(self, request):
# Create serializers with request.DATA
serializer = self.serializer_class(data=request.DATA)
profile_serializer_class = self.get_profile_serializer_class()
profile_serializer = profile_serializer_class(data=request.DATA)
if serializer.is_valid() and profile_serializer.is_valid():
# Change the password key to password1 so that RESTRegistrationView
# can accept the data
serializer.data['password1'] = serializer.data.pop('password')
# TODO: Make this customizable backend via settings.
# Call RESTRegistrationView().register to create new Django User
# and UserProfile models
data = serializer.data.copy()
data.update(profile_serializer.data)
RESTRegistrationView = get_registration_backend()
RESTRegistrationView().register(request, **data)
# Return the User object with Created HTTP status
return Response(UserDetailsSerializer(serializer.data).data,
status=status.HTTP_201_CREATED)
else:
return Response({
'user': serializer.errors,
'profile': profile_serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
class UserDetails(LoggedInRESTAPIView, GenericAPIView): class UserDetails(LoggedInRESTAPIView, GenericAPIView):
""" """
@ -328,37 +264,6 @@ class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
return Response({"errors": "Couldn\'t find the user from uid."}, status=status.HTTP_400_BAD_REQUEST) return Response({"errors": "Couldn\'t find the user from uid."}, status=status.HTTP_400_BAD_REQUEST)
class VerifyEmail(LoggedOutRESTAPIView, GenericAPIView):
"""
Verifies the email of the user through their activation_key.
Accepts activation_key django argument: key from activation email.
Returns the success/fail message.
"""
model = RegistrationProfile
def get(self, request, activation_key=None):
# Get the user registration profile with the activation key
target_user = RegistrationProfile.objects.activate_user(activation_key)
if target_user:
# Send the activation signal
signals.user_activated.send(sender=ActivationView.__class__,
user=target_user,
request=request)
# Return the success message with OK HTTP status
ret_msg = "User {0}'s account was successfully activated!".format(
target_user.username)
return Response({"success": ret_msg}, status=status.HTTP_200_OK)
else:
ret_msg = "The account was not able to be activated or already activated, please contact support."
return Response({"errors": ret_msg}, status=status.HTTP_400_BAD_REQUEST)
class PasswordChange(LoggedInRESTAPIView, GenericAPIView): class PasswordChange(LoggedInRESTAPIView, GenericAPIView):
""" """

View File

@ -29,7 +29,7 @@ setup(
zip_safe=False, zip_safe=False,
install_requires=[ install_requires=[
'Django>=1.5.0', 'Django>=1.5.0',
'django-registration>=1.0', 'django-allauth>=0.18.0',
'djangorestframework>=2.3.13', 'djangorestframework>=2.3.13',
], ],
test_suite='rest_auth.runtests.runtests', test_suite='rest_auth.runtests.runtests',