Created AUTHORS, MANIFEST.in, and setup.py.

+ Revised README.md.
+ AutoPEP8 rest_auth python files.
This commit is contained in:
Silin Na 2014-04-30 13:55:04 -07:00
parent e1c577d4fd
commit 93a3de57ff
10 changed files with 276 additions and 55 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
http://github.com/Tivix/django-rest-auth/contributors

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
include AUTHORS
include LICENSE
include MANIFEST.in
include README.md

115
README.md
View File

@ -1,2 +1,115 @@
django-rest-auth
================
=====
Since the introduction of django-rest-framework, Django apps have been able to serve up app-level REST API endpoints. As a result, we saw a lot of instances where developers implemented their own REST registration API endpoints here and there, snippets, and so on. We aim to solve this demand by providing django-rest-auth, a set of REST API endpoints to handle User Registration and Authentication tasks. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for User Management. Of course, we'll add more API endpoints as we see the demand.
Features
--------
1. User Registration with activation
2. Login/Logout
3. Retrieve/Update the Django User & user-defined UserProfile model
4. Password change
5. Password reset via e-mail
Installation
-----------
1. This project needs the following packages
> django-registration>=1.0
>
> djangorestframework>=2.3.13
>
> django-rest-swagger>=0.1.14
2. Install this package.
3. Add rest_auth app to INSTALLED\_APPS in your django settings.py
> INSTALLED\_APPS = (
>
> ...,
>
> 'rest_auth',
> )
4. This project depends on django-rest-framework library, therefore the following REST_FRAMEWORK settings needs to be entered in your Django settings.py::
> REST_FRAMEWORK = {
>
> 'DEFAULT_AUTHENTICATION_CLASSES': (
> 'rest_framework.authentication.SessionAuthentication',
> ),
>
> 'DEFAULT_PERMISSION_CLASSES': (
> 'rest_framework.permissions.IsAuthenticated'
> )
> }
5. Lastly, this project accepts the following Django setting values. You can set the UserProfile model and/or create your own REST registration backend for django-registration.
> REST\_REGISTRATION\_BACKEND = 'rest\_auth.backends.rest\_registration.RESTRegistrationView'
>
> REST\_PROFILE\_MODULE = 'accounts.UserProfile'
6. You're good to go now!
API endpoints without Authentication
------------------------------------
1. /rest\_accounts/register/ - POST
Parameters
username, password, email, first\_name, last\_name
2. /rest\_accounts/password/reset/ - POST
Parameters
email
3. /rest\_accounts/password/reset/confirm/{uidb64}/{token}/ - POST
Django URL Keywords
uidb64, token
Parameters
new\_password1, new\_password2
4. /rest\_accounts/login/ - POST
Parameters
username, password
5. /rest\_accounts/verify-email/{activation\_key}/ - GET
Django URL Keywords
activation_key
API endpoints with Authentication
------------------------------------
1. /rest\_accounts/logout/ - GET
2. /rest\_accounts/user/ - GET & POST
GET Parameters
POST Parameters
user as dictionary with id, email, first\_name, last\_name
Ex) "user": {"id": 1, "first\_name": "Person", "last\_name": "2"}
user-defined UserProfile model fields
3. /rest\_accounts/password/change/ - POST
Parameters
new\_password1, new\_password2

View File

@ -12,6 +12,7 @@ class LoginSerializer(serializers.Serializer):
class TokenSerializer(serializers.ModelSerializer):
"""
Serializer for Token model.
"""
@ -22,6 +23,7 @@ class TokenSerializer(serializers.ModelSerializer):
class UserRegistrationSerializer(serializers.ModelSerializer):
"""
Serializer for Django User model and most of its fields.
"""
@ -32,6 +34,7 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
class UserRegistrationProfileSerializer(serializers.ModelSerializer):
"""
Serializer that includes all profile fields except for user fk / id.
"""
@ -42,6 +45,7 @@ class UserRegistrationProfileSerializer(serializers.ModelSerializer):
class UserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
"""
@ -51,6 +55,7 @@ class UserDetailsSerializer(serializers.ModelSerializer):
class UserProfileSerializer(serializers.ModelSerializer):
"""
Serializer for UserProfile model.
"""
@ -63,6 +68,7 @@ class UserProfileSerializer(serializers.ModelSerializer):
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
ModelSerializer that allows fields argument to control fields
"""
@ -81,6 +87,7 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer):
class UserUpdateSerializer(DynamicFieldsModelSerializer):
"""
User model w/o username and password
"""
@ -90,6 +97,7 @@ class UserUpdateSerializer(DynamicFieldsModelSerializer):
class UserProfileUpdateSerializer(serializers.ModelSerializer):
"""
Serializer for updating User and UserProfile model.
"""
@ -102,6 +110,7 @@ class UserProfileUpdateSerializer(serializers.ModelSerializer):
class SetPasswordSerializer(serializers.Serializer):
"""
Serializer for changing Django User password.
"""
@ -111,6 +120,7 @@ class SetPasswordSerializer(serializers.Serializer):
class PasswordResetSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""

View File

@ -11,13 +11,16 @@ from registration.models import RegistrationProfile
# Get the UserProfile model from the setting value
user_profile_model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None))
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')
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
@ -30,7 +33,8 @@ class RegistrationAndActivationTestCase(TestCase):
def test_successful_registration(self):
print 'Registering a new user'
payload = {"username": "person", "password": "person", "email": "person@world.com", "newsletter_subscribe": "false"}
payload = {"username": "person", "password": "person",
"email": "person@world.com", "newsletter_subscribe": "false"}
print 'The request will attempt to register:'
print 'Django User object'
@ -39,7 +43,8 @@ class RegistrationAndActivationTestCase(TestCase):
print 'newsletter_subscribe: false'
print 'Sending a POST request to register API'
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 201):
print r.content
@ -47,10 +52,12 @@ class RegistrationAndActivationTestCase(TestCase):
print 'Activating a new user'
# Get the latest activation key from RegistrationProfile model
activation_key = RegistrationProfile.objects.latest('id').activation_key
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 + "/"
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"
@ -69,7 +76,8 @@ class RegistrationAndActivationTestCase(TestCase):
def test_successful_registration_without_userprofile_model(self):
print 'Registering a new user'
payload = {"username": "person1", "password": "person1", "email": "person1@world.com"}
payload = {"username": "person1", "password":
"person1", "email": "person1@world.com"}
print 'The request will attempt to register:'
print 'Django User object'
@ -77,7 +85,8 @@ class RegistrationAndActivationTestCase(TestCase):
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)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 201):
print r.content
@ -85,10 +94,12 @@ class RegistrationAndActivationTestCase(TestCase):
print 'Activating a new user'
# Get the latest activation key from RegistrationProfile model
activation_key = RegistrationProfile.objects.latest('id').activation_key
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 + "/"
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"
@ -112,13 +123,15 @@ class RegistrationAndActivationTestCase(TestCase):
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)
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
@ -137,7 +150,8 @@ class LoginTestCase(TestCase):
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)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 200):
print r.content
@ -152,7 +166,8 @@ class LoginTestCase(TestCase):
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)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 401):
print r.content
@ -164,13 +179,15 @@ class LoginTestCase(TestCase):
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)
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
@ -188,7 +205,8 @@ class PasswordChangeCase(TestCase):
print 'Sending a POST request to login API'
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
r = requests.post(login_url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 200):
print r.content
@ -198,18 +216,22 @@ class PasswordChangeCase(TestCase):
self.token = r.json()['key']
self.headers['authorization'] = "Token " + r.json()['key']
payload = {"new_password1": "new_person", "new_password2": "new_person"}
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)
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"}
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)
r = requests.post(
self.url, data=json.dumps(payload), headers=self.headers)
if self.assertEqual(r.status_code, 200):
print r.content
@ -221,7 +243,8 @@ class PasswordChangeCase(TestCase):
print 'Sending a POST request to login API'
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
r = requests.post(login_url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 200):
print r.content
@ -231,10 +254,12 @@ class PasswordChangeCase(TestCase):
self.token = r.json()['key']
self.headers['authorization'] = "Token " + r.json()['key']
payload = {"new_password1": "new_person", "new_password2": "wrong_person"}
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)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 400):
print r.content
@ -246,7 +271,8 @@ class PasswordChangeCase(TestCase):
print 'Sending a POST request to login API'
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
r = requests.post(login_url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 200):
print r.content
@ -261,7 +287,8 @@ class PasswordChangeCase(TestCase):
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)
r = requests.post(self.url, data=json.dumps(payload),
headers=self.headers)
if self.assertEqual(r.status_code, 400):
print r.content

View File

@ -11,6 +11,7 @@ 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)
@ -19,6 +20,7 @@ class APIClient(Client):
class CustomJSONEncoder(json.JSONEncoder):
"""
Convert datetime/date objects into isoformat
"""
@ -31,13 +33,15 @@ class CustomJSONEncoder(json.JSONEncoder):
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')
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)
@ -55,10 +59,12 @@ class BaseAPITestCase(object):
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
if hasattr(self, 'company_token'):
kwargs['HTTP_AUTHORIZATION'] = 'Company-Token %s' % 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']))
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:
@ -95,7 +101,8 @@ class BaseAPITestCase(object):
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)
self.assertEqual(
bool(filter(lambda x: content_type in x, response._headers['content-type'])), True)
return response
def init(self):
@ -103,12 +110,11 @@ class BaseAPITestCase(object):
self.client = APIClient()
# -----------------------
# T E S T H E R E
# -----------------------
class LoginAPITestCase(TestCase, BaseAPITestCase):
"""
just run: python manage.py test rest_auth
"""
@ -137,7 +143,6 @@ class LoginAPITestCase(TestCase, BaseAPITestCase):
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
@ -145,4 +150,5 @@ class LoginAPITestCase(TestCase, BaseAPITestCase):
# TODO:
# another case to test - make user inactive and test if login is impossible
# another case to test - make user inactive and test if login is
# impossible

View File

@ -7,23 +7,29 @@ from .views import Login, Logout, Register, UserDetails, \
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'^register/$', Register.as_view(),
name='rest_register'),
url(r'^password/reset/$', PasswordReset.as_view(),
name='rest_password_reset'),
url(r'^password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
PasswordResetConfirm.as_view(), name='rest_password_reset_confirm'),
PasswordResetConfirm.as_view(
), name='rest_password_reset_confirm'),
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 session / token.
# 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'^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')),
url(r'^docs/',
include('rest_framework_swagger.urls')),
)

View File

@ -3,6 +3,7 @@ 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.

View File

@ -230,7 +230,8 @@ class PasswordReset(LoggedOutRESTAPIView, GenericAPIView):
reset_form.save(**opts)
# Return the success message with OK HTTP status
return Response({"success": "Password reset e-mail has been sent."},
return Response(
{"success": "Password reset e-mail has been sent."},
status=status.HTTP_200_OK)
else:
@ -241,7 +242,9 @@ class PasswordReset(LoggedOutRESTAPIView, GenericAPIView):
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.
@ -276,10 +279,13 @@ class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
form.save()
# Return the success message with OK HTTP status
return Response({"success": "Password has been reset with the new password."},
return Response(
{"success":
"Password has been reset with the new password."},
status=status.HTTP_200_OK)
else:
return Response({"error": "Invalid password reset token."},
return Response(
{"error": "Invalid password reset token."},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(form._errors, status=status.HTTP_400_BAD_REQUEST)
@ -293,6 +299,7 @@ class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
class VerifyEmail(LoggedOutRESTAPIView, GenericAPIView):
"""
Verifies the email of the user through their activation_key.

46
setup.py Normal file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
import os
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, 'README.md'))
long_description = f.read().strip()
f.close()
setup(
name='django-rest-auth',
version='0.1',
author='Sumit Chachra',
author_email='chachra@tivix.com',
url='http://github.com/Tivix/django-rest-auth',
description='Create a set of REST API endpoints for Authentication and Registration',
packages=find_packages(),
long_description=long_description,
keywords='django rest auth registration rest-framework django-registration api',
zip_safe=False,
install_requires=[
'Django>=1.5.0',
'django-registration>=1.0',
'djangorestframework>=2.3.13',
'django-rest-swagger>=0.1.14',
],
test_suite='rest_auth.tests',
include_package_data=True,
# cmdclass={},
classifiers=[
'Framework :: Django',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'Operating System :: OS Independent',
'Topic :: Software Development'
],
)