From 445c211ebe38f8797434304275ee1bf30d274cc0 Mon Sep 17 00:00:00 2001 From: Mateusz Sikora Date: Tue, 8 Jul 2014 11:36:59 +0200 Subject: [PATCH] make UserProfile fully optional; version 0.2.3 --- rest_auth/runtests.py | 12 +-- rest_auth/serializers.py | 81 ++++++++++-------- rest_auth/tests.py | 179 +++++++++++++++++++++++++++++---------- rest_auth/views.py | 82 ++++++++++-------- setup.py | 2 +- 5 files changed, 231 insertions(+), 125 deletions(-) diff --git a/rest_auth/runtests.py b/rest_auth/runtests.py index dc9b53d..01a3797 100644 --- a/rest_auth/runtests.py +++ b/rest_auth/runtests.py @@ -26,8 +26,6 @@ class UserProfile(models.Model): class Meta: app_label = 'rest_auth' -settings.REST_PROFILE_MODULE = UserProfile - """ overwrite register to avoid sending email @@ -46,15 +44,13 @@ class RegistrationView(BaseRegistrationView): request=request) # create user profile - user_profile_model = _resolve_model( - getattr(settings, 'REST_PROFILE_MODULE', None)) - user_profile_model.objects.create(user=new_user) + 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 -settings.REST_REGISTRATION_BACKEND = 'rest_auth.runtests.RegistrationView' - - def runtests(): TestRunner = get_runner(settings) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 1d820d3..b9a9c92 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -5,7 +5,8 @@ from rest_framework import serializers from rest_framework.serializers import _resolve_model from rest_framework.authtoken.models import Token -profile_model_path = getattr(settings, 'REST_PROFILE_MODULE', None) + +profile_model_path = lambda: getattr(settings, 'REST_PROFILE_MODULE', None) class LoginSerializer(serializers.Serializer): username = serializers.CharField(max_length=30) @@ -74,49 +75,63 @@ class UserUpdateSerializer(DynamicFieldsModelSerializer): -if profile_model_path: - class UserRegistrationProfileSerializer(serializers.ModelSerializer): +def get_user_registration_profile_serializer(): + if profile_model_path(): + class UserRegistrationProfileSerializer(serializers.ModelSerializer): - """ - Serializer that includes all profile fields except for user fk / id. - """ - class Meta: + """ + Serializer that includes all profile fields except for user fk / id. + """ + class Meta: - model = _resolve_model(profile_model_path) - fields = filter(lambda x: x != 'id' and x != 'user', - map(lambda x: x.name, model._meta.fields)) + model = _resolve_model(profile_model_path()) + fields = filter(lambda x: x != 'id' and x != 'user', + map(lambda x: x.name, model._meta.fields)) + else: + class UserRegistrationProfileSerializer(serializers.Serializer): + pass + return UserRegistrationProfileSerializer - class UserProfileSerializer(serializers.ModelSerializer): - """ - Serializer for UserProfile model. - """ +def get_user_profile_serializer(): + if profile_model_path(): + class UserProfileSerializer(serializers.ModelSerializer): - user = UserDetailsSerializer() + """ + Serializer for UserProfile model. + """ - class Meta: - # http://stackoverflow.com/questions/4881607/django-get-model-from-string - model = _resolve_model(profile_model_path) + user = UserDetailsSerializer() - class UserProfileUpdateSerializer(serializers.ModelSerializer): + class Meta: + # http://stackoverflow.com/questions/4881607/django-get-model-from-string + model = _resolve_model(profile_model_path()) - """ - Serializer for updating User and UserProfile model. - """ + def __init__(self, *args, **kwargs): + super(UserProfileSerializer, self).__init__(*args, **kwargs) + else: + class UserProfileSerializer(serializers.Serializer): + pass + return UserProfileSerializer - user = UserUpdateSerializer() - class Meta: - # http://stackoverflow.com/questions/4881607/django-get-model-from-string - model = _resolve_model(profile_model_path) +def get_user_profile_update_serializer(): + if profile_model_path(): + class UserProfileUpdateSerializer(serializers.ModelSerializer): -else: - class UserRegistrationProfileSerializer(serializers.Serializer): - pass - class UserProfileSerializer(serializers.Serializer): - pass - class UserProfileUpdateSerializer(serializers.Serializer): - pass + """ + Serializer for updating User and UserProfile model. + """ + + user = UserUpdateSerializer() + + class Meta: + # http://stackoverflow.com/questions/4881607/django-get-model-from-string + model = _resolve_model(profile_model_path()) + else: + class UserProfileUpdateSerializer(serializers.Serializer): + pass + return UserProfileUpdateSerializer class SetPasswordSerializer(serializers.Serializer): diff --git a/rest_auth/tests.py b/rest_auth/tests.py index 39aa2b1..2ad4945 100644 --- a/rest_auth/tests.py +++ b/rest_auth/tests.py @@ -112,19 +112,40 @@ class BaseAPITestCase(object): # T E S T H E R E # ----------------------- -user_profile_model = _resolve_model( - getattr(settings, 'REST_PROFILE_MODULE', None)) -class LoginAPITestCase(TestCase, BaseAPITestCase): +class APITestCase1(TestCase, BaseAPITestCase): """ - just run: python manage.py test rest_auth + Case #1: + - user profile: defined + - custom registration: backend defined """ USERNAME = 'person' PASS = 'person' EMAIL = "person1@world.com" NEW_PASS = 'new-test-pass' + PROFILE_MODEL = 'rest_auth.UserProfile' + REGISTRATION_VIEW = 'rest_auth.runtests.RegistrationView' + + # data without user profile + BASIC_REGISTRATION_DATA = { + "username": USERNAME, + "password": PASS, + "email": EMAIL + } + + # data with user profile + REGISTRATION_DATA = BASIC_REGISTRATION_DATA.copy() + REGISTRATION_DATA['newsletter_subscribe'] = False + + BASIC_USER_DATA = { + 'first_name': "John", + 'last_name': 'Smith', + 'email': EMAIL + } + USER_DATA = BASIC_USER_DATA.copy() + USER_DATA['newsletter_subscribe'] = True def setUp(self): self.init() @@ -132,6 +153,17 @@ class LoginAPITestCase(TestCase, BaseAPITestCase): self.password_change_url = reverse('rest_password_change') self.register_url = reverse('rest_register') self.password_reset_url = reverse('rest_password_reset') + self.user_url = reverse('rest_user_details') + + setattr(settings, 'REST_PROFILE_MODULE', self.PROFILE_MODEL) + self.user_profile_model = None + if self.PROFILE_MODEL: + self.user_profile_model = _resolve_model(self.PROFILE_MODEL) + + if self.REGISTRATION_VIEW: + setattr(settings, 'REST_REGISTRATION_BACKEND', self.REGISTRATION_VIEW) + elif hasattr(settings, 'REST_REGISTRATION_BACKEND'): + delattr(settings, 'REST_REGISTRATION_BACKEND') def test_login(self): payload = { @@ -202,59 +234,60 @@ class LoginAPITestCase(TestCase, BaseAPITestCase): # send empty payload self.post(self.password_change_url, data={}, status_code=400) - def test_registration_user_with_profile(self): - payload = { - "username": self.USERNAME, - "password": self.PASS, - "email": "person@world.com", - "newsletter_subscribe": "false" - } + 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=payload, status_code=201) - - 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.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') - self.assertEqual(new_user.is_active, False) - # let's active new user and check is_active flag - self.get(verify_url) + 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') - self.assertEqual(new_user.is_active, True) - user_profile = user_profile_model.objects.get(user=new_user) - self.assertIsNotNone(user_profile) - def test_registration_user_without_profile(self): + if self.REGISTRATION_VIEW: + activation_key = RegistrationProfile.objects.latest('id').activation_key + verify_url = reverse('verify_email', + kwargs={'activation_key': activation_key}) - payload = { - "username": self.USERNAME, - "password": self.PASS, - "email": self.EMAIL, - } + # new user at this point shouldn't be active + self.assertEqual(new_user.is_active, False) - self.post(self.register_url, data=payload, status_code=201) + # 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) - 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 - new_user = get_user_model().objects.latest('id') - 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) - - user_profile = user_profile_model.objects.get(user=new_user) - self.assertIsNotNone(user_profile) def test_password_reset(self): user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS) @@ -280,6 +313,33 @@ class LoginAPITestCase(TestCase, BaseAPITestCase): } self.post(self.login_url, data=payload, status_code=200) + def test_user_details(self): + user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS) + if self.user_profile_model: + self.user_profile_model.objects.create(user=user) + payload = { + "username": self.USERNAME, + "password": self.PASS + } + self.post(self.login_url, data=payload, status_code=200) + self.token = self.response.json['key'] + self.get(self.user_url, status_code=200) + + self.post(self.user_url, data=self.BASIC_USER_DATA, status_code=200) + user = User.objects.get(pk=user.pk) + + if self.user_profile_model: + self.post(self.user_url, data=self.USER_DATA, status_code=200) + user = User.objects.get(pk=user.pk) + self.assertEqual(user.first_name, self.response.json['user']['first_name']) + self.assertEqual(user.last_name, self.response.json['user']['last_name']) + self.assertEqual(user.email, self.response.json['user']['email']) + self.assertIn('newsletter_subscribe', self.response.json) + else: + self.assertEqual(user.first_name, self.response.json['first_name']) + self.assertEqual(user.last_name, self.response.json['last_name']) + self.assertEqual(user.email, self.response.json['email']) + def generate_uid_and_token(self, user): result = {} @@ -295,3 +355,30 @@ class LoginAPITestCase(TestCase, BaseAPITestCase): result['token'] = default_token_generator.make_token(user) return result + +class APITestCase2(APITestCase1): + """ + Case #2: + - user profile: not defined + - custom registration backend: not defined + """ + PROFILE_MODEL = None + + +class APITestCase3(APITestCase1): + """ + Case #3: + - 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 diff --git a/rest_auth/views.py b/rest_auth/views.py index e773add..0241d0a 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -23,31 +23,31 @@ from registration.models import RegistrationProfile from registration import signals from registration.views import ActivationView -from .utils import construct_modules_and_import -from .models import * -from .serializers import TokenSerializer, UserDetailsSerializer, \ - LoginSerializer, UserRegistrationSerializer, \ - UserRegistrationProfileSerializer, \ - UserProfileUpdateSerializer, SetPasswordSerializer, \ - PasswordResetSerializer +from rest_auth.utils import construct_modules_and_import +from rest_auth.models import * +from rest_auth.serializers import (TokenSerializer, UserDetailsSerializer, + LoginSerializer, UserRegistrationSerializer, + SetPasswordSerializer, PasswordResetSerializer, UserUpdateSerializer, + get_user_registration_profile_serializer, get_user_profile_serializer, + get_user_profile_update_serializer) -# Get the UserProfile model from the setting value -user_profile_path = getattr(settings, 'REST_PROFILE_MODULE', None) -user_profile_model = None -if user_profile_path: - user_profile_model = _resolve_model(user_profile_path) +def get_user_profile_model(): + # Get the UserProfile model from the setting value + user_profile_path = getattr(settings, 'REST_PROFILE_MODULE', None) + if user_profile_path: + setattr(settings, 'AUTH_PROFILE_MODULE', user_profile_path) + return _resolve_model(user_profile_path) -# Get the REST Registration Backend for django-registration -registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND', - 'registration.backends.default.views.RegistrationView') -if not registration_backend: - raise Exception('Please configure a registration backend') +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 -RESTRegistrationView = construct_modules_and_import(registration_backend) + # Get the REST REGISTRATION BACKEND class from the setting value via above + # method + return construct_modules_and_import(registration_backend) class LoggedInRESTAPIView(APIView): @@ -139,13 +139,15 @@ class Register(LoggedOutRESTAPIView, GenericAPIView): """ serializer_class = UserRegistrationSerializer - profile_serializer_class = UserRegistrationProfileSerializer + + 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 = self.profile_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 @@ -158,6 +160,7 @@ class Register(LoggedOutRESTAPIView, GenericAPIView): 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 @@ -182,28 +185,33 @@ class UserDetails(LoggedInRESTAPIView, GenericAPIView): Optional: email, first_name, last_name and UserProfile fields Returns the updated UserProfile and/or User object. """ + def get_profile_serializer_class(self): + return get_user_profile_serializer() - serializer_class = UserProfileUpdateSerializer + def get_profile_update_serializer_class(self): + return get_user_profile_update_serializer() def get(self, request): # Create serializers with request.user and profile - user_details = UserDetailsSerializer(request.user) - try: - serializer = self.serializer_class(request.user.get_profile()) - profile_data = serializer.data - except ObjectDoesNotExist: - - profile_data = {} + user_profile_model = get_user_profile_model() + if user_profile_model: + profile_serializer_class = self.get_profile_serializer_class() + serializer = profile_serializer_class(request.user.get_profile()) + else: + serializer = UserDetailsSerializer(request.user) # Send the Return the User and its profile model with OK HTTP status - return Response({ - 'user': user_details.data, - 'profile': profile_data}, - status=status.HTTP_200_OK) + return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request): # Get the User object updater via this Serializer - serializer = self.serializer_class( - request.user.get_profile(), data=request.DATA, partial=True) + user_profile_model = get_user_profile_model() + if user_profile_model: + profile_serializer_class = self.get_profile_update_serializer_class() + serializer = profile_serializer_class(request.user.get_profile(), + data=request.DATA, partial=True) + else: + serializer = UserUpdateSerializer(request.user, data=request.DATA, + partial=True) if serializer.is_valid(): # Save UserProfileUpdateSerializer diff --git a/setup.py b/setup.py index cbd387f..4c12f69 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ f.close() setup( name='django-rest-auth', - version='0.2.2', + version='0.2.3', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/Tivix/django-rest-auth',