Skip to content

Commit

Permalink
Merge pull request #18 from ProjetSigma/feature/110-group-creation
Browse files Browse the repository at this point in the history
Feature/110 group creation
  • Loading branch information
tizot committed Feb 1, 2016
2 parents 14185c0 + d0a27bd commit 203d730
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 18 deletions.
2 changes: 2 additions & 0 deletions sigma/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

from sigma_core.views.user import UserViewSet
from sigma_core.views.group import GroupViewSet
from sigma_core.views.school import SchoolViewSet
from sigma_core.views.group_user import GroupUserViewSet
from sigma_core.views.group_member import GroupMemberViewSet

router = routers.DefaultRouter()

router.register(r'user', UserViewSet)
router.register(r'group', GroupViewSet)
router.register(r'school', SchoolViewSet)
router.register(r'group-member', GroupMemberViewSet)

urlpatterns = [
Expand Down
5 changes: 4 additions & 1 deletion sigma_core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
from django.contrib.auth.models import Group as AuthGroup

from sigma_core.models.user import User
from sigma_core.models.group import Group
from sigma_core.models.group import Group, GroupAcknowledgment
from sigma_core.models.school import School
from sigma_core.models.group_member import GroupMember


admin.site.unregister(AuthGroup)
admin.site.register(User)
admin.site.register(Group)
admin.site.register(School)
admin.site.register(GroupAcknowledgment)
admin.site.register(GroupMember)
24 changes: 24 additions & 0 deletions sigma_core/migrations/0010_school.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-26 13:41
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('sigma_core', '0009_auto_20160111_0057'),
]

operations = [
migrations.CreateModel(
name='School',
fields=[
('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sigma_core.Group')),
('design', models.CharField(max_length=255)),
],
bases=('sigma_core.group',),
),
]
27 changes: 27 additions & 0 deletions sigma_core/migrations/0011_schoolgroup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-27 14:24
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('sigma_core', '0010_school'),
]

operations = [
migrations.CreateModel(
name='SchoolGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('validated', models.BooleanField(default=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_schools', to='sigma_core.Group')),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='school_groups', to='sigma_core.School')),
],
),
]
51 changes: 51 additions & 0 deletions sigma_core/migrations/0012_auto_20160127_1848.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-27 17:48
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('sigma_core', '0011_schoolgroup'),
]

operations = [
migrations.CreateModel(
name='GroupAcknowledgment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('validated', models.BooleanField(default=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
],
),
migrations.RemoveField(
model_name='schoolgroup',
name='group',
),
migrations.RemoveField(
model_name='schoolgroup',
name='school',
),
migrations.AddField(
model_name='group',
name='resp_school',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sigma_core.School'),
),
migrations.DeleteModel(
name='SchoolGroup',
),
migrations.AddField(
model_name='groupacknowledgment',
name='asking_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_recognizers', to='sigma_core.Group'),
),
migrations.AddField(
model_name='groupacknowledgment',
name='validator_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_acknowledgments', to='sigma_core.Group'),
),
]
54 changes: 47 additions & 7 deletions sigma_core/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@


class Group(models.Model):
class Meta:
pass

#########################
# Constants and choices #
#########################
ADMINISTRATOR_RANK = 10

VIS_PUBLIC = 'public'
Expand All @@ -34,10 +34,16 @@ class Meta:
(TYPE_SCHOOL, 'School')
)

##########
# Fields #
##########
name = models.CharField(max_length=254)
visibility = models.CharField(max_length=64, choices=VISIBILITY_CHOICES, default=VIS_PRIVATE)
type = models.CharField(max_length=64, choices=TYPE_CHOICES, default=TYPE_BASIC)

# The school responsible of the group in case of admin conflict (can be null for non-school-related groups)
resp_school = models.ForeignKey('School', null=True, blank=True, on_delete=models.SET_NULL)

# The permission a member has upon joining
# A value of -1 means that no one can join the group.
# A value of 0 means that anyone can request to join the group
Expand All @@ -61,20 +67,31 @@ class Meta:

# objects = GroupManager()

@property
def acknowledged_groups(self):
return [ga.asking_group for ga in self.group_acknowledgments.filter(validated=True).select_related('asking_group')]

#################
# Model methods #
#################
def can_anyone_join(self):
return self.default_member_rank >= 0

def __str__(self):
return "%s (%s)" % (self.name, self.get_type_display())

###############
# Permissions #
###############

# Perms for admin site
def has_perm(self, perm, obj=None):
return True

def has_module_perms(self, app_label):
return True

# Permissions
# DRY Permissions
@staticmethod
def has_read_permission(request):
"""
Expand All @@ -98,12 +115,21 @@ def has_create_permission(request):
"""
Everybody can create a private group. For other types, user must be school admin or sigma admin.
"""
#TODO: Adapt after School model implementation.
from sigma_core.models.school import School
group_type = request.data.get('type', None)
return group_type == Group.TYPE_BASIC
if group_type == Group.TYPE_BASIC:
return True

resp_school = request.data.get('resp_school', None)
try:
school = School.objects.get(pk=resp_school)
except School.DoesNotExist:
school = None
return school is not None and request.user.has_group_admin_perm(school)

@allow_staff_or_superuser
def has_object_write_permission(self, request):
return False
return request.user.has_group_admin_perm(self)

@allow_staff_or_superuser
def has_object_update_permission(self, request):
Expand All @@ -115,3 +141,17 @@ def has_object_update_permission(self, request):
@allow_staff_or_superuser
def has_object_invite_permission(self, request):
return request.user.can_invite(self)


class GroupAcknowledgment(models.Model):
asking_group = models.ForeignKey(Group, related_name='group_recognizers')
validator_group = models.ForeignKey(Group, related_name='group_acknowledgments')
validated = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

def __str__(self):
if self.validated:
return "Group %s acknowledged by Group %s" % (self.asking_group.__str__(), self.validator_group.__str__())
else:
return "Group %s awaiting for acknowledgment by Group %s since %s" % (self.asking_group.__str__(), self.validator_group.__str__(), self.created.strftime("%Y-%m-%d %H:%M"))
48 changes: 48 additions & 0 deletions sigma_core/models/school.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.db import models

from dry_rest_permissions.generics import allow_staff_or_superuser

from sigma_core.models.group import Group


class School(Group):
design = models.CharField(max_length=255)

def save(self, *args, **kwargs):
"""
Schools are special groups: some params cannot be specified by user.
"""
self.visibility = Group.VIS_PUBLIC
self.type = Group.TYPE_SCHOOL
self.default_member_rank = -1
self.req_rank_invite = Group.ADMINISTRATOR_RANK
self.req_rank_kick = Group.ADMINISTRATOR_RANK
self.req_rank_accept_join_requests = Group.ADMINISTRATOR_RANK
self.req_rank_promote = Group.ADMINISTRATOR_RANK
self.req_rank_demote = Group.ADMINISTRATOR_RANK
self.req_rank_modify_group_infos = Group.ADMINISTRATOR_RANK
self.resp_school = None

return super(School, self).save(*args, **kwargs)

# Permissions
@staticmethod
def has_read_permission(request):
"""
Schools list is visible by everybody.
"""
return True

def has_object_read_permission(self, request):
"""
Schools are only visible by members.
"""
return request.user.is_group_member(self)

@staticmethod
@allow_staff_or_superuser
def has_create_permission(request):
"""
Schools can be created by Sigma admin only.
"""
return False
16 changes: 16 additions & 0 deletions sigma_core/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ def get_short_name(self):
def is_sigma_admin(self):
return self.is_staff or self.is_superuser

def get_group_membership(self, group):
from sigma_core.models.group_member import GroupMember
from sigma_core.models.group import Group
try:
return self.memberships.get(group=group)
except GroupMember.DoesNotExist:
return None

def is_group_member(self, g):
from sigma_core.models.group_member import GroupMember
try:
Expand Down Expand Up @@ -111,6 +119,14 @@ def can_modify_group_infos(self, group):
return False
return mem.perm_rank >= group.req_rank_modify_group_infos

def has_group_admin_perm(self, group):
from sigma_core.models.group_member import GroupMember
from sigma_core.models.group import Group
if self.is_sigma_admin():
return True
mem = self.get_group_membership(group)
return mem is not None and mem.perm_rank == Group.ADMINISTRATOR_RANK


# Perms for admin site
def has_perm(self, perm, obj=None):
Expand Down
1 change: 1 addition & 0 deletions sigma_core/serializers/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ class GroupSerializer(BasicGroupSerializer):
class Meta:
model = Group

acknowledged_groups = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
memberships = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
22 changes: 22 additions & 0 deletions sigma_core/serializers/school.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from rest_framework import serializers

from sigma_core.models.school import School


class BasicSchoolSerializer(serializers.ModelSerializer):
"""
Serialize School model without memberships.
"""
class Meta:
model = School


class SchoolSerializer(BasicSchoolSerializer):
"""
Serialize School model with memberships.
"""
class Meta:
model = School

acknowledged_groups = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
memberships = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
9 changes: 9 additions & 0 deletions sigma_core/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from sigma_core.models.user import User
from sigma_core.models.group import Group
from sigma_core.models.school import School
from sigma_core.models.group_member import GroupMember

faker = FakerFactory.create('fr_FR')
Expand All @@ -30,6 +31,14 @@ class Meta:
name = factory.Sequence(lambda n: 'Group %d' % n)


class SchoolFactory(factory.django.DjangoModelFactory):
class Meta:
model = School

name = factory.Sequence(lambda n: 'School %d' % n)
design = "default"


class GroupMemberFactory(factory.django.DjangoModelFactory):
class Meta:
model = GroupMember
Expand Down
Loading

0 comments on commit 203d730

Please sign in to comment.