Skip to content

Commit

Permalink
PLAT-165: make m2m relations between CoreGroup and WorkflowLevel1/2
Browse files Browse the repository at this point in the history
  • Loading branch information
docktorrr committed Apr 1, 2019
1 parent 6c078be commit d4dd68a
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 200 deletions.
5 changes: 2 additions & 3 deletions workflow/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ class OrganizationAdmin(admin.ModelAdmin):


class CoreGroupAdmin(admin.ModelAdmin):
list_display = ('name', 'workflowlevel1', 'workflowlevel2')
list_display = ('name',)
display = 'Core Group'
list_filter = ('workflowlevel1', 'workflowlevel2',)
search_fields = ('name', 'workflowlevel1', 'workflowlevel2',)
search_fields = ('name',)


class CoreUserAdmin(admin.ModelAdmin):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Generated by Django 2.0.7 on 2019-03-28 12:31
# Generated by Django 2.0.7 on 2019-04-01 14:02

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


class Migration(migrations.Migration):
Expand All @@ -25,14 +24,14 @@ class Migration(migrations.Migration):
name='roles',
),
migrations.AddField(
model_name='coregroup',
name='workflowlevel1',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflow.WorkflowLevel1'),
model_name='workflowlevel1',
name='core_groups',
field=models.ManyToManyField(blank=True, related_name='workflowlevel1s', related_query_name='workflowlevel1s', to='workflow.CoreGroup', verbose_name='Core groups'),
),
migrations.AddField(
model_name='coregroup',
name='workflowlevel2',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflow.WorkflowLevel2'),
model_name='workflowlevel2',
name='core_groups',
field=models.ManyToManyField(blank=True, related_name='workflowlevel2s', related_query_name='workflowlevel2s', to='workflow.CoreGroup', verbose_name='Core groups'),
),
migrations.AlterField(
model_name='coregroup',
Expand Down
101 changes: 36 additions & 65 deletions workflow/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,49 @@ def __str__(self):
return self.name


TITLE_CHOICES = (
('mr', 'Mr.'),
('mrs', 'Mrs.'),
('ms', 'Ms.'),
)
class CoreGroup(models.Model):
"""
CoreGroup model defines the groups of the users with specific permissions for the set of workflowlevel1's
and workflowlevel2's (it has many-to-many relationship to WorkFlowLevel1 and WorkFlowLevel2 models).
Permissions field is the decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions
for CRUD. For example: 12 -> 1100 -> CR__ (allowed to Create and Read).
"""
uuid = models.CharField('CoreGroup UUID', max_length=255, default=uuid.uuid4, unique=True)
name = models.CharField('Name of the role', max_length=80)
permissions = models.PositiveSmallIntegerField('Permissions', default=4, help_text='Decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD')
create_date = models.DateTimeField(default=timezone.now)
edit_date = models.DateTimeField(null=True, blank=True)

class Meta:
ordering = ('name',)

def __str__(self):
return self.name

def save(self, *args, **kwargs):
self.edit_date = timezone.now()
super(CoreGroup, self).save(*args, **kwargs)

@property
def display_permissions(self) -> str:
return '{0:04b}'.format(self.permissions if self.permissions < 16 else 15)


class CoreUser(AbstractUser):
"""
CoreUser is the registered user who belongs to some organization and can manage its projects.
"""
TITLE_CHOICES = (
('mr', 'Mr.'),
('mrs', 'Mrs.'),
('ms', 'Ms.'),
)

core_user_uuid = models.CharField(max_length=255, verbose_name='CoreUser UUID', default=uuid.uuid4, unique=True)
title = models.CharField(blank=True, null=True, max_length=3, choices=TITLE_CHOICES)
contact_info = models.CharField(blank=True, null=True, max_length=255)
organization = models.ForeignKey(Organization, blank=True, null=True, on_delete=models.CASCADE)
core_groups = models.ManyToManyField('CoreGroup', verbose_name='User groups', blank=True, related_name='user_set', related_query_name='user')
core_groups = models.ManyToManyField(CoreGroup, verbose_name='User groups', blank=True, related_name='user_set', related_query_name='user')
privacy_disclaimer_accepted = models.BooleanField(default=False)
create_date = models.DateTimeField(default=timezone.now)
edit_date = models.DateTimeField(null=True, blank=True)
Expand Down Expand Up @@ -175,7 +202,8 @@ class WorkflowLevel1(models.Model):
end_date = models.DateTimeField(null=True, blank=True, help_text='If required a time span can be associated with workflow level')
create_date = models.DateTimeField(null=True, blank=True)
edit_date = models.DateTimeField(null=True, blank=True)
sort = models.IntegerField(default=0) #sort array
sort = models.IntegerField(default=0) # sort array
core_groups = models.ManyToManyField(CoreGroup, verbose_name='Core groups', blank=True, related_name='workflowlevel1s', related_query_name='workflowlevel1s')

class Meta:
ordering = ('name',)
Expand Down Expand Up @@ -213,6 +241,7 @@ class WorkflowLevel2(models.Model):
created_by = models.ForeignKey(CoreUser, related_name='workflowlevel2', null=True, blank=True, on_delete=models.SET_NULL)
edit_date = models.DateTimeField("Last Edit Date", null=True, blank=True)
history = HistoricalRecords()
core_groups = models.ManyToManyField(CoreGroup, verbose_name='Core groups', blank=True, related_name='workflowlevel2s', related_query_name='workflowlevel2s')

class Meta:
ordering = ('name',)
Expand All @@ -237,64 +266,6 @@ def organization(self) -> Union[Organization, None]:
return self.workflowlevel1.organization


class CoreGroupQuerySet(models.QuerySet):
def by_organization(self, org_id):
return self.filter(
models.Q(workflowlevel1__organization_id=org_id) |
models.Q(workflowlevel2__workflowlevel1__organization_id=org_id)
)


class CoreGroupManager(models.Manager):
def get_queryset(self):
return CoreGroupQuerySet(self.model, using=self._db)

def by_organization(self, org_id):
return self.get_queryset().by_organization(org_id)


class CoreGroup(models.Model):
"""
CoreGroup model defines the groups of the users with specific permissions in the context of given workflow
(it could be Workflow Level 1 or Workflow Level 2).
If there's no workflow attached than it's a global Role (such as Organization Admin)
Permissions field is the decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions
for CRUD. For example: 12 -> 1100 -> CR__ (allowed to Create and Read).
"""
uuid = models.CharField('CoreGroup UUID', max_length=255, default=uuid.uuid4, unique=True)
name = models.CharField('Name of the role', max_length=80)
workflowlevel1 = models.ForeignKey(WorkflowLevel1, null=True, blank=True, on_delete=models.CASCADE)
workflowlevel2 = models.ForeignKey(WorkflowLevel2, null=True, blank=True, on_delete=models.CASCADE)
permissions = models.PositiveSmallIntegerField('Permissions', default=4, help_text='Decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD')
create_date = models.DateTimeField(default=timezone.now)
edit_date = models.DateTimeField(null=True, blank=True)

objects = CoreGroupManager()

class Meta:
ordering = ('name',)

def __str__(self):
wf = self.workflowlevel2 or self.workflowlevel1
return f'{self.name} <{wf}>'

def save(self, *args, **kwargs):
self.edit_date = timezone.now()
super(CoreGroup, self).save(*args, **kwargs)

@property
def organization(self) -> Union[Organization, None]:
if self.workflowlevel2:
return self.workflowlevel2.organization
if self.workflowlevel1:
return self.workflowlevel1.organization
return None

@property
def display_permissions(self) -> str:
return '{0:04b}'.format(self.permissions if self.permissions < 16 else 15)


class WorkflowTeam(models.Model):
"""
WorkflowTeam defines m2m relations between CoreUser and Workflowlevel1.
Expand Down
5 changes: 5 additions & 0 deletions workflow/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ def reset_password_request(org_member):
uid = urlsafe_base64_encode(force_bytes(org_member.pk)).decode()
token = default_token_generator.make_token(org_member)
return org_member, uid, token


@pytest.fixture
def superuser():
return factories.CoreUser.create(is_superuser=True)
29 changes: 0 additions & 29 deletions workflow/tests/test_coregroupmodel.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,5 @@
import pytest
import factories
from workflow.models import CoreGroup
from .fixtures import org


@pytest.mark.django_db()
def test_coregroup_with_wfl1_organization(org):
wfl1 = factories.WorkflowLevel1.create(organization=org, name='Program')
coregroup = CoreGroup(workflowlevel1=wfl1, name='Program Admin')
coregroup.save()
created = CoreGroup.objects.get(name='Program Admin')
assert created.organization == org


@pytest.mark.django_db()
def test_coregroup_with_wfl2_organization(org):
wfl1 = factories.WorkflowLevel1.create(organization=org, name='Program')
wfl2 = factories.WorkflowLevel2.create(workflowlevel1=wfl1, name='Project')
coregroup = CoreGroup(workflowlevel2=wfl2, name='Project Admin')
coregroup.save()
created = CoreGroup.objects.get(name='Project Admin')
assert created.organization == org


@pytest.mark.django_db()
def test_coregroup_with_no_wfl():
coregroup = CoreGroup(name='Organization Admin')
coregroup.save()
created = CoreGroup.objects.get(name='Organization Admin')
assert created.organization is None


@pytest.mark.django_db()
Expand Down
102 changes: 18 additions & 84 deletions workflow/tests/test_coregroupview.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import factories
from workflow import models as wfm
from workflow.views import CoreGroupViewSet
from .fixtures import org, org_admin, org_member, group_org_admin
from .fixtures import org, org_admin, org_member, superuser


@pytest.mark.django_db()
Expand Down Expand Up @@ -66,99 +66,39 @@ def test_coregroup_views_permissions_org_member(self, request_factory, org_membe
response = CoreGroupViewSet.as_view({'delete': 'destroy'})(request, pk=1)
assert response.status_code == 403

def test_coregroup_views_permissions_different_org_admin(self, request_factory, org, group_org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org)
coregroup = factories.CoreGroup.create(workflowlevel1=wfl1)

# create admin of another organization
wfl1_another = factories.WorkflowLevel1.create(organization=factories.Organization(name='Another Org'))
coreuser = factories.CoreUser.create(workflowlevel1=wfl1_another)
coreuser.groups.add(group_org_admin)

request = request_factory.get(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = coreuser
response = CoreGroupViewSet.as_view({'get': 'retrieve'})(request, pk=coregroup.pk)
assert response.status_code == 403

request = request_factory.put(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = coreuser
response = CoreGroupViewSet.as_view({'put': 'update'})(request, pk=coregroup.pk)
assert response.status_code == 403

request = request_factory.patch(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = coreuser
response = CoreGroupViewSet.as_view({'patch': 'partial_update'})(request, pk=coregroup.pk)
assert response.status_code == 403

request = request_factory.delete(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = coreuser
response = CoreGroupViewSet.as_view({'delete': 'destroy'})(request, pk=coregroup.pk)
assert response.status_code == 403


@pytest.mark.django_db()
class TestCoreGroupCreateView:

def test_coregroup_create_fail(self, request_factory, org_admin):
def test_coregroup_create_fail(self, request_factory, superuser):
request = request_factory.post(reverse('coregroup-list'), {}, format='json')
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'post': 'create'})(request)
assert response.status_code == 400

def test_coregroup_create_min(self, request_factory, org_admin):
def test_coregroup_create_min(self, request_factory, superuser):
data = {'name': 'New Group'}
request = request_factory.post(reverse('coregroup-list'), data, format='json')
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'post': 'create'})(request)
assert response.status_code == 201
coregroup = wfm.CoreGroup.objects.get(name='New Group')
assert coregroup.organization is None
assert coregroup.permissions == 4 # check default permissions

def test_coregroup_create_with_workfllow1(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
data = {
'name': 'New Group',
'workflowlevel1': wfl1.pk,
}
request = request_factory.post(reverse('coregroup-list'), data, format='json')
request.user = org_admin
response = CoreGroupViewSet.as_view({'post': 'create'})(request)
assert response.status_code == 201
coregroup = wfm.CoreGroup.objects.get(name='New Group')
assert coregroup.workflowlevel1 == wfl1
assert coregroup.workflowlevel2 is None

def test_coregroup_create_with_workfllow2(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
wfl2 = factories.WorkflowLevel2.create(workflowlevel1=wfl1)
data = {
'name': 'New Group',
'workflowlevel2': wfl2.pk,
}
request = request_factory.post(reverse('coregroup-list'), data, format='json')
request.user = org_admin
response = CoreGroupViewSet.as_view({'post': 'create'})(request)
assert response.status_code == 201
coregroup = wfm.CoreGroup.objects.get(name='New Group')
assert coregroup.workflowlevel1 is None
assert coregroup.workflowlevel2 == wfl2


@pytest.mark.django_db()
class TestCoreGroupUpdateView:

def test_coregroup_update(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
coregroup = factories.CoreGroup.create(name='Program Admin', workflowlevel1=wfl1)
def test_coregroup_update(self, request_factory, superuser):
coregroup = factories.CoreGroup.create(name='Program Admin')

data = {
'name': 'Admin of something else',
'permissions': 9,
}

request = request_factory.put(reverse('coregroup-detail', args=(coregroup.pk,)), data, format='json')
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'put': 'update'})(request, pk=coregroup.pk)
assert response.status_code == 200
coregroup_upd = wfm.CoreGroup.objects.get(pk=coregroup.pk)
Expand All @@ -169,15 +109,12 @@ def test_coregroup_update(self, request_factory, org_admin):
@pytest.mark.django_db()
class TestCoreGroupListView:

def test_coregroup_list(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
wfl1_another_org = factories.WorkflowLevel1.create(organization=factories.Organization(name='Another org'))
factories.CoreGroup.create(name='Group 1', workflowlevel1=wfl1)
factories.CoreGroup.create(name='Group 2', workflowlevel1=wfl1)
factories.CoreGroup.create(name='Group 3', workflowlevel1=wfl1_another_org)
def test_coregroup_list(self, request_factory, superuser):
factories.CoreGroup.create(name='Group 1')
factories.CoreGroup.create(name='Group 2')

request = request_factory.get(reverse('coregroup-list'))
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'get': 'list'})(request)
assert response.status_code == 200
assert len(response.data) == 2
Expand All @@ -186,26 +123,23 @@ def test_coregroup_list(self, request_factory, org_admin):
@pytest.mark.django_db()
class TestCoreGroupDetailView:

def test_coregroup_detail(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
coregroup = factories.CoreGroup.create(workflowlevel1=wfl1)
def test_coregroup_detail(self, request_factory, superuser):
coregroup = factories.CoreGroup.create()

request = request_factory.get(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'get': 'retrieve'})(request, pk=coregroup.pk)
assert response.status_code == 200
assert response.data['workflowlevel1'] == wfl1.id


@pytest.mark.django_db()
class TestCoreGroupDeleteView:

def test_coregroup_delete(self, request_factory, org_admin):
wfl1 = factories.WorkflowLevel1.create(organization=org_admin.organization)
coregroup = factories.CoreGroup.create(workflowlevel1=wfl1)
def test_coregroup_delete(self, request_factory, superuser):
coregroup = factories.CoreGroup.create()

request = request_factory.delete(reverse('coregroup-detail', args=(coregroup.pk,)))
request.user = org_admin
request.user = superuser
response = CoreGroupViewSet.as_view({'delete': 'destroy'})(request, pk=coregroup.pk)
assert response.status_code == 204

Expand Down
Loading

0 comments on commit d4dd68a

Please sign in to comment.