Refactoring user resource.

remotes/origin/enhancement/email-actions
Andrey Antukh 2013-10-16 17:14:25 +02:00
parent 4b7b2727a3
commit e13f5dfe42
6 changed files with 228 additions and 25 deletions

View File

@ -3,9 +3,10 @@
import uuid
from django.db.models.loading import get_model
from django.db.models import Q
from django.contrib.auth import logout, login, authenticate
from rest_framework.decorators import action
from rest_framework.decorators import list_route, action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import status, viewsets
@ -13,10 +14,13 @@ from rest_framework import status, viewsets
from djmail.template_mail import MagicMailBuilder
from greenmine.base import exceptions as exc
from greenmine.base.filters import FilterBackend
from greenmine.base.api import ModelCrudViewSet
from .models import User, Role
from .serializers import (LoginSerializer, UserLogged,
UserSerializer, RoleSerializer,)
UserSerializer, RoleSerializer,
RecoverySerializer)
class RolesViewSet(viewsets.ViewSet):
@ -38,55 +42,95 @@ class RolesViewSet(viewsets.ViewSet):
return Response(serializer.data)
class UsersViewSet(viewsets.ViewSet):
permission_classes = (IsAuthenticated,)
class ProjectMembershipFilter(FilterBackend):
def filter_queryset(self, request, queryset, view):
queryset = super().filter_queryset(request, queryset, view)
if request.user.is_superuser:
return queryset
def get_list_queryset(self):
project_model = get_model("projects", "Project")
own_projects = (project_model.objects
.filter(members=self.request.user))
own_projects = project_model.objects.filter(members=request.user)
project = self.request.QUERY_PARAMS.get('project', None)
project = request.QUERY_PARAMS.get('project', None)
if project is not None:
own_projects = own_projects.filter(pk=project)
queryset = (User.objects.filter(projects__in=own_projects)
queryset = (queryset.filter(projects__in=own_projects)
.order_by('username').distinct())
return queryset
def list(self, request, pk=None):
queryset = self.get_list_queryset()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
return Response({})
class UsersViewSet(ModelCrudViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
queryset = User.objects.all()
filter_backends = (ProjectMembershipFilter,)
@action(methods=["POST"], permission_classes=[])
def pre_conditions_on_save(self, obj):
if not self.request.user.is_superuser and obj.id != self.request.user.id:
raise exc.PreconditionError()
def pre_conditions_on_delete(self, obj):
if not self.request.user.is_superuser and obj.id != self.request.user.id:
raise exc.PreconditionError()
@list_route(permission_classes=[AllowAny], methods=["POST"])
def password_recovery(self, request, pk=None):
username_or_email = request.DATA.get('username', None)
if not username_or_email:
return Response({"detail": "Invalid username or password"},
status.HTTP_400_BAD_REQUEST)
raise exc.WrongArguments("Invalid username or email")
try:
queryset = User.objects.all()
user = queryset.get(Q(username=username_or_email) |
Q(email=username_or_email))
except User.DoesNotExist:
return Response({"detail": "Invalid username or password"},
status.HTTP_400_BAD_REQUEST)
raise exc.WrongArguments("Invalid username or email")
user.token = str(uuid.uuid1())
user.save(update_fields=["token"])
mbuilder = MagicMailBuilder()
email = mbuilder.password_recovery(user.email, {"user": user})
email.send()
return Response({"detail": "Mail sended successful!"})
@list_route(permission_classes=[AllowAny], methods=["POST"])
def change_password_from_recovery(self, request, pk=None):
"""
Change password with token (from password recovery step).
"""
serializer = RecoverySerializer(data=request.DATA, many=False)
if not serializer.is_valid():
raise exc.WrongArguments("Token is invalid")
user = User.objects.get(token=serializer.data["token"])
user.set_password(serializer.data["password"])
user.token = None
user.save(update_fields=["password", "token"])
return Response(status=status.HTTP_204_NO_CONTENT)
@list_route(permission_classes=[IsAuthenticated], methods=["POST"])
def change_password(self, request, pk=None):
"""
Change password to current logged user.
"""
password = request.DATA.get("password")
if not password:
raise exc.WrongArguments("incomplete argiments")
if len(password) < 6:
raise exc.WrongArguments("invalid password length")
request.user.set_password(password)
request.user.save(update_fields=["password"])
return Response(status=status.HTTP_204_NO_CONTENT)
class AuthViewSet(viewsets.ViewSet):
permission_classes = (AllowAny,)

View File

@ -18,7 +18,7 @@ class User(WatcherMixin, AbstractUser):
verbose_name=_('default language'))
default_timezone = models.CharField(max_length=20, null=False, blank=True, default='',
verbose_name=_('default timezone'))
token = models.CharField(max_length=200, null=False, blank=True, default='',
token = models.CharField(max_length=200, null=True, blank=True, default=None,
verbose_name=_('token'))
colorize_tags = models.BooleanField(null=False, blank=True, default=False,
verbose_name=_('colorize tags'))

View File

@ -69,6 +69,20 @@ class UserSerializer(serializers.ModelSerializer):
return [{"id": x.id, "name": x.name} for x in obj.projects.all()]
class RecoverySerializer(serializers.Serializer):
token = serializers.CharField(max_length=200)
password = serializers.CharField(min_length=6)
def validate_token(self, attrs, source):
token = attrs[source]
try:
user = User.objects.get(token=token)
except User.DoesNotExist:
raise serializers.ValidationError("invalid token")
return attrs
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role

View File

@ -3,7 +3,7 @@
from django.db.models.loading import get_model
def create_user(id, save=True):
def create_user(id, save=True, is_superuser=False):
model = get_model("users", "User")
instance = model(
@ -12,7 +12,11 @@ def create_user(id, save=True):
first_name="Foo{0}".format(id),
last_name="Bar{0}".format(id)
)
instance.set_password(instance.username)
if is_superuser:
instance.is_staff = True
instance.is_superuser = True
if save:
instance.save()

View File

@ -4,12 +4,153 @@ import json
from django import test
from django.core.urlresolvers import reverse
from django.core import mail
from django.db.models import get_model
from greenmine.base.users.tests import create_user
from greenmine.projects.models import Project
from . import create_project, add_membership
class ProfileTestCase(test.TestCase):
fixtures = ["initial_role.json", ]
def setUp(self):
self.user1 = create_user(1, is_superuser=True)
self.user2 = create_user(2)
self.user3 = create_user(3)
self.project1 = create_project(1, self.user1)
self.project2 = create_project(2, self.user1)
self.project3 = create_project(3, self.user2)
add_membership(self.project1, self.user3, "dev")
add_membership(self.project3, self.user3, "dev")
add_membership(self.project3, self.user2, "dev")
def test_list_users(self):
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
response = self.client.get(reverse("users-list"))
self.assertEqual(response.status_code, 200)
users_list = response.data
self.assertEqual(len(users_list), 2)
def test_update_users(self):
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.patch(
reverse("users-detail", args=[self.user2.pk]),
content_type="application/json",
data=json.dumps(data))
self.assertEqual(response.status_code, 400)
def test_update_users_self(self):
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.patch(
reverse("users-detail", args=[self.user3.pk]),
content_type="application/json",
data=json.dumps(data))
self.assertEqual(response.status_code, 200)
def test_update_users_superuser(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.patch(
reverse("users-detail", args=[self.user3.pk]),
content_type="application/json",
data=json.dumps(data))
self.assertEqual(response.status_code, 200)
def test_delete_users(self):
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.delete(
reverse("users-detail", args=[self.user2.pk]))
self.assertEqual(response.status_code, 400)
def test_delete_users_self(self):
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.delete(
reverse("users-detail", args=[self.user3.pk]))
self.assertEqual(response.status_code, 204)
def test_delete_users_superuser(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
data = {"first_name": "Foo Bar"}
response = self.client.delete(
reverse("users-detail", args=[self.user3.pk]))
self.assertEqual(response.status_code, 204)
def test_password_recovery(self):
url = reverse("users-password-recovery")
data = {"username": self.user1.username}
response = self.client.post(url, data=json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertNotEqual(len(mail.outbox[0].body), 0)
def test_users_change_password_from_recovery(self):
self.user1.token = "1111-1111-1111-1111"
self.user1.save()
url = reverse("users-change-password-from-recovery")
data = {"token": self.user1.token, "password": "111111"}
response = self.client.post(url, data=json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 204)
user = get_model("users", "User").objects.get(pk=self.user1.pk)
self.assertTrue(user.check_password("111111"))
def test_users_change_password(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
url = reverse("users-change-password")
data = {"password": "111111"}
response = self.client.post(url, data=json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 204)
user = get_model("users", "User").objects.get(pk=self.user1.pk)
self.assertTrue(user.check_password("111111"))
class ProjectsTestCase(test.TestCase):
fixtures = ["initial_role.json", ]

View File

@ -59,7 +59,7 @@ SEND_BROKEN_LINK_EMAILS = True
IGNORABLE_404_ENDS = ('.php', '.cgi')
IGNORABLE_404_STARTS = ('/phpmyadmin/',)
ATOMIC_REQUESTS = True
ATOMIC_REQUESTS = False
TIME_ZONE = 'Europe/Madrid'
LANGUAGE_CODE = 'en'