Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy permission and role model #749

Merged
merged 2 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion server/apps/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from apps.profile.models import Profile, RcrainfoProfile, RcrainfoSiteAccess

from .models import TrakUser
from .models import Permission, Role, TrakUser


class HiddenListView(admin.ModelAdmin):
Expand Down Expand Up @@ -73,3 +73,5 @@ def api_user(self, profile: RcrainfoProfile) -> bool:

admin.site.register(Profile)
admin.site.unregister(DRFToken)
admin.site.register(Permission)
admin.site.register(Role)
32 changes: 32 additions & 0 deletions server/apps/core/migrations/0002_permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 5.0.6 on 2024-07-11 19:06

import django.contrib.auth.models
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('core', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Permission',
fields=[
],
options={
'verbose_name': 'Permission',
'verbose_name_plural': 'Permissions',
'ordering': ['name'],
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('auth.permission',),
managers=[
('objects', django.contrib.auth.models.PermissionManager()),
],
),
]
26 changes: 26 additions & 0 deletions server/apps/core/migrations/0003_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.0.6 on 2024-07-11 19:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0002_permission'),
]

operations = [
migrations.CreateModel(
name='Role',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=150, unique=True, verbose_name='name')),
('permissions', models.ManyToManyField(to='core.permission', verbose_name='permissions')),
],
options={
'verbose_name': 'Role',
'verbose_name_plural': 'Roles',
'ordering': ['name'],
},
),
]
47 changes: 46 additions & 1 deletion server/apps/core/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import uuid

from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, Group
from django.contrib.auth.models import Permission as DjangoPermission
from django.db import models
from django.utils.translation import gettext_lazy as _


class TrakUser(AbstractUser):
Expand All @@ -17,3 +19,46 @@ class Meta:
editable=False,
default=uuid.uuid4,
)


class Permission(DjangoPermission):
"""Haztrak proxy permission model used for our custom object level permissions."""

class Meta:
proxy = True
verbose_name = "Permission"
verbose_name_plural = "Permissions"
ordering = ["name"]

@property
def app_label(self):
return self.content_type.app_label

@property
def model_name(self):
return self.content_type.model

def __str__(self):
return f"{self.content_type.name} | {self.name}"


class Role(models.Model):
"""A job/function within the system that can assigned to users to grant them permissions."""

class Meta:
verbose_name = _("Role")
verbose_name_plural = _("Roles")
ordering = ["name"]

name = models.CharField(
_("name"),
max_length=150,
unique=True,
)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
)

def __str__(self):
return f"{self.name}"
22 changes: 22 additions & 0 deletions server/apps/core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from typing import Dict, List, Optional

import pytest
from django.contrib.contenttypes.models import ContentType
from faker import Faker

from apps.core.models import Permission
from apps.rcrasite.models import RcraSiteType


Expand Down Expand Up @@ -40,3 +42,23 @@ def create_quicker_sign(
}

return create_quicker_sign


@pytest.fixture
def permission_factory(faker: Faker):
"""
Factory for creating dynamic permission data
"""

def create_permission(
name: str = faker.word(),
content_type_id: int = faker.random_int(min=1),
) -> Permission:
content_type = ContentType.objects.create(app_label=faker.word(), model=faker.word())
return Permission.objects.create(
name=name,
content_type=content_type,
content_type_id=content_type_id,
)

return create_permission
42 changes: 42 additions & 0 deletions server/apps/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest
from django.contrib.contenttypes.models import ContentType

from apps.core.models import Permission, Role


class TestPermissionModel:
mock_app_label = "test_app"
mock_model = "test_model"

@pytest.fixture
def mock_content_type(self):
return ContentType.objects.create(app_label=self.mock_app_label, model=self.mock_model)

@pytest.mark.django_db
def test_saves_new_permissions(self, mock_content_type):
permission_name = "test_permission"
permission = Permission.objects.create(
content_type=mock_content_type, content_type_id=1, name=permission_name
)

saved_permission = Permission.objects.get(name=permission_name)

assert saved_permission.name == permission_name
assert saved_permission.id == permission.id


class TestRoleModel:
@pytest.mark.django_db
def test_role_with_multiple_permissions(self, permission_factory):
permission1 = permission_factory(content_type_id=1)
permission2 = permission_factory(content_type_id=2)
role = Role.objects.create(name="test_role")
role.permissions.add(permission1, permission2)
assert role.permissions.count() == 2
assert permission1 in role.permissions.all()
assert permission2 in role.permissions.all()

@pytest.mark.django_db
def test_role_str_representation(self):
role = Role.objects.create(name="test_role")
assert str(role) == "test_role"
Loading