Commit e8079030e46eb506b6f03157dcdfdb69cf3fde3e

Authored by Chung Wang
Exists in master

Test

Merge branch 'master' of https://git.ucsd.edu/110swag/flashy-backend

Showing 6 changed files Side-by-side Diff

flashcards/admin.py View file @ e807903
1 1 from django.contrib import admin
2 2 from flashcards.models import Flashcard, UserFlashcard, Section, FlashcardMask, \
3   - UserFlashcardReview
  3 + UserFlashcardReview, LecturePeriod, User
  4 +from simple_email_confirmation import EmailAddress
4 5  
5 6 admin.site.register([
  7 + User,
6 8 Flashcard,
7 9 FlashcardMask,
8 10 UserFlashcard,
9 11 UserFlashcardReview,
10   - Section
  12 + Section,
  13 + LecturePeriod
11 14 ])
flashcards/api.py View file @ e807903
1 1 from django.core.mail import send_mail
  2 +from django.contrib.auth import authenticate, login
  3 +from django.contrib.auth.tokens import default_token_generator
2 4 from rest_framework.views import APIView
3 5 from rest_framework.response import Response
4 6 from rest_framework import status
5   -from rest_framework.exceptions import ValidationError
  7 +from rest_framework.exceptions import ValidationError, NotFound
6 8 from flashcards.serializers import *
7 9  
8 10  
... ... @@ -49,7 +51,7 @@
49 51 raise ValidationError('Password is required')
50 52  
51 53 email = request.data['email']
52   - user = User.objects.create_user(email)
  54 + user = User.objects.create_user(email, email=email, password=request.data['password'])
53 55  
54 56 body = '''
55 57 Visit the following link to confirm your email address:
56 58  
... ... @@ -63,5 +65,91 @@
63 65 "noreply@flashy.cards",
64 66 [user.email])
65 67  
  68 + user = authenticate(email=email, password=request.data['password'])
  69 + login(request, user)
  70 + return Response(UserSerializer(user).data)
  71 +
  72 + def delete(self, request, format=None):
  73 + request.user.delete()
  74 + return Response(status=status.HTTP_204_NO_CONTENT)
  75 +
  76 +
  77 +class UserLogin(APIView):
  78 + """
  79 + Authenticates user and returns user data if valid. Handles invalid
  80 + users.
  81 + """
  82 +
  83 + def post(self, request, format=None):
  84 + """
  85 + Returns user data if valid.
  86 + """
  87 + if 'email' not in request.data:
  88 + raise ValidationError('Email is required')
  89 + if 'password' not in request.data:
  90 + raise ValidationError('Password is required')
  91 +
  92 + email = request.data['email']
  93 + password = request.data['password']
  94 + user = authenticate(username=email, password=password)
  95 +
  96 + if user is None:
  97 + raise ValidationError('Invalid email or password')
  98 + if not user.is_active:
  99 + raise ValidationError('Account is disabled')
  100 + login(request, user)
66 101 return Response(UserSerializer(User).data)
  102 +
  103 +
  104 +class PasswordReset(APIView):
  105 + """
  106 + Allows user to reset their password.
  107 + """
  108 +
  109 + def post(self, request, format=None):
  110 + """
  111 + Send a password reset token/link to the provided email.
  112 + """
  113 + if 'email' not in request.data:
  114 + raise ValidationError('Email is required')
  115 +
  116 + email = request.data['email']
  117 +
  118 + # Find the user since they are not logged in.
  119 + try:
  120 + user = User.objects.get(email=email)
  121 + except User.DoesNotExist:
  122 + raise NotFound('Email does not exist')
  123 +
  124 + token = default_token_generator.make_token(user)
  125 +
  126 + body = '''
  127 + Visit the following link to reset your password:
  128 + http://flashy.cards/app/reset_password/%d/%s
  129 +
  130 + If you did not request a password reset, no action is required.
  131 + '''
  132 +
  133 + send_mail("Flashy password reset",
  134 + body % (user.pk, token),
  135 + "noreply@flashy.cards",
  136 + [user.email])
  137 +
  138 + return Response(status=status.HTTP_204_NO_CONTENT)
  139 +
  140 + def patch(self, request, format=None):
  141 + """
  142 + Updates user's password to new password.
  143 + """
  144 + if 'new_password' not in request.data:
  145 + raise ValidationError('New password is required')
  146 + if not request.data['new_password']:
  147 + raise ValidationError('Password cannot be blank')
  148 +
  149 + user = request.user
  150 +
  151 + user.set_password(request.data['new_password'])
  152 + user.save()
  153 +
  154 + return Response(status=status.HTTP_204_NO_CONTENT)
flashcards/models.py View file @ e807903
1   -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
  1 +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin, AbstractUser
2 2 from django.contrib.auth.tests.custom_user import CustomUser
3 3 from django.db.models import *
4 4 from simple_email_confirmation import SimpleEmailConfirmationUserMixin
5 5  
  6 +# Hack to fix AbstractUser before subclassing it
  7 +AbstractUser._meta.get_field('email')._unique = True
6 8  
7   -class UserManager(BaseUserManager):
8   - def create_user(self, email, password=None):
9   - """
10   - Creates and saves a User with the given email, date of
11   - birth and password.
12   - """
13   - if not email:
14   - raise ValueError('Users must have an email address')
15   -
16   - user = self.model(email=self.normalize_email(email))
17   -
18   - user.set_password(password)
19   - user.save(using=self._db)
20   - return user
21   -
22   - def create_superuser(self, email, password):
23   - """
24   - Creates and saves a superuser with the given email and password.
25   - """
26   - user = self.create_user(email, password=password)
27   - user.is_staff = True
28   - user.save(using=self._db)
29   - return user
30   -
31   -
32   -class User(AbstractBaseUser, SimpleEmailConfirmationUserMixin, ):
  9 +class User(AbstractUser, SimpleEmailConfirmationUserMixin, ):
33 10 USERNAME_FIELD = 'email'
34 11 REQUIRED_FIELDS = []
35   -
36   - objects = UserManager()
37   - is_staff = BooleanField(default=False)
38   -
39   - email = EmailField(
40   - verbose_name='email address',
41   - max_length=255,
42   - unique=True,
43   - )
44   - date_joined = DateTimeField(auto_now_add=True)
45 12 sections = ManyToManyField('Section')
46 13  
47 14  
... ... @@ -52,7 +19,7 @@
52 19 2. A user used to have a flashcard in their deck
53 20 3. A user has a flashcard hidden from them
54 21 """
55   - user = ForeignKey(User)
  22 + user = ForeignKey('User')
56 23 mask = ForeignKey('FlashcardMask', help_text="A mask which overrides the card's mask")
57 24 pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card")
58 25 flashcard = ForeignKey('Flashcard')
flashcards/serializers.py View file @ e807903
... ... @@ -18,11 +18,10 @@
18 18  
19 19  
20 20 class UserSerializer(HyperlinkedModelSerializer):
21   - """
22   - """
23 21 email = EmailField(required=False)
  22 + sections = HyperlinkedRelatedField(queryset=Section.objects.all(), many=True, view_name='section-detail')
24 23  
25 24 class Meta:
26 25 model = User
27   - fields = ("email", "is_active", "last_login", "date_joined")
  26 + fields = ("sections", "email", "is_active", "last_login", "date_joined")
flashy/settings.py View file @ e807903
... ... @@ -56,6 +56,10 @@
56 56 )
57 57  
58 58 ROOT_URLCONF = 'flashy.urls'
  59 +# Authentication backends
  60 +AUTHENTICATION_BACKENDS = (
  61 + 'django.contrib.auth.backends.ModelBackend',
  62 +)
59 63  
60 64 TEMPLATES = [
61 65 {
flashy/urls.py View file @ e807903
... ... @@ -9,7 +9,9 @@
9 9 router.register(r'lectureperiods', LecturePeriodViewSet)
10 10  
11 11 urlpatterns = [
12   - url(r'^api/user/me$', UserDetail.as_view()),
  12 + url(r'^api/users/me$', UserDetail.as_view()),
  13 + url(r'^api/login$', UserLogin.as_view()),
  14 + url(r'^api/reset_password$', PasswordReset.as_view()),
13 15 url(r'^api/', include(router.urls)),
14 16 url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
15 17 url(r'^admin/', include(admin.site.urls)),