--- a/.hgignore Mon Jul 17 14:13:32 2017 +0200
+++ b/.hgignore Tue Jul 18 17:08:27 2017 +0200
@@ -36,3 +36,5 @@
^design/Gemfile
.ruby-version$
+
+^design/api/node_modules
--- a/src/notes/__init__.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/__init__.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,1 @@
+default_app_config = 'notes.apps.NotesConfig'
--- a/src/notes/api/permissions/__init__.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/permissions/__init__.py Tue Jul 18 17:08:27 2017 +0200
@@ -2,5 +2,6 @@
Permissions classes fro notes
"""
from .core import SessionPermission, NotePermission
+from .auth import GroupPermission
-__all__ = ["SessionPermission", "NotePermission"]
+__all__ = ["SessionPermission", "NotePermission", "GroupPermission"]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes/api/permissions/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,25 @@
+"""
+Permissions for auth objects
+"""
+import logging
+
+from rest_framework import permissions
+from rest_framework.permissions import BasePermission
+from rest_framework.compat import is_authenticated
+
+logger = logging.getLogger(__name__)
+
+
+class GroupPermission(BasePermission):
+ """
+ Pemissions for Groups objects
+ """
+
+ def has_permission(self, request, view):
+ return request.user and is_authenticated(request.user)
+
+
+ def has_object_permission(self, request, view, obj):
+ if request.method not in permissions.SAFE_METHODS:
+ return request.user == obj.profile.owner
+ return True
--- a/src/notes/api/permissions/core.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/permissions/core.py Tue Jul 18 17:08:27 2017 +0200
@@ -17,7 +17,6 @@
def has_object_permission(self, request, view, obj):
return request.user == obj.owner
-
class NotePermission(IsAuthenticated):
"""
Permissions for notes
--- a/src/notes/api/serializers/auth.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/serializers/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -1,11 +1,82 @@
import logging
+from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from rest_framework import serializers
logger = logging.getLogger(__name__)
+User = get_user_model()
+
class GroupSerializer(serializers.ModelSerializer):
+ owner = serializers.CharField(source='profile.owner.username', read_only=True)
+ description = serializers.CharField(source='profile.description')
+
+ class Meta:
+ model = Group
+ fields = ['name', 'owner', 'description']
+
+
+class DetailGroupSerializer(GroupSerializer):
+ users = serializers.SlugRelatedField(
+ many=True,
+ read_only=True,
+ slug_field='username',
+ source='user_set'
+ )
+
class Meta:
model = Group
- fields = '__all__'
+ fields = ['name', 'owner', 'description', 'users']
+
+class WriteGroupSerializer(serializers.ModelSerializer):
+ '''
+ Serializers for writing groups.
+ '''
+
+ description = serializers.CharField(source='profile.description')
+ users = serializers.SlugRelatedField(
+ many=True,
+ slug_field='username',
+ source='user_set',
+ queryset=User.objects.all(),
+ default=[]
+ )
+
+ class Meta:
+ model = Group
+ fields = ['name', 'description', 'users']
+
+
+ def create(self, validated_data):
+ profile_data = validated_data.pop('profile', None)
+
+ group = super().create(validated_data)
+
+ if profile_data is not None:
+ group.profile.description = profile_data.get('description')
+
+ user = None
+ request = self.context.get("request")
+ if request and hasattr(request, "user"):
+ user = request.user
+ group.user_set.add(user)
+ group.save()
+ group.profile.owner = user
+ group.profile.save()
+
+ return group
+
+ def update(self, instance, validated_data):
+ profile_data = validated_data.pop('profile', None)
+ group = super().update(instance, validated_data)
+
+ if profile_data is not None:
+ group.profile.description = profile_data.get('description')
+ group.profile.save()
+
+ # check that owner is still in user list
+ if group.profile.owner and group.profile.owner not in group.user_set.all():
+ group.user_set.add(group.profile.owner)
+
+ return group
--- a/src/notes/api/serializers/core.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/serializers/core.py Tue Jul 18 17:08:27 2017 +0200
@@ -88,6 +88,7 @@
owner = serializers.SlugRelatedField(
read_only=True, slug_field='username', default=serializers.CurrentUserDefault())
+
class Meta:
model = Session
fields = (
--- a/src/notes/api/views/auth.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/views/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -3,13 +3,25 @@
from django.contrib.auth.models import Group
from rest_framework import viewsets
-from ..serializers.auth import (GroupSerializer)
+from ..serializers.auth import (GroupSerializer, WriteGroupSerializer, DetailGroupSerializer)
+from ..permissions.auth import (GroupPermission, )
logger = logging.getLogger(__name__)
class GroupViewSet(viewsets.ModelViewSet):
serializer_class = GroupSerializer
- permission_classes = ()
+ permission_classes = (GroupPermission, )
+ lookup_field = 'name'
+
def get_queryset(self):
return Group.objects.all()
+
+ serializers = {
+ 'create': WriteGroupSerializer,
+ 'update': WriteGroupSerializer,
+ 'retrieve': DetailGroupSerializer,
+ }
+
+ def get_serializer_class(self):
+ return self.serializers.get(self.action, GroupSerializer)
--- a/src/notes/api/views/core.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/api/views/core.py Tue Jul 18 17:08:27 2017 +0200
@@ -1,11 +1,15 @@
+"""
+Core viewsets
+"""
import logging
from notes.models import Note, Session
-from rest_framework import viewsets, serializers
+from rest_framework import viewsets
from ..permissions import NotePermission, SessionPermission
-from ..serializers.core import (DetailNoteSerializer, UpdateNoteSerializer, DetailSessionSerializer, CreateNoteSerializer,
- ListNoteSerializer, ListSessionSerializer, CreateSessionSerializer)
+from ..serializers.core import (DetailNoteSerializer, UpdateNoteSerializer, DetailSessionSerializer,
+ CreateNoteSerializer, ListNoteSerializer, ListSessionSerializer,
+ CreateSessionSerializer)
logger = logging.getLogger(__name__)
--- a/src/notes/apps.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/apps.py Tue Jul 18 17:08:27 2017 +0200
@@ -3,3 +3,7 @@
class NotesConfig(AppConfig):
name = 'notes'
+
+ def ready(self):
+ # import signal handlers
+ import notes.signals
--- a/src/notes/migrations/0001_initial.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/migrations/0001_initial.py Tue Jul 18 17:08:27 2017 +0200
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.11.2 on 2017-06-21 10:50
+# Generated by Django 1.11.2 on 2017-07-07 09:59
from __future__ import unicode_literals
import colorful.fields
@@ -66,7 +66,8 @@
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(blank=True, null=True)),
- ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
+ ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to='auth.Group')),
+ ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'GroupProfile',
--- a/src/notes/models/auth.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/models/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -11,17 +11,22 @@
verbose_name = _('User')
verbose_name_plural = _('Users')
+
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
+
class Meta:
verbose_name = _('UserProfile')
verbose_name_plural = _('UserProfiles')
class GroupProfile(models.Model):
- group = models.OneToOneField(Group, unique=True, on_delete=models.CASCADE)
+ group = models.OneToOneField(
+ Group, unique=True, on_delete=models.CASCADE, related_name='profile')
description = models.TextField(null=True, blank=True)
+ # TODO: manage when user is deleted: put first user as owner. delete group if empty
+ owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
+
class Meta:
verbose_name = _('GroupProfile')
verbose_name_plural = _('GroupProfiles')
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes/signals.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,15 @@
+"""
+Signals for notes app
+"""
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from django.contrib.auth.models import Group
+from notes.models import GroupProfile
+
+@receiver(post_save, sender=Group, dispatch_uid="group_created_signal")
+def group_saved_callback(sender, instance, **kwargs):
+ created = kwargs.pop('created')
+ if instance and created:
+ profile = GroupProfile(group=instance)
+ profile.save()
+
--- a/src/notes/tests/__init__.py Mon Jul 17 14:13:32 2017 +0200
+++ b/src/notes/tests/__init__.py Tue Jul 18 17:08:27 2017 +0200
@@ -1,1 +1,2 @@
from .api import SessionApiTests, NoteApiTests
+from .models import AuthModelTests
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes/tests/api/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,137 @@
+"""
+Tests the core api for sessions
+"""
+import logging
+from uuid import uuid4
+
+from django.contrib.auth import get_user_model
+from django.urls import reverse
+from django.utils import timezone
+from rest_framework import status
+from rest_framework.test import APITransactionTestCase
+from django.contrib.auth.models import Group
+
+logger = logging.getLogger(__name__)
+
+
+class GroupApiTests(APITransactionTestCase):
+
+ def setUp(self):
+ User = get_user_model()
+ self.user1 = User.objects.create_user(
+ username='test_user1',
+ email='test_user@emial.com',
+ password='top_secret'
+ )
+ self.user2 = User.objects.create_user(
+ username='test_user2',
+ email='test_user@emial.com',
+ password='top_secret'
+ )
+
+ self.group1 = Group(name='group1')
+ self.group1.save()
+ self.group1.user_set.add(self.user1)
+ self.group1.profile.owner = self.user1
+ self.group1.profile.description = "This is the group 1"
+ self.group1.profile.save()
+
+ self.group2 = Group(name='group2')
+ self.group2.save()
+ self.group2.profile.owner = self.user2
+ self.group2.profile.description = "This is the group 2"
+ self.group2.profile.save()
+
+
+ def test_list_group_no_login(self):
+ url = reverse('auth_group-list')
+ response = self.client.get(url)
+ logger.debug("LIST group response %r", response.data)
+
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+
+ def test_list_group(self):
+ url = reverse('auth_group-list')
+ self.client.login(username='test_user1', password='top_secret')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ self.assertEqual(2, len(response.data), "Must have 2 groups")
+ for group_def in response.data:
+ self.assertIn('name', group_def.keys())
+ self.assertIn('owner', group_def.keys())
+ self.assertIn('description', group_def.keys())
+ self.assertIn(group_def.get('owner'), ['test_user1', 'test_user2'])
+ self.assertIn(group_def.get('description'), ['This is the group 1', 'This is the group 2'])
+ self.assertIn(group_def.get('name'), ['group1', 'group2'])
+
+ def test_create_group(self):
+ url = reverse('auth_group-list')
+ self.client.login(username='test_user1', password='top_secret')
+ response = self.client.post(
+ url,
+ {'name':"group3", 'description': "this is group 3", 'users': ['test_user2']})
+
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ self.assertEqual('group3', response.data.get('name'))
+ self.assertSetEqual(set(['test_user1', 'test_user2']), set(response.data.get('users')))
+
+ group3 = Group.objects.get(name='group3')
+ self.assertEqual('this is group 3', group3.profile.description)
+ self.assertSetEqual(
+ set(['test_user1', 'test_user2']),
+ set(map(lambda u: u.username, group3.user_set.all()))
+ )
+
+ def test_create_group_no_user(self):
+ url = reverse('auth_group-list')
+ self.client.login(username='test_user1', password='top_secret')
+ response = self.client.post(
+ url,
+ {'name':"group3", 'description': "this is group 3"})
+
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ self.assertEqual('group3', response.data.get('name'))
+ self.assertSetEqual(set(['test_user1']), set(response.data.get('users')))
+
+ group3 = Group.objects.get(name='group3')
+ self.assertEqual('this is group 3', group3.profile.description)
+ self.assertSetEqual(
+ set(['test_user1']),
+ set(map(lambda u: u.username, group3.user_set.all()))
+ )
+
+
+ def test_detail_group(self):
+ url = reverse('auth_group-detail', kwargs={'name': self.group1.name})
+ self.client.login(username='test_user1', password='top_secret')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual('group1', response.data.get('name'))
+ self.assertEqual('This is the group 1', response.data.get('description'))
+ self.assertSetEqual(set(['test_user1']), set(response.data.get('users')))
+
+ def test_update_group(self):
+ url = reverse('auth_group-detail', kwargs={'name': self.group1.name})
+ self.client.login(username='test_user1', password='top_secret')
+ response = self.client.put(
+ url,
+ {'name': 'group1', 'description': "this is group 1 changed", 'users': ['test_user2']})
+
+ logger.debug("RESPONSE %r", response.data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual('group1', response.data.get('name'))
+ self.assertEqual('this is group 1 changed', response.data.get('description'))
+ self.assertSetEqual(set(['test_user1', 'test_user2']), set(response.data.get('users')))
+
+ group1 = Group.objects.get(name='group1')
+ self.assertEqual('this is group 1 changed', group1.profile.description)
+ self.assertSetEqual(
+ set(['test_user1', 'test_user2']),
+ set(map(lambda u: u.username, group1.user_set.all()))
+ )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes/tests/models/__init__.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,3 @@
+from .auth import AuthModelTests
+
+__all__ = ['AuthModelTests']
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes/tests/models/auth.py Tue Jul 18 17:08:27 2017 +0200
@@ -0,0 +1,34 @@
+"""
+Tests the core models for auth
+"""
+import logging
+from uuid import uuid4
+
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+from django.test import TransactionTestCase
+
+logger = logging.getLogger(__name__)
+
+
+class AuthModelTests(TransactionTestCase):
+
+ def setUp(self):
+ User = get_user_model()
+ self.user1 = User.objects.create_user(
+ username='test_user1',
+ email='test_user@emial.com',
+ password='top_secret'
+ )
+ self.user2 = User.objects.create_user(
+ username='test_user2',
+ email='test_user@emial.com',
+ password='top_secret'
+ )
+ self.group = Group(name='group1')
+ self.group.save()
+
+ def test_create_profile(self):
+ self.assertIsNotNone(self.group)
+ self.assertIsNotNone(self.group.profile)
+