Skip to content

Commit aaf5f66

Browse files
author
Emmanouil Konstantinidis
committed
Tests, tests, tests
1 parent a46f82b commit aaf5f66

File tree

6 files changed

+221
-22
lines changed

6 files changed

+221
-22
lines changed

tests/models.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import uuid
2+
from django.db import models
3+
from django.contrib.auth.models import AbstractBaseUser
4+
5+
6+
class User(AbstractBaseUser):
7+
created = models.DateTimeField(auto_now_add=True, db_index=True)
8+
modified = models.DateTimeField(auto_now=True)
9+
10+
email = models.EmailField(unique=True, verbose_name='email address', max_length=255)
11+
full_name = models.CharField(max_length=255)
12+
13+
is_active = models.BooleanField(default=False)
14+
is_admin = models.BooleanField(default=False)
15+
16+
USERNAME_FIELD = 'email'
17+
REQUIRED_FIELDS = ['email', 'full_name']
18+
19+
20+
class Organisation(models.Model):
21+
22+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
23+
created = models.DateTimeField(auto_now_add=True)
24+
modified = models.DateTimeField(auto_now=True)
25+
26+
name = models.CharField(unique=True, max_length=100)
27+
slug = models.SlugField(unique=True, null=True, blank=True)
28+
members = models.ManyToManyField(User, through='Membership', through_fields=('organisation', 'user'))
29+
30+
is_active = models.BooleanField(default=False)
31+
32+
33+
class Membership(models.Model):
34+
35+
class Meta:
36+
unique_together = ("organisation", "user")
37+
38+
MEMBER_ROLES = (
39+
("ADMIN", "Admin"),
40+
("USER", "User")
41+
)
42+
43+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
44+
joined = models.DateTimeField(auto_now_add=True)
45+
46+
organisation = models.ForeignKey(Organisation)
47+
user = models.ForeignKey(User)
48+
role = models.CharField(choices=MEMBER_ROLES, max_length=20, default="USER")
49+
is_owner = models.BooleanField(default=False)

tests/serializers.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from rest_framework import serializers
2+
from tests.models import User, Organisation, Membership
3+
4+
5+
class UserRegistrationSerializer(serializers.ModelSerializer):
6+
7+
class Meta:
8+
model = User
9+
fields = ('email', 'full_name', 'password',)
10+
extra_kwargs = {'password': {'write_only': True}}
11+
12+
13+
class UserProfileSerializer(serializers.ModelSerializer):
14+
15+
class Meta:
16+
model = User
17+
fields = ('email', 'full_name', 'password', 'is_active')
18+
extra_kwargs = {
19+
'password': {'write_only': True}
20+
}
21+
read_only_fields = ('is_active',)
22+
23+
24+
class ResetPasswordSerializer(serializers.ModelSerializer):
25+
26+
id = serializers.CharField()
27+
token = serializers.CharField()
28+
29+
class Meta:
30+
model = User
31+
fields = ('id', 'token', 'password',)
32+
extra_kwargs = {'password': {'write_only': True}}
33+
34+
35+
class CreateOrganisationSerializer(serializers.ModelSerializer):
36+
37+
class Meta:
38+
model = Organisation
39+
fields = ('name', 'slug',)
40+
41+
42+
class OrganisationMembersSerializer(serializers.ModelSerializer):
43+
user = serializers.SerializerMethodField()
44+
45+
class Meta:
46+
model = Membership
47+
fields = ('joined', 'user', 'is_owner', 'role')
48+
49+
def get_user(self, obj):
50+
serializer = UserProfileSerializer(obj.user)
51+
return serializer.data
52+
53+
54+
class OrganisationDetailSerializer(serializers.ModelSerializer):
55+
56+
class Meta:
57+
model = Organisation
58+
fields = ('name', 'slug', 'is_active')

tests/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
ROOT_URLCONF = 'tests.urls'
2727

28+
REST_FRAMEWORK_DOCS = {
29+
'HIDDEN': False
30+
}
31+
2832
# Static files (CSS, JavaScript, Images)
2933
# https://docs.djangoproject.com/en/1.8/howto/static-files/
3034

tests/tests.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
from django.core.urlresolvers import reverse
2-
from django.test import TestCase
2+
from django.test import TestCase, override_settings
33

44

5-
class SimpleTest(TestCase):
6-
7-
def test_details(self):
8-
self.assertEqual(1, 1)
5+
class DRFDocsViewTests(TestCase):
96

7+
SETTINGS_HIDE_DOCS = {
8+
'HIDDEN': True # Default: False
9+
}
1010

11-
class DRFDocsViewTests(TestCase):
1211
def setUp(self):
1312
super(DRFDocsViewTests, self).setUp()
1413

15-
def test_index_view_with_no_questions(self):
14+
def test_index_view_with_endpoints(self):
1615
"""
17-
Should load the drf focs view
16+
Should load the drf focs view with all the endpoints.
17+
NOTE: Views that do **not** inherit from DRF's "APIView" are not included.
1818
"""
1919
response = self.client.get(reverse('drfdocs'))
2020

21-
# print()
22-
# print()
23-
# print(response)
24-
# print(response["context"])
25-
# print(response.context)
26-
# print(dir(response))
27-
# print()
28-
# print()
29-
3021
self.assertEqual(response.status_code, 200)
31-
self.assertEqual(response.context["endpoints"], [])
32-
# self.assertContains(response, "No polls are available.")
33-
# self.assertQuerysetEqual(response.context['latest_question_list'], [])
22+
self.assertEqual(len(response.context["endpoints"]), 8)
23+
24+
# Test the login view
25+
self.assertEqual(response.context["endpoints"][0].name_parent, "accounts")
26+
self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS'])
27+
self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/")
28+
self.assertEqual(len(response.context["endpoints"][0].fields), 2)
29+
self.assertEqual(response.context["endpoints"][0].fields[0]["type"], "CharField")
30+
self.assertTrue(response.context["endpoints"][0].fields[0]["required"])
31+
32+
@override_settings(REST_FRAMEWORK_DOCS=SETTINGS_HIDE_DOCS)
33+
def test_index_view_docs_hidden(self):
34+
"""
35+
Should prevent the docs from loading the "HIDDEN" is set
36+
to "False" in settings
37+
"""
38+
response = self.client.get(reverse('drfdocs'))
39+
40+
self.assertEqual(response.status_code, 404)
41+
self.assertEqual(response.reason_phrase, "NOT FOUND")

tests/urls.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@
44

55
accounts_urls = [
66
url(r'^login/$', views.LoginView.as_view(), name="login"),
7+
url(r'^register/$', views.UserRegistrationView.as_view(), name="register"),
8+
url(r'^reset-password/$', view=views.PasswordResetView.as_view(), name="reset-password"),
9+
url(r'^reset-password/confirm/$', views.PasswordResetConfirmView.as_view(), name="reset-password-confirm"),
10+
11+
url(r'^user/profile/$', views.UserProfileView.as_view(), name="profile"),
12+
13+
url(r'^test/$', views.TestView.as_view(), name="test-view"),
714
]
815

916
organisations_urls = [
10-
17+
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
18+
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
19+
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave")
1120
]
1221

1322
urlpatterns = [

tests/views.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from django.shortcuts import get_object_or_404
12
from django.views.generic.base import TemplateView
2-
from rest_framework import parsers, renderers
3+
from rest_framework import parsers, renderers, generics, status
34
from rest_framework.authtoken.models import Token
45
from rest_framework.authtoken.serializers import AuthTokenSerializer
6+
from rest_framework.permissions import AllowAny
57
from rest_framework.response import Response
68
from rest_framework.views import APIView
9+
from tests.models import User, Organisation, Membership
10+
from tests import serializers
711

812

913
class TestView(TemplateView):
@@ -27,3 +31,70 @@ def post(self, request):
2731
user = serializer.validated_data['user']
2832
token, created = Token.objects.get_or_create(user=user)
2933
return Response({'token': token.key})
34+
35+
36+
class UserRegistrationView(generics.CreateAPIView):
37+
38+
permission_classes = (AllowAny,)
39+
serializer_class = serializers.UserRegistrationSerializer
40+
41+
42+
class UserProfileView(generics.RetrieveUpdateAPIView):
43+
44+
serializer_class = serializers.UserProfileSerializer
45+
46+
def get_object(self):
47+
return self.request.user
48+
49+
50+
class PasswordResetView(APIView):
51+
52+
permission_classes = (AllowAny,)
53+
queryset = User.objects.all()
54+
55+
def get_object(self):
56+
email = self.request.data.get('email')
57+
obj = get_object_or_404(self.queryset, email=email)
58+
return obj
59+
60+
def post(self, request, *args, **kwargs):
61+
user = self.get_object()
62+
user.send_reset_password_email()
63+
return Response({}, status=status.HTTP_200_OK)
64+
65+
66+
class PasswordResetConfirmView(APIView):
67+
68+
permission_classes = (AllowAny,)
69+
serializer_class = serializers.ResetPasswordSerializer
70+
71+
def post(self, request, *args, **kwargs):
72+
serializer = serializers.ResetPasswordSerializer(data=request.data)
73+
if not serializer.is_valid():
74+
return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
75+
return Response({"msg": "Password updated successfully."}, status=status.HTTP_200_OK)
76+
77+
78+
class CreateOrganisationView(generics.CreateAPIView):
79+
80+
serializer_class = serializers.CreateOrganisationSerializer
81+
82+
83+
class OrganisationMembersView(generics.ListAPIView):
84+
85+
serializer_class = serializers.OrganisationMembersSerializer
86+
87+
def get_queryset(self):
88+
organisation = Organisation.objects.order_by('?').first()
89+
return Membership.objects.filter(organisation=organisation)
90+
91+
92+
class LeaveOrganisationView(generics.DestroyAPIView):
93+
94+
def get_object(self):
95+
return Membership.objects.order_by('?').first()
96+
97+
def delete(self, request, *args, **kwargs):
98+
instance = self.get_object()
99+
self.perform_destroy(instance)
100+
return Response(status=status.HTTP_204_NO_CONTENT)

0 commit comments

Comments
 (0)