Skip to content

Commit

Permalink
feat: add and test many-to-many relationship between program_area and…
Browse files Browse the repository at this point in the history
… project
  • Loading branch information
del9ra authored and fyliu committed Nov 13, 2024
1 parent e3441c8 commit a291bd8
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 7 deletions.
6 changes: 5 additions & 1 deletion app/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class ProjectSerializer(serializers.ModelSerializer):
"""Used to retrieve project info"""

sdgs = serializers.StringRelatedField(many=True)
program_areas = serializers.StringRelatedField(many=True)

class Meta:
model = Project
Expand All @@ -122,6 +123,7 @@ class Meta:
"image_hero",
"image_icon",
"sdgs",
"program_areas",
)
read_only_fields = (
"uuid",
Expand Down Expand Up @@ -232,9 +234,11 @@ class Meta:
class ProgramAreaSerializer(serializers.ModelSerializer):
"""Used to retrieve program_area info"""

projects = serializers.StringRelatedField(many=True)

class Meta:
model = ProgramArea
fields = ("uuid", "name", "description", "image")
fields = ("uuid", "name", "description", "image", "projects")
read_only_fields = ("uuid", "created_at", "updated_at")


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Generated by Django 4.2.11 on 2024-11-12 20:15

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


class Migration(migrations.Migration):

dependencies = [("core", "0031_userstatustype_user_user_status")]

operations = [
migrations.CreateModel(
name="ProjectProgramAreaXref",
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"),
),
(
"program_area",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="core.programarea",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="core.project"
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="project",
name="program_areas",
field=models.ManyToManyField(
blank=True,
related_name="projects",
through="core.ProjectProgramAreaXref",
to="core.programarea",
),
),
]
2 changes: 1 addition & 1 deletion app/core/migrations/max_migration.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0031_userstatustype_user_user_status
0032_projectprogramareaxref_project_program_areas
11 changes: 11 additions & 0 deletions app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ class Project(AbstractBaseModel):
sdgs = models.ManyToManyField(
"Sdg", related_name="projects", blank=True, through="ProjectSdgXref"
)
program_areas = models.ManyToManyField(
"ProgramArea",
related_name="projects",
blank=True,
through="ProjectProgramAreaXref",
)

def __str__(self):
return f"{self.name}"
Expand Down Expand Up @@ -433,6 +439,11 @@ def __str__(self):
return self.title


class ProjectProgramAreaXref(AbstractBaseModel):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
program_area = models.ForeignKey(ProgramArea, on_delete=models.CASCADE)


class ProjectSdgXref(AbstractBaseModel):
"""
Joins an SDG to a project
Expand Down
30 changes: 26 additions & 4 deletions app/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
FAQS_VIEWED_URL = reverse("faq-viewed-list")
AFFILIATE_URL = reverse("affiliate-list")
LOCATION_URL = reverse("location-list")
PROGRAM_AREA_URL = reverse("program-area-list")
PROGRAM_AREAS_URL = reverse("program-area-list")
SKILL_URL = reverse("skill-list")
STACK_ELEMENT_URL = reverse("stack-element-list")
PERMISSION_TYPE = reverse("permission-type-list")
PROJECTS_URL = reverse("project-list")
STACK_ELEMENT_TYPE_URL = reverse("stack-element-type-list")
SDGS_URL = reverse("sdg-list")
AFFILIATION_URL = reverse("affiliation-list")
Expand Down Expand Up @@ -261,7 +262,7 @@ def test_create_program_area(auth_client):
"description": "About program area",
"image": "http://www.imageurl.com",
}
res = auth_client.post(PROGRAM_AREA_URL, payload)
res = auth_client.post(PROGRAM_AREAS_URL, payload)
assert res.status_code == status.HTTP_201_CREATED
assert res.data["name"] == payload["name"]

Expand All @@ -274,9 +275,9 @@ def test_list_program_area(auth_client):
"description": "About program area",
"image": "http://www.imageurl.com",
}
res = auth_client.post(PROGRAM_AREA_URL, payload)
res = auth_client.post(PROGRAM_AREAS_URL, payload)

res = auth_client.get(PROGRAM_AREA_URL)
res = auth_client.get(PROGRAM_AREAS_URL)

program_areas = ProgramArea.objects.all()
expected_data = ProgramAreaSerializer(program_areas, many=True).data
Expand Down Expand Up @@ -430,3 +431,24 @@ def test_create_user_status_type(auth_client):
res = auth_client.post(USER_STATUS_TYPES_URL, payload)
assert res.status_code == status.HTTP_201_CREATED
assert res.data["name"] == payload["name"]


def test_project_program_area_xref(auth_client, project, program_area):
def get_object(objects, target_uuid):
for obj in objects:
if str(obj["uuid"]) == str(target_uuid):
return obj
return None

project.program_areas.add(program_area)
proj_res = auth_client.get(PROJECTS_URL)
test_proj = get_object(proj_res.data, project.uuid)
assert test_proj is not None
assert len(test_proj["program_areas"]) == 1
assert program_area.name in test_proj["program_areas"]

program_area_res = auth_client.get(PROGRAM_AREAS_URL)
test_program_ar = get_object(program_area_res.data, program_area.uuid)
assert test_program_ar is not None
assert len(test_program_ar["projects"]) == 1
assert project.name in test_program_ar["projects"]
20 changes: 20 additions & 0 deletions app/core/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from ..models import Event
from ..models import ProjectSdgXref
from ..models import Sdg
from ..models import ProgramArea
from ..models import ProjectProgramAreaXref

pytestmark = pytest.mark.django_db

Expand Down Expand Up @@ -149,6 +151,24 @@ def test_soc_major(soc_major):
assert str(soc_major) == "Test Soc Major"


def test_project_program_area_relationship(project):
workforce_development_program_area = ProgramArea.objects.get(
name="Workforce Development"
)
project.program_areas.add(workforce_development_program_area)
assert project.program_areas.count() == 1
assert project.program_areas.contains(workforce_development_program_area)
assert workforce_development_program_area.projects.contains(project)
workforce_development_program_area_xref = ProjectProgramAreaXref.objects.get(
project=project, program_area=workforce_development_program_area
)
assert workforce_development_program_area_xref.created_at is not None
project.program_areas.remove(workforce_development_program_area)
assert project.program_areas.count() == 0
assert not workforce_development_program_area.projects.contains(project)
assert not project.program_areas.contains(workforce_development_program_area)


def test_project_sdg_relationship(project):
climate_action_sdg = Sdg.objects.get(name="Climate Action")

Expand Down
2 changes: 1 addition & 1 deletion scripts/createsuperuser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ set -x

# This command requires the DJANGO_SUPERUSER_USERNAME and
# DJANGO_SUPERUSER_PASSWORD environmental variables to be set when django starts
echo "DJANGO_SUPERUSER_USERNAME: $DJANGO_SUPERUSER_USERNAME"
# echo "DJANGO_SUPERUSER_USERNAME: $DJANGO_SUPERUSER_USERNAME"
docker-compose exec web python manage.py createsuperuser --no-input

0 comments on commit a291bd8

Please sign in to comment.