Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix http methods and add password update endpoint #26

Open
wants to merge 11 commits into
base: ng
Choose a base branch
from
5 changes: 4 additions & 1 deletion src/codeschool/core/users/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from codeschool.api import router
from django.conf.urls import url

from . import views

router.register(r'users', views.UserViewSet,base_name="users")
router.register(r'profile', views.ProfileViewSet)
router.register(r'detail', views.UserDetailViewSet)
router.register(r'change_password',views.ChangePasswordViewSet,base_name="change-password")
4 changes: 2 additions & 2 deletions src/codeschool/core/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class Profile(models.TimeStampedModel):
_('Phone'),
max_length=20,
blank=True,
null=True,
default="",
)
gender = models.SmallIntegerField(
_('gender'),
Expand All @@ -217,7 +217,7 @@ class Profile(models.TimeStampedModel):
website = models.URLField(
_('Website'),
blank=True,
null=True,
default="",
help_text=_(
'A website that is shown publicly in your profile.'
)
Expand Down
28 changes: 16 additions & 12 deletions src/codeschool/core/users/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@
from .views import *
import re

# https://github.com/HazyResearch/elementary/blob/master/django/resources/views.py


class UserPermissions(BasePermission):
"""
Custom user permissions. Admin can do everything. POSTS are allowed from anyone.
"""

def has_permission(self, request, view):
single_user_regex = r"/api/users/[0-9]+"
single_user_regex = r"/api/detail/$"
user_regex = r"/api/users"
profile_regex = r"/api/profile/"
profile_regex = r"/api/detail/[0-9]+/"
prof_regex = r"/api/prof"
p_regex = r"/api/prof/[0-9]+/"
change_password_regex = r"/api/users/[0-9]+/change-password"
if request.user.is_authenticated() and not request.user.is_staff and re.match(prof_regex,request.path):
all_info = view.queryset
owner_user_id = request.user.id
owner_user_info = Profile.objects.filter(user_id=owner_user_id)
view.queryset = owner_user_info
return request.method in ['GET', 'HEAD', 'OPTIONS', 'PUT']
if re.match(change_password_regex,request.path):
return request.user.is_staff
if request.user.is_authenticated() and not request.user.is_staff and re.match(profile_regex,request.path):
all_info = view.queryset
owner_user_id = request.user.id
owner_user_info = Profile.objects.filter(id=owner_user_id)
owner_user_info = User.objects.filter(id=owner_user_id)
view.queryset = owner_user_info
return request.method in ['GET', 'HEAD', 'OPTIONS', 'PUT']
if request.user.is_authenticated() and not request.user.is_staff:
Expand All @@ -28,17 +36,13 @@ def has_permission(self, request, view):
owner_user_info = User.objects.filter(id=owner_user_id)
view.queryset = owner_user_info
return request.method in ['GET', 'HEAD', 'OPTIONS', 'PUT']
if re.match(single_user_regex, request.path):
url_user_search = re.search(r"[0-9]+", request.path)
url_user_id = int(url_user_search.group(0))
return request.user.id == url_user_id or request.user.is_staff
if not request.user or not request.user.is_authenticated():
return request.method == 'POST'
if re.match(user_regex, request.path) or re.match(profile_regex,request.path):
if re.match(single_user_regex, request.path):
return request.user.is_staff
return False

def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
return request.user == obj
return request.user.id == obj.__dict__['id']
158 changes: 130 additions & 28 deletions src/codeschool/core/users/serializers.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,70 @@
from rest_framework import serializers
from rest_framework.decorators import detail_route, list_route
from django.contrib.auth.hashers import make_password
from django.contrib.auth.hashers import make_password,check_password
from . import models



class ChangePasswordSerializer(serializers.ModelSerializer):

url = serializers.HyperlinkedIdentityField(view_name="change-password-detail")
password_confirmation = serializers.CharField(write_only=True)
old_password = serializers.CharField(write_only=True)

class Meta:
model = models.User
fields = ('url','old_password','password','password_confirmation')

write_only = {'write_only':True}
extra_kwargs = {'password':write_only,
'password_confirmation':write_only,
}

def update(self, instance, validated_data):

old_password_from_db = models.User.objects.values().get(id=instance.__dict__['id'])['password']
old_password_from_input = validated_data.pop('old_password')
new_password = validated_data.pop('password')
if check_password(old_password_from_input,old_password_from_db) and check_password(new_password,make_password(validated_data['password_confirmation'])):
instance.password = make_password(new_password)
instance.save()
else:
raise serializers.ValidationError("I guess you didn't type the old password correctly")
return instance

class ProfileSerializer(serializers.ModelSerializer):
"""
Serialize user profiles
"""

# TODO: hyperlinks as sub-resource under each user
# TODO: add extra user fields?
# TODO: nullify fields that user is not allowed to see? (this may be
# expensive in querysets)


class Meta:
model = models.Profile
fields = (
'gender','phone','date_of_birth'
,'website','about_me', 'visibility', 'user'
)
read_only = {'read_only': True}
extra_kwargs = {
'user':read_only
}
fields = ('gender','phone','website','date_of_birth',
'website','about_me', 'visibility','user')

read_only = {'read_only':True}
extra_kwargs = {"user":read_only}

class UserSerializer(serializers.ModelSerializer):
class CreateUserSerializer(serializers.ModelSerializer):
"""
Serialize User objects.
"""

role = serializers.SerializerMethodField()
profile = ProfileSerializer()
password_confirmation = serializers.CharField(write_only=True)

class Meta:
model = models.User
fields = ('alias', 'role','email', 'name', 'school_id', 'password', 'password_confirmation')
fields = ('username', 'alias', 'role','email', 'name', 'school_id','password','password_confirmation', 'profile')
write_only = {'write_only': True}
read_only = {'read_only':True}



extra_kwargs = {
'email': write_only,
'role': write_only,
'name': write_only,
'school_id': write_only,
'password': write_only,
'password_confirmation': write_only
}
'password':write_only,
'profile':write_only,
}

def get_role(self, obj):
if(obj.role == models.User.ROLE_STUDENT):
Expand All @@ -58,14 +76,98 @@ def get_role(self, obj):
elif(obj.role == models.User.ROLE_ADMIN):
return 'admin'


def create(self, validated_data):
validated_data['password'] = validated_data['password']
password_confirmation = validated_data.pop('password_confirmation', None)
if(password_confirmation == validated_data['password']):
validated_data['password'] = make_password(validated_data['password'])
return super(UserSerializer, self).create(validated_data)
else:
raise Exception()
raise serializers.ValidationError("I guess you didn't type the password confirmation just like the password")
profile_data = validated_data.pop('profile')
user = super(CreateUserSerializer,self).create(validated_data)
self.update_or_create_profile(user,profile_data)
return user

def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
models.Profile.objects.update_or_create(user=user, defaults=profile_data)



class UserSerializer(serializers.HyperlinkedModelSerializer):

class Meta:
model = models.User
fields = ('url',)



class UserDetailSerializer(serializers.HyperlinkedModelSerializer):
"""
Serialize User objects.
"""

role = serializers.SerializerMethodField()
profile = ProfileSerializer()

class Meta:
model = models.User
fields = ('username', 'alias', 'role','email', 'name', 'school_id', 'profile')
write_only = {'write_only': True}
read_only = {'read_only':True}



extra_kwargs = {
'profile':write_only,
'school_id':read_only,
}

def get_role(self, obj):
if(obj.role == models.User.ROLE_STUDENT):
return 'student'
elif(obj.role == models.User.ROLE_TEACHER):
return 'teacher'
elif(obj.role == models.User.ROLE_STAFF):
return 'staff'
elif(obj.role == models.User.ROLE_ADMIN):
return 'admin'


def create(self, validated_data):
validated_data['password'] = make_password(validated_data['password'])
profile_data = validated_data.pop('profile')
user = super(UserDetailSerializer,self).create(validated_data)
self.update_or_create_profile(user,profile_data)
return user

def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
models.Profile.objects.update_or_create(user=user, defaults=profile_data)

def update(self, instance, validated_data):


profile_data = validated_data.pop('profile')
profile = instance.profile

instance.alias = validated_data.get('alias')
instance.email = validated_data.get('email')
instance.name = validated_data.get('name')

instance.profile.gender = profile_data.get('gender')
instance.profile.phone = profile_data.get('phone')
instance.profile.date_of_birth = profile_data.get('date_of_birth')
instance.profile.about_me = profile_data.get('about_me')
instance.profile.website = profile_data.get('website')


instance.save()
profile.save()

return instance


class FullUserSerializer(serializers.ModelSerializer):
Expand Down
32 changes: 28 additions & 4 deletions src/codeschool/core/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,34 @@
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser
from .permissions import UserPermissions

authentication_backend = get_config('AUTHENTICATION_BACKENDS')[-1]


#
# REST endpoints
#

class ChangePasswordViewSet(viewsets.ModelViewSet):
permission_classes = (UserPermissions,)
queryset = models.User.objects.all()
serializer_class = serializers.ChangePasswordSerializer
class ProfileViewSet(viewsets.ModelViewSet):
permission_classes = (UserPermissions,)
method = 'put'
queryset = models.Profile.objects.all()
serializer_class = serializers.ProfileSerializer

class UserDetailViewSet(viewsets.ModelViewSet):
permission_classes = (UserPermissions,)
queryset = models.User.objects.all()
serializer_class = serializers.UserDetailSerializer

def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method == 'POST':
serializer_class = serializers.CreateUserSerializer
return serializer_class


class UserViewSet(viewsets.ModelViewSet):
"""
Active users in the Codeschool platform.
Expand All @@ -48,8 +62,18 @@ def set_profile(self, request, pk=None):
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)



@detail_route(methods=['put'],url_path='change-password')
def set_profile(self, request, pk=None):
print("Llll")
profile = self.get_object()
serializer = serializers.ProfileSerializer(data=request.data)
if serializer.is_valid():
profile.set_profile(serializer.data['phone'])
profile.save()
return Response({'status': 'gender set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
#
# Auth views
#
Expand Down