Skip to content

Commit

Permalink
Implement user_permissions model, api, and test (#342)
Browse files Browse the repository at this point in the history
* Implemented user_permissions model, API, and test
* Added rank field to permission_type and modified the initial migration file

Note: people who already have the old initial data will need to roll back and re-apply the migration
  • Loading branch information
ethanstrominger authored Sep 19, 2024
1 parent edee44d commit b2398e1
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 9 deletions.
5 changes: 5 additions & 0 deletions app/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
global_admin = "globalAdmin"
admin_project = "adminProject"
practice_lead_project = "practiceLeadProject"
member_project = "memberProject"
self_value = "self"
22 changes: 22 additions & 0 deletions app/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from core.models import StackElement
from core.models import StackElementType
from core.models import User
from core.models import UserPermission


class PracticeAreaSerializer(serializers.ModelSerializer):
Expand All @@ -38,6 +39,27 @@ class Meta:
)


class UserPermissionSerializer(serializers.ModelSerializer):
"""Used to retrieve user permissions"""

class Meta:
model = UserPermission
fields = (
"uuid",
"created_at",
"updated_at",
"user",
"permission_type",
"project",
"practice_area",
)
read_only_fields = (
"uuid",
"created_at",
"updated_at",
)


class UserSerializer(serializers.ModelSerializer):
"""Used to retrieve user info"""

Expand Down
2 changes: 2 additions & 0 deletions app/core/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
from .views import SkillViewSet
from .views import StackElementTypeViewSet
from .views import StackElementViewSet
from .views import UserPermissionViewSet
from .views import UserProfileAPIView
from .views import UserViewSet

router = routers.SimpleRouter()
router.register(r"user-permissions", UserPermissionViewSet, basename="user-permission")
router.register(r"users", UserViewSet, basename="user")
router.register(r"projects", ProjectViewSet, basename="project")
router.register(r"events", EventViewSet, basename="event")
Expand Down
12 changes: 12 additions & 0 deletions app/core/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ..models import Skill
from ..models import StackElement
from ..models import StackElementType
from ..models import UserPermission
from .serializers import AffiliateSerializer
from .serializers import AffiliationSerializer
from .serializers import CheckTypeSerializer
Expand All @@ -41,6 +42,7 @@
from .serializers import SkillSerializer
from .serializers import StackElementSerializer
from .serializers import StackElementTypeSerializer
from .serializers import UserPermissionSerializer
from .serializers import UserSerializer


Expand Down Expand Up @@ -346,3 +348,13 @@ class CheckTypeViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = CheckType.objects.all()
serializer_class = CheckTypeSerializer


@extend_schema_view(
list=extend_schema(description="Return a list of all the user permissions"),
retrieve=extend_schema(description="Return the details of a user permission"),
)
class UserPermissionViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = []
queryset = UserPermission.objects.all()
serializer_class = UserPermissionSerializer
3 changes: 1 addition & 2 deletions app/core/migrations/0025_stackelement_delete_technology.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
("core", "0024_checktype"),
("core", "0024_checktype")
]

operations = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Generated by Django 4.2.11 on 2024-08-30 05:51

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
("core", "0025_stackelement_delete_technology"),
]

operations = [
migrations.AddField(
model_name="permissiontype",
name="rank",
field=models.IntegerField(default=0, unique=True),
),
migrations.AlterField(
model_name="permissiontype",
name="name",
field=models.CharField(max_length=255, unique=True),
),
migrations.CreateModel(
name="UserPermission",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Created at"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="Updated at"),
),
(
"permission_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="core.permissiontype",
),
),
(
"practice_area",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="core.practicearea",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="core.project"
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="permissions",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddConstraint(
model_name="userpermission",
constraint=models.UniqueConstraint(
fields=("user", "permission_type", "project", "practice_area"),
name="unique_user_permission",
),
),
]
2 changes: 1 addition & 1 deletion app/core/migrations/max_migration.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0025_stackelement_delete_technology
0026_permissiontype_rank_alter_permissiontype_name_and_more
33 changes: 32 additions & 1 deletion app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,9 @@ class PermissionType(AbstractBaseModel):
Permission Type
"""

name = models.CharField(max_length=255)
name = models.CharField(max_length=255, unique=True)
description = models.TextField(blank=True)
rank = models.IntegerField(unique=True, default=0)

def __str__(self):
if self.description and isinstance(self.description, str):
Expand All @@ -291,6 +292,36 @@ def __str__(self):
return f"{self.name}"


class UserPermission(AbstractBaseModel):
"""
User Permissions
"""

user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="permissions")
permission_type = models.ForeignKey(PermissionType, on_delete=models.CASCADE)
practice_area = models.ForeignKey(
PracticeArea, on_delete=models.CASCADE, blank=True, null=True
)
project = models.ForeignKey(Project, on_delete=models.CASCADE)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "permission_type", "project", "practice_area"],
name="unique_user_permission",
)
]

def __str__(self):
username = self.user.username
permission_type_name = self.permission_type.name
project_name = self.project.name
str_val = f"User: {username} / Permission Type: {permission_type_name}/ Project: {project_name}"
if self.practice_area:
str_val += f" / Practice Area: {self.practice_area.name}"
return str_val


class StackElementType(AbstractBaseModel):
"""
Stack element type used to update a shared data store across projects
Expand Down
72 changes: 72 additions & 0 deletions app/core/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import pytest
from rest_framework.test import APIClient

from constants import admin_project
from constants import practice_lead_project

from ..models import Affiliate
from ..models import Affiliation
from ..models import CheckType
Expand All @@ -16,6 +19,75 @@
from ..models import Skill
from ..models import StackElement
from ..models import StackElementType
from ..models import User
from ..models import UserPermission


@pytest.fixture
def user_superuser_admin():
return User.objects.create_user(
username="AdminUser",
email="[email protected]",
password="adminuser",
is_superuser=True,
)


@pytest.fixture
def user_permissions():
user1 = User.objects.create(username="TestUser1", email="[email protected]")
user2 = User.objects.create(username="TestUser2", email="[email protected]")
project = Project.objects.create(name="Test Project")
permission_type = PermissionType.objects.first()
practice_area = PracticeArea.objects.first()
user1_permission = UserPermission.objects.create(
user=user1,
permission_type=permission_type,
project=project,
practice_area=practice_area,
)
user2_permissions = UserPermission.objects.create(
user=user2,
project=project,
permission_type=permission_type,
practice_area=practice_area,
)
return [user1_permission, user2_permissions]


@pytest.fixture
def user_permission_admin_project():
user = User.objects.create(
username="TestUser Admin Project", email="[email protected]"
)
project = Project.objects.create(name="Test Project Admin Project")
permission_type = PermissionType.objects.filter(name=admin_project).first()
user_permission = UserPermission.objects.create(
user=user,
permission_type=permission_type,
project=project,
)

return user_permission


@pytest.fixture
def user_permission_practice_lead_project():
user = User.objects.create(
username="TestUser Practie Lead Project",
email="[email protected]",
)
permission_type = PermissionType.objects.filter(name=practice_lead_project).first()
project = Project.objects.create(name="Test Project Admin Project")
practice_area = PracticeArea.objects.first()
user_permission = UserPermission.objects.create(
user=user,
permission_type=permission_type,
project=project,
practice_area=practice_area,
)

return user_permission


@pytest.fixture
Expand Down
12 changes: 11 additions & 1 deletion app/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from core.api.serializers import ProgramAreaSerializer
from core.api.serializers import UserSerializer
from core.models import ProgramArea
from core.models import UserPermission

pytestmark = pytest.mark.django_db

USER_PERMISSIONS_URL = reverse("user-permission-list")
ME_URL = reverse("my_profile")
USERS_URL = reverse("user-list")
EVENTS_URL = reverse("event-list")
Expand Down Expand Up @@ -306,7 +308,7 @@ def test_create_stack_element(auth_client, stack_element_type):


def test_create_permission_type(auth_client):
payload = {"name": "adminGlobal", "description": "Can CRUD anything"}
payload = {"name": "newRecord", "description": "Can CRUD anything"}
res = auth_client.post(PERMISSION_TYPE, payload)
assert res.status_code == status.HTTP_201_CREATED
assert res.data["name"] == payload["name"]
Expand All @@ -323,6 +325,14 @@ def test_create_stack_element_type(auth_client):
assert res.data["name"] == payload["name"]


def test_get_user_permissions(user_superuser_admin, user_permissions, auth_client):
auth_client.force_authenticate(user=user_superuser_admin)
permission_count = UserPermission.objects.count()
res = auth_client.get(USER_PERMISSIONS_URL)
assert len(res.data) == permission_count
assert res.status_code == status.HTTP_200_OK


def test_create_sdg(auth_client):
payload = {
"name": "Test SDG name",
Expand Down
23 changes: 23 additions & 0 deletions app/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

import pytest

from ..models import Event
Expand Down Expand Up @@ -91,6 +93,27 @@ def test_affiliation_sponsor(affiliation1):
assert str(xref_instance) == f"Sponsor {xref_instance.project}"


def test_user_permission_admin_project(user_permission_admin_project):
user_permission = user_permission_admin_project
username = user_permission.user.username
permission_type_name = user_permission.permission_type.name
project_name = user_permission.project.name
pattern = f".*{username}.*{permission_type_name}.*{project_name}"
assert re.search(pattern, str(user_permission))


def test_user_permission_practice_lead_project(user_permission_practice_lead_project):
user_permission = user_permission_practice_lead_project
username = user_permission.user.username
permission_type_name = user_permission.permission_type.name
project_name = user_permission.project.name
practice_area_name = user_permission.practice_area.name
pattern = (
f".*{username}.*{permission_type_name}.*{project_name}.*{practice_area_name}"
)
assert re.search(pattern, str(user_permission))


def test_affiliation_partner(affiliation2):
xref_instance = affiliation2
assert xref_instance.is_sponsor is False
Expand Down
Loading

0 comments on commit b2398e1

Please sign in to comment.