Merge pull request #30 from Tivix/develop

Develop
This commit is contained in:
Mateusz Sikora 2014-11-12 12:00:22 +01:00
commit 86eb741b34
11 changed files with 103 additions and 17 deletions

View File

@ -16,7 +16,7 @@ Basic
- email - email
- /rest-auth/password/reset/confim/ (POST) - /rest-auth/password/reset/confirm/ (POST)
- uid - uid
- token - token

View File

@ -1,7 +1,9 @@
Demo project Demo project
============ ============
To run demo project (ideally in virtualenv): The idea of creating demo project was to show how you can potentially use
django-rest-auth app with jQuery on frontend.
Do these steps to make it running (ideally in virtualenv).
.. code-block:: python .. code-block:: python

17
docs/faq.rst Normal file
View File

@ -0,0 +1,17 @@
FAQ
===
1. Why account_confirm_email url is defined but it is not usable?
In /rest_auth/registration/urls.py we can find something like this:
.. code-block:: python
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
name='account_confirm_email'),
This url is used by django-allauth. Empty TemplateView is defined just to allow reverse() call inside app - when email with verification link is being sent.
You should override this view/url to handle it in your API client somehow and then, send post to /verify-email/ endpoint with proper key.
If you don't want to use API on that step, then just use ConfirmEmailView view from:
djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190

View File

@ -22,6 +22,7 @@ Contents
API endpoints <api_endpoints> API endpoints <api_endpoints>
Configuration <configuration> Configuration <configuration>
Demo project <demo> Demo project <demo>
FAQ <faq>
Changelog <changelog> Changelog <changelog>

View File

@ -14,7 +14,21 @@ Features
* Password reset via e-mail * Password reset via e-mail
* Social Media authentication * Social Media authentication
Apps structure Apps structure
-------------- --------------
* ``rest_auth`` has basic auth functionality like login, logout, password reset and password change * ``rest_auth`` has basic auth functionality like login, logout, password reset and password change
* ``rest_auth.registration`` has logic related with registration and social media authentication * ``rest_auth.registration`` has logic related with registration and social media authentication
Angular app
-----------
- Tivix has also created angular module which uses API endpoints from this app - `angular-django-registration-auth <https://github.com/Tivix/angular-django-registration-auth>`_
Demo project
------------
- You can also check our :doc:`Demo Project </demo>` which is using jQuery on frontend.

View File

@ -37,5 +37,3 @@ PasswordChangeSerializer = import_callable(
serializers.get('PASSWORD_CHANGE_SERIALIZER', serializers.get('PASSWORD_CHANGE_SERIALIZER',
DefaultPasswordChangeSerializer) DefaultPasswordChangeSerializer)
) )

View File

@ -7,7 +7,7 @@ urlpatterns = patterns('',
url(r'^$', Register.as_view(), name='rest_register'), url(r'^$', Register.as_view(), name='rest_register'),
url(r'^verify-email/$', VerifyEmail.as_view(), name='rest_verify_email'), url(r'^verify-email/$', VerifyEmail.as_view(), name='rest_verify_email'),
# These two views are used in django-allauth and empty TemplateView were # This url is used by django-allauth and empty TemplateView is
# defined just to allow reverse() call inside app, for example when email # defined just to allow reverse() call inside app, for example when email
# with verification link is being sent, then it's required to render email # with verification link is being sent, then it's required to render email
# content. # content.
@ -18,8 +18,6 @@ urlpatterns = patterns('',
# If you don't want to use API on that step, then just use ConfirmEmailView # If you don't want to use API on that step, then just use ConfirmEmailView
# view from: # view from:
# djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 # djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
name='account_email_verification_sent'),
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(), url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
name='account_confirm_email'), name='account_confirm_email'),
) )

View File

@ -16,6 +16,13 @@ class Register(APIView, SignupView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
user_serializer_class = UserDetailsSerializer user_serializer_class = UserDetailsSerializer
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
def get(self, *args, **kwargs):
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
def put(self, *args, **kwargs):
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
def form_valid(self, form): def form_valid(self, form):
self.user = form.save(self.request) self.user = form.save(self.request)
@ -45,6 +52,10 @@ class Register(APIView, SignupView):
class VerifyEmail(APIView, ConfirmEmailView): class VerifyEmail(APIView, ConfirmEmailView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
def get(self, *args, **kwargs):
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.kwargs['key'] = self.request.DATA.get('key', '') self.kwargs['key'] = self.request.DATA.get('key', '')

View File

@ -121,14 +121,31 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
class PasswordChangeSerializer(serializers.Serializer): class PasswordChangeSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=128)
new_password1 = serializers.CharField(max_length=128) new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128) new_password2 = serializers.CharField(max_length=128)
set_password_form_class = SetPasswordForm set_password_form_class = SetPasswordForm
def __init__(self, *args, **kwargs):
self.old_password_field_enabled = getattr(settings,
'OLD_PASSWORD_FIELD_ENABLED', False)
super(PasswordChangeSerializer, self).__init__(*args, **kwargs)
if not self.old_password_field_enabled:
self.fields.pop('old_password')
self.request = self.context.get('request')
self.user = self.request.user
def validate_old_password(self, attrs, source):
if self.old_password_field_enabled and \
not self.user.check_password(attrs.get(source, '')):
raise serializers.ValidationError('Invalid password')
return attrs
def validate(self, attrs): def validate(self, attrs):
request = self.context.get('request') self.set_password_form = self.set_password_form_class(user=self.user,
self.set_password_form = self.set_password_form_class(user=request.user,
data=attrs) data=attrs)
if not self.set_password_form.is_valid(): if not self.set_password_form.is_valid():

View File

@ -248,6 +248,40 @@ 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)
@override_settings(OLD_PASSWORD_FIELD_ENABLED=True)
def test_password_change_with_old_password(self):
login_payload = {
"username": self.USERNAME,
"password": self.PASS
}
User.objects.create_user(self.USERNAME, '', self.PASS)
self.post(self.login_url, data=login_payload, status_code=200)
self.token = self.response.json['key']
new_password_payload = {
"old_password": "%s!" % self.PASS, # wrong password
"new_password1": "new_person",
"new_password2": "new_person"
}
self.post(self.password_change_url, data=new_password_payload,
status_code=400)
new_password_payload = {
"old_password": self.PASS,
"new_password1": "new_person",
"new_password2": "new_person"
}
self.post(self.password_change_url, data=new_password_payload,
status_code=200)
# user should not be able to login using old password
self.post(self.login_url, data=login_payload, status_code=400)
# new password should work
login_payload['password'] = new_password_payload['new_password1']
self.post(self.login_url, data=login_payload, status_code=200)
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)

View File

@ -6,8 +6,6 @@ from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.generics import GenericAPIView from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.authentication import SessionAuthentication, \
TokenAuthentication
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.generics import RetrieveUpdateAPIView from rest_framework.generics import RetrieveUpdateAPIView
@ -32,10 +30,6 @@ class Login(GenericAPIView):
token_model = Token token_model = Token
response_serializer = TokenSerializer response_serializer = TokenSerializer
def get_serializer(self):
return self.serializer_class(data=self.request.DATA,
context={'request': self.request, 'view': self})
def login(self): def login(self):
self.user = self.serializer.object['user'] self.user = self.serializer.object['user']
self.token, created = self.token_model.objects.get_or_create( self.token, created = self.token_model.objects.get_or_create(
@ -52,7 +46,7 @@ class Login(GenericAPIView):
status=status.HTTP_400_BAD_REQUEST) status=status.HTTP_400_BAD_REQUEST)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.serializer = self.get_serializer() self.serializer = self.get_serializer(data=self.request.DATA)
if not self.serializer.is_valid(): if not self.serializer.is_valid():
return self.get_error_response() return self.get_error_response()
self.login() self.login()
@ -67,7 +61,7 @@ class Logout(APIView):
Accepts/Returns nothing. Accepts/Returns nothing.
""" """
permissions_classes = (AllowAny,) permission_classes = (AllowAny,)
def post(self, request): def post(self, request):
try: try: