From e1c577d4fd0b06aa701889b93b498c54583bc516 Mon Sep 17 00:00:00 2001 From: Sumit Chachra Date: Wed, 30 Apr 2014 12:52:05 -0700 Subject: [PATCH] First commit. --- rest_auth/__init__.py | 0 rest_auth/admin.py | 3 + rest_auth/models.py | 3 + rest_auth/serializers.py | 118 +++++++++++++ rest_auth/tests.org.py | 267 +++++++++++++++++++++++++++++ rest_auth/tests.py | 148 ++++++++++++++++ rest_auth/urls.py | 29 ++++ rest_auth/utils.py | 42 +++++ rest_auth/views.py | 357 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 967 insertions(+) create mode 100644 rest_auth/__init__.py create mode 100644 rest_auth/admin.py create mode 100644 rest_auth/models.py create mode 100644 rest_auth/serializers.py create mode 100644 rest_auth/tests.org.py create mode 100644 rest_auth/tests.py create mode 100644 rest_auth/urls.py create mode 100644 rest_auth/utils.py create mode 100644 rest_auth/views.py diff --git a/rest_auth/__init__.py b/rest_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rest_auth/admin.py b/rest_auth/admin.py new file mode 100644 index 0000000..4185d36 --- /dev/null +++ b/rest_auth/admin.py @@ -0,0 +1,3 @@ +# from django.contrib import admin + +# Register your models here. diff --git a/rest_auth/models.py b/rest_auth/models.py new file mode 100644 index 0000000..e703865 --- /dev/null +++ b/rest_auth/models.py @@ -0,0 +1,3 @@ +# from django.db import models + +# Register your models here. diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py new file mode 100644 index 0000000..7526c41 --- /dev/null +++ b/rest_auth/serializers.py @@ -0,0 +1,118 @@ +from django.contrib.auth import get_user_model +from django.conf import settings + +from rest_framework import serializers +from rest_framework.serializers import _resolve_model +from rest_framework.authtoken.models import Token + + +class LoginSerializer(serializers.Serializer): + username = serializers.CharField(max_length=30) + password = serializers.CharField(max_length=128) + + +class TokenSerializer(serializers.ModelSerializer): + """ + Serializer for Token model. + """ + + class Meta: + model = Token + fields = ('key',) + + +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 UserRegistrationProfileSerializer(serializers.ModelSerializer): + """ + Serializer that includes all profile fields except for user fk / id. + """ + class Meta: + model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None)) + fields = filter(lambda x: x != 'id' and x != 'user', + map(lambda x: x.name, model._meta.fields)) + + +class UserDetailsSerializer(serializers.ModelSerializer): + """ + User model w/o password + """ + class Meta: + model = get_user_model() + fields = ('username', 'email', 'first_name', 'last_name') + + +class UserProfileSerializer(serializers.ModelSerializer): + """ + Serializer for UserProfile model. + """ + + user = UserDetailsSerializer() + + class Meta: + # http://stackoverflow.com/questions/4881607/django-get-model-from-string + model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None)) + + +class DynamicFieldsModelSerializer(serializers.ModelSerializer): + """ + ModelSerializer that allows fields argument to control fields + """ + + def __init__(self, *args, **kwargs): + fields = kwargs.pop('fields', None) + + super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) + + if fields: + allowed = set(fields) + existing = set(self.fields.keys()) + + for field_name in existing - allowed: + self.fields.pop(field_name) + + +class UserUpdateSerializer(DynamicFieldsModelSerializer): + """ + User model w/o username and password + """ + class Meta: + model = get_user_model() + fields = ('id', 'email', 'first_name', 'last_name') + + +class UserProfileUpdateSerializer(serializers.ModelSerializer): + """ + Serializer for updating User and UserProfile model. + """ + + user = UserUpdateSerializer() + + class Meta: + # http://stackoverflow.com/questions/4881607/django-get-model-from-string + model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None)) + + +class SetPasswordSerializer(serializers.Serializer): + """ + Serializer for changing Django User password. + """ + + new_password1 = serializers.CharField(max_length=128) + new_password2 = serializers.CharField(max_length=128) + + +class PasswordResetSerializer(serializers.Serializer): + """ + Serializer for requesting a password reset e-mail. + """ + + email = serializers.EmailField() diff --git a/rest_auth/tests.org.py b/rest_auth/tests.org.py new file mode 100644 index 0000000..da0f6d2 --- /dev/null +++ b/rest_auth/tests.org.py @@ -0,0 +1,267 @@ +import requests +import json + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.conf import settings + +from rest_framework.serializers import _resolve_model + +from registration.models import RegistrationProfile + + +# Get the UserProfile model from the setting value +user_profile_model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None)) + +# Get the REST Registration Backend for django-registration +registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND', 'rest_auth.backends.rest_registration.RESTRegistrationView') + + +class RegistrationAndActivationTestCase(TestCase): + """ + Unit Test for registering and activating a new user + + This test case assumes that the local server runs at port 8000. + """ + + def setUp(self): + self.url = "http://localhost:8000/rest_auth/register/" + self.headers = {"content-type": "application/json"} + + def test_successful_registration(self): + print 'Registering a new user' + payload = {"username": "person", "password": "person", "email": "person@world.com", "newsletter_subscribe": "false"} + + print 'The request will attempt to register:' + print 'Django User object' + print 'Username: %s\nPassword: %s\nEmail: %s\n' % ('person', 'person', 'person@world.com') + print 'Django UserProfile object' + print 'newsletter_subscribe: false' + print 'Sending a POST request to register API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 201): + print r.content + + print 'Activating a new user' + + # Get the latest activation key from RegistrationProfile model + activation_key = RegistrationProfile.objects.latest('id').activation_key + + # Set the url and GET the request to verify and activate a new user + url = "http://localhost:8000/rest_auth/verify-email/" + activation_key + "/" + r = requests.get(url) + + print "Sending a GET request to activate the user from verify-email API" + + if self.assertEqual(r.status_code, 200): + print r.content + + # Get the latest User object + new_user = get_user_model().objects.latest('id') + print "Got the new user %s" % new_user.username + + try: + print "Got the new user profile %s" % (user_profile_model.objects.get(user=new_user)) + except user_profile_model.DoesNotExist: + pass + + def test_successful_registration_without_userprofile_model(self): + print 'Registering a new user' + payload = {"username": "person1", "password": "person1", "email": "person1@world.com"} + + print 'The request will attempt to register:' + print 'Django User object' + print 'Username: %s\nPassword: %s\nEmail: %s\n' % ('person1', 'person1', 'person1@world.com') + print 'No Django UserProfile object' + print 'Sending a POST request to register API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 201): + print r.content + + print 'Activating a new user' + + # Get the latest activation key from RegistrationProfile model + activation_key = RegistrationProfile.objects.latest('id').activation_key + + # Set the url and GET the request to verify and activate a new user + url = "http://localhost:8000/rest_auth/verify-email/" + activation_key + "/" + r = requests.get(url) + + print "Sending a GET request to activate the user from verify-email API" + + if self.assertEqual(r.status_code, 200): + print r.content + + # Get the latest User object + new_user = get_user_model().objects.latest('id') + print "Got the new user %s" % new_user.username + + try: + print "Got the new user profile %s" % (user_profile_model.objects.get(user=new_user)) + except user_profile_model.DoesNotExist: + pass + + def test_required_fields_for_registration(self): + print 'Registering a new user' + payload = {} + + print 'The request will attempt to register with no data provided.' + print 'Sending a POST request to register API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 400): + print r.content + + +class LoginTestCase(TestCase): + """ + Unit Test for logging in + + This test case assumes that the local server runs at port 8000. + """ + + def setUp(self): + self.url = "http://localhost:8000/rest_auth/login/" + self.headers = {"content-type": "application/json"} + + def test_successful_login(self): + print 'Logging in as a new user' + payload = {"username": "person", "password": "person"} + + print 'The request will attempt to login:' + print 'Username: %s\nPassword: %s' % ('person', 'person') + print 'Sending a POST request to login API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + print "Got the REST Token: " + r.json()['key'] + + def test_invalid_login(self): + print 'Logging in as a new user' + payload = {"username": "person", "password": "person32"} + + print 'The request will attempt to login:' + print 'Username: %s\nPassword: %s' % ('person', 'person32') + print 'Sending a POST request to login API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 401): + print r.content + + def test_required_fields_for_login(self): + print 'Logging in as a new user' + payload = {} + + print 'The request will attempt to login with no data provided.' + print 'Sending a POST request to login API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 400): + print r.content + + +class PasswordChangeCase(TestCase): + """ + Unit Test for changing the password while logged in + + This test case assumes that the local server runs at port 8000. + """ + + def setUp(self): + self.url = "http://localhost:8000/rest_auth/password/change/" + self.headers = {"content-type": "application/json"} + + def test_successful_password_change(self): + print 'Logging in' + payload = {"username": "person", "password": "person"} + login_url = "http://localhost:8000/rest_auth/login/" + + print 'Sending a POST request to login API' + + r = requests.post(login_url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + print "Got the REST Token: " + r.json()['key'] + + self.token = r.json()['key'] + self.headers['authorization'] = "Token " + r.json()['key'] + + payload = {"new_password1": "new_person", "new_password2": "new_person"} + print 'Sending a POST request to password change API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + payload = {"new_password1": "person", "new_password2": "person"} + print 'Sending a POST request to password change API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + def test_invalid_password_change(self): + print 'Logging in' + payload = {"username": "person", "password": "person"} + login_url = "http://localhost:8000/rest_auth/login/" + + print 'Sending a POST request to login API' + + r = requests.post(login_url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + print "Got the REST Token: " + r.json()['key'] + + self.token = r.json()['key'] + self.headers['authorization'] = "Token " + r.json()['key'] + + payload = {"new_password1": "new_person", "new_password2": "wrong_person"} + print 'Sending a POST request to password change API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 400): + print r.content + + def test_required_fields_for_password_change(self): + print 'Logging in' + payload = {"username": "person", "password": "person"} + login_url = "http://localhost:8000/rest_auth/login/" + + print 'Sending a POST request to login API' + + r = requests.post(login_url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 200): + print r.content + + print "Got the REST Token: " + r.json()['key'] + + self.token = r.json()['key'] + self.headers['authorization'] = "Token " + r.json()['key'] + + payload = {} + + print 'The request will attempt to login with no data provided.' + print 'Sending a POST request to password change API' + + r = requests.post(self.url, data=json.dumps(payload), headers=self.headers) + + if self.assertEqual(r.status_code, 400): + print r.content diff --git a/rest_auth/tests.py b/rest_auth/tests.py new file mode 100644 index 0000000..cdd71ed --- /dev/null +++ b/rest_auth/tests.py @@ -0,0 +1,148 @@ +import json +import os +from datetime import datetime, date, time +from pprint import pprint + +from django.conf import settings +from django.test.client import Client, MULTIPART_CONTENT +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User + + +class APIClient(Client): + def patch(self, path, data='', content_type=MULTIPART_CONTENT, follow=False, **extra): + return self.generic('PATCH', path, data, content_type, **extra) + + def options(self, path, data='', content_type=MULTIPART_CONTENT, follow=False, **extra): + return self.generic('OPTIONS', path, data, content_type, **extra) + + +class CustomJSONEncoder(json.JSONEncoder): + """ + Convert datetime/date objects into isoformat + """ + + def default(self, obj): + if isinstance(obj, (datetime, date, time)): + return obj.isoformat() + else: + return super(CustomJSONEncoder, self).default(obj) + + +class BaseAPITestCase(object): + """ + base for API tests: + * easy request calls, f.e.: self.post(url, data), self.get(url) + * easy status check, f.e.: self.post(url, data, status_code=200) + """ + + img = os.path.join(settings.STATICFILES_DIRS[0][1], 'images/no_profile_photo.png') + + def send_request(self, request_method, *args, **kwargs): + request_func = getattr(self.client, request_method) + status_code = None + if not 'content_type' in kwargs and request_method != 'get': + kwargs['content_type'] = 'application/json' + if 'data' in kwargs and request_method != 'get' and kwargs['content_type'] == 'application/json': + data = kwargs.get('data', '') + kwargs['data'] = json.dumps(data, cls=CustomJSONEncoder) + if 'status_code' in kwargs: + status_code = kwargs.pop('status_code') + + # check_headers = kwargs.pop('check_headers', True) + if hasattr(self, 'token'): + kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token + + if hasattr(self, 'company_token'): + kwargs['HTTP_AUTHORIZATION'] = 'Company-Token %s' % self.company_token + + self.response = request_func(*args, **kwargs) + is_json = bool(filter(lambda x: 'json' in x, self.response._headers['content-type'])) + if is_json and self.response.content: + self.response.json = json.loads(self.response.content) + else: + self.response.json = {} + if status_code: + self.assertEqual(self.response.status_code, status_code) + return self.response + + def post(self, *args, **kwargs): + return self.send_request('post', *args, **kwargs) + + def get(self, *args, **kwargs): + return self.send_request('get', *args, **kwargs) + + def patch(self, *args, **kwargs): + return self.send_request('patch', *args, **kwargs) + + def put(self, *args, **kwargs): + return self.send_request('put', *args, **kwargs) + + def delete(self, *args, **kwargs): + return self.send_request('delete', *args, **kwargs) + + def options(self, *args, **kwargs): + return self.send_request('options', *args, **kwargs) + + def post_file(self, *args, **kwargs): + kwargs['content_type'] = MULTIPART_CONTENT + return self.send_request('post', *args, **kwargs) + + def get_file(self, *args, **kwargs): + content_type = None + if 'content_type' in kwargs: + content_type = kwargs.pop('content_type') + response = self.send_request('get', *args, **kwargs) + if content_type: + self.assertEqual(bool(filter(lambda x: content_type in x, response._headers['content-type'])), True) + return response + + def init(self): + settings.DEBUG = True + self.client = APIClient() + + + +# ----------------------- +# T E S T H E R E +# ----------------------- + +class LoginAPITestCase(TestCase, BaseAPITestCase): + """ + just run: python manage.py test rest_auth + """ + + USERNAME = 'person' + PASS = 'person' + + def setUp(self): + self.init() + self.login_url = reverse('rest_login') + + def test_login(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + # there is no users in db so it should throw error (401) + self.post(self.login_url, data=payload, status_code=401) + + # you can easily print response + pprint(self.response.json) + + # create user + user = User.objects.create_user(self.USERNAME, '', self.PASS) + + self.post(self.login_url, data=payload, status_code=200) + self.assertEqual('key' in self.response.json.keys(), True) + + + self.token = self.response.json['key'] + # TODO: + # now all urls that required token should be available + # would be perfect to test one of + + + # TODO: + # another case to test - make user inactive and test if login is impossible diff --git a/rest_auth/urls.py b/rest_auth/urls.py new file mode 100644 index 0000000..9bced00 --- /dev/null +++ b/rest_auth/urls.py @@ -0,0 +1,29 @@ +from django.conf import settings +from django.conf.urls import patterns, url, include + +from .views import Login, Logout, Register, UserDetails, \ + PasswordChange, PasswordReset, VerifyEmail, PasswordResetConfirm + + +urlpatterns = patterns('rest_auth.views', + # 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(), name='rest_password_reset'), + url(r'^password/reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', + PasswordResetConfirm.as_view(), name='rest_password_reset_confirm'), + url(r'^login/$', Login.as_view(), name='rest_login'), + url(r'^verify-email/(?P\w+)/$', + VerifyEmail.as_view(), name='verify_email'), + + # URLs that require a user to be logged in with a valid session / token. + url(r'^logout/$', Logout.as_view(), name='rest_logout'), + url(r'^user/$', UserDetails.as_view(), name='rest_user_details'), + url(r'^password/change/$', PasswordChange.as_view(), + name='rest_password_change'), +) + +if settings.DEBUG: + urlpatterns += patterns('', + # Swagger Docs + url(r'^docs/', include('rest_framework_swagger.urls')), + ) diff --git a/rest_auth/utils.py b/rest_auth/utils.py new file mode 100644 index 0000000..f02f853 --- /dev/null +++ b/rest_auth/utils.py @@ -0,0 +1,42 @@ +from django.utils.crypto import get_random_string + + +HASH_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + +def generate_new_hash_with_length(length): + """ + Generates a random string with the alphanumerical character set and given length. + """ + return get_random_string(length, HASH_CHARACTERS) + + +# Based on http://stackoverflow.com/a/547867. Thanks! Credit goes to you! +def construct_modules_and_import(name): + """ + Grab the Python string to import + """ + + # Get all the components by dot notations + components = name.split('.') + module = '' + i = 1 + + # Construct the partial Python string except the last package name + for comp in components: + if i < len(components): + module += str(comp) + + if i < (len(components) - 1): + module += '.' + + i += 1 + + # Import the module from above python string + mod = __import__(module) + + # Import the component recursivcely + for comp in components[1:]: + mod = getattr(mod, comp) + + # Return the imported module's class + return mod diff --git a/rest_auth/views.py b/rest_auth/views.py new file mode 100644 index 0000000..bc6acd7 --- /dev/null +++ b/rest_auth/views.py @@ -0,0 +1,357 @@ +from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm +from django.contrib.auth import authenticate, login, logout, get_user_model +from django.contrib.auth.tokens import default_token_generator +from django.utils.http import urlsafe_base64_decode +from django.conf import settings + +from rest_framework import status +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.generics import GenericAPIView +from rest_framework.serializers import _resolve_model +from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.authentication import SessionAuthentication, \ + TokenAuthentication +from rest_framework.authtoken.models import Token + +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 + + +# Get the UserProfile model from the setting value +user_profile_model = _resolve_model( + getattr(settings, 'REST_PROFILE_MODULE', None)) + +# Get the REST Registration Backend for django-registration +registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND', None) + +if not registration_backend: + raise Exception('Please configure a registration backend') + +# Get the REST REGISTRATION BACKEND class from the setting value via above +# method +RESTRegistrationView = construct_modules_and_import(registration_backend) + + +class LoggedInRESTAPIView(APIView): + authentication_classes = ((SessionAuthentication, TokenAuthentication)) + permission_classes = ((IsAuthenticated,)) + + +class LoggedOutRESTAPIView(APIView): + permission_classes = ((AllowAny,)) + + +class Login(LoggedOutRESTAPIView, GenericAPIView): + + """ + Check the credentials and return the REST Token + if the credentials are valid and authenticated. + Calls Django Auth login method to register User ID + in Django session framework + + Accept the following POST parameters: username, password + Return the REST Framework Token Object's key. + """ + + serializer_class = LoginSerializer + + def post(self, request): + # Create a serializer with request.DATA + serializer = self.serializer_class(data=request.DATA) + + if serializer.is_valid(): + # Authenticate the credentials by grabbing Django User object + user = authenticate(username=serializer.data['username'], + password=serializer.data['password']) + + if user and user.is_authenticated(): + if user.is_active: + # TODO: be able to configure this to either be + # session or token or both + # right now it's both. + login(request, user) + + # Return REST Token object with OK HTTP status + token, created = Token.objects.get_or_create(user=user) + return Response(TokenSerializer(token).data, + status=status.HTTP_200_OK) + else: + return Response({'error': 'This account is disabled.'}, + status=status.HTTP_401_UNAUTHORIZED) + else: + return Response({'error': 'Invalid Username/Password.'}, + status=status.HTTP_401_UNAUTHORIZED) + else: + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + + +class Logout(LoggedInRESTAPIView): + + """ + Calls Django logout method and delete the Token object + assigned to the current User object. + + Accepts/Returns nothing. + """ + + def get(self, request): + try: + request.user.auth_token.delete() + except: + pass + + logout(request) + + return Response({"success": "Successfully logged out."}, + 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 post(self, request): + # Create serializers with request.DATA + serializer = self.serializer_class(data=request.DATA) + profile_serializer = UserRegistrationProfileSerializer( + 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 + RESTRegistrationView().register(request, **serializer.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): + + """ + Returns User's details in JSON format. + + Accepts the following GET parameters: token + Accepts the following POST parameters: + Required: token + Optional: email, first_name, last_name and UserProfile fields + Returns the updated UserProfile and/or User object. + """ + + serializer_class = UserProfileUpdateSerializer + + def get(self, request): + # Create serializers with request.user and profile + user_details = UserDetailsSerializer(request.user) + serializer = self.serializer_class(request.user.get_profile()) + + # Send the Return the User and its profile model with OK HTTP status + return Response({ + 'user': user_details.data, + 'profile': 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) + + if serializer.is_valid(): + # Save UserProfileUpdateSerializer + serializer.save() + + # Return the User object with OK HTTP status + return Response(serializer.data, status=status.HTTP_200_OK) + + else: + # Return the UserProfileUpdateSerializer errors with Bad Request + # HTTP status + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class PasswordReset(LoggedOutRESTAPIView, GenericAPIView): + + """ + Calls Django Auth PasswordResetForm save method. + + Accepts the following POST parameters: email + Returns the success/fail message. + """ + + serializer_class = PasswordResetSerializer + + def post(self, request): + # Create a serializer with request.DATA + serializer = self.serializer_class(data=request.DATA) + + if serializer.is_valid(): + # Create PasswordResetForm with the serializer + reset_form = PasswordResetForm(data=serializer.data) + + if reset_form.is_valid(): + # Sett some values to trigger the send_email method. + opts = { + 'use_https': request.is_secure(), + 'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'), + 'request': request, + } + + reset_form.save(**opts) + + # Return the success message with OK HTTP status + return Response({"success": "Password reset e-mail has been sent."}, + status=status.HTTP_200_OK) + + else: + return Response(reset_form._errors, + status=status.HTTP_400_BAD_REQUEST) + + else: + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + +class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView): + """ + Password reset e-mail link is confirmed, therefore this resets the user's password. + + Accepts the following POST parameters: new_password1, new_password2 + Accepts the following Django URL arguments: token, uidb64 + Returns the success/fail message. + """ + + serializer_class = SetPasswordSerializer + + def post(self, request, uidb64=None, token=None): + # Get the UserModel + UserModel = get_user_model() + + # Decode the uidb64 to uid to get User object + try: + uid = urlsafe_base64_decode(uidb64) + user = UserModel._default_manager.get(pk=uid) + except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): + user = None + + # If we get the User object + if user: + serializer = self.serializer_class(data=request.DATA) + + if serializer.is_valid(): + # Construct SetPasswordForm instance + form = SetPasswordForm(user=user, data=serializer.data) + + if form.is_valid(): + if default_token_generator.check_token(user, token): + form.save() + + # Return the success message with OK HTTP status + return Response({"success": "Password has been reset with the new password."}, + status=status.HTTP_200_OK) + else: + return Response({"error": "Invalid password reset token."}, + status=status.HTTP_400_BAD_REQUEST) + else: + return Response(form._errors, status=status.HTTP_400_BAD_REQUEST) + + else: + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + + else: + return Response({"errors": "Couldn\'t find the user from uidb64."}, 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): + + """ + Calls Django Auth SetPasswordForm save method. + + Accepts the following POST parameters: new_password1, new_password2 + Returns the success/fail message. + """ + + serializer_class = SetPasswordSerializer + + def post(self, request): + # Create a serializer with request.DATA + serializer = self.serializer_class(data=request.DATA) + + if serializer.is_valid(): + # Construct the SetPasswordForm instance + form = SetPasswordForm(user=request.user, data=serializer.data) + + if form.is_valid(): + form.save() + + # Return the success message with OK HTTP status + return Response({"success": "New password has been saved."}, + status=status.HTTP_200_OK) + + else: + return Response(form._errors, + status=status.HTTP_400_BAD_REQUEST) + + else: + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST)