Skip to content
Open
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
8 changes: 8 additions & 0 deletions app/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .models import ReferrerType
from .models import Sdg
from .models import Skill
from .models import SocBroad
from .models import SocMajor
from .models import SocMinor
from .models import StackElement
Expand Down Expand Up @@ -278,6 +279,13 @@ class ProjectStatusAdmin(admin.ModelAdmin):
list_display = ("name", "description")


@admin.register(SocBroad)
class SocBroadAdmin(admin.ModelAdmin):
list_display = ("title", "occ_code", "soc_minor")
list_filter = ("soc_minor",)
search_fields = ("title", "occ_code")


@admin.register(SocMajor)
class SocMajorAdmin(admin.ModelAdmin):
list_display = ("occ_code", "title")
Expand Down
15 changes: 15 additions & 0 deletions app/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from core.models import ReferrerType
from core.models import Sdg
from core.models import Skill
from core.models import SocBroad
from core.models import SocMajor
from core.models import SocMinor
from core.models import StackElement
Expand Down Expand Up @@ -427,6 +428,20 @@ class Meta:
read_only_fields = ("uuid", "created_at", "updated_at")


class SocBroadSerializer(serializers.ModelSerializer):
class Meta:
model = SocBroad
fields = (
"uuid",
"created_at",
"updated_at",
"soc_minor",
"occ_code",
"title",
)
read_only_fields = ("uuid", "created_at", "updated_at")


class SocMajorSerializer(serializers.ModelSerializer):
"""Used to retrieve soc_major 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 @@ -22,6 +22,7 @@
from .views import ReferrerViewSet
from .views import SdgViewSet
from .views import SkillViewSet
from .views import SocBroadViewSet
from .views import SocMajorViewSet
from .views import SocMinorViewSet
from .views import StackElementTypeViewSet
Expand Down Expand Up @@ -65,6 +66,7 @@
router.register(r"check-types", CheckTypeViewSet, basename="check-type")
router.register(r"project-statuses", ProjectStatusViewSet, basename="project-status")
router.register(r"project-urls", ProjectUrlViewSet, basename="project-url")
router.register(r"soc-broads", SocBroadViewSet, basename="soc-broad")
router.register(r"soc-majors", SocMajorViewSet, basename="soc-major")
router.register(r"soc-minors", SocMinorViewSet, basename="soc-minor")
router.register(r"url-types", UrlTypeViewSet, basename="url-type")
Expand Down
16 changes: 16 additions & 0 deletions app/core/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from ..models import ReferrerType
from ..models import Sdg
from ..models import Skill
from ..models import SocBroad
from ..models import SocMajor
from ..models import SocMinor
from ..models import StackElement
Expand Down Expand Up @@ -67,6 +68,7 @@
from .serializers import ReferrerTypeSerializer
from .serializers import SdgSerializer
from .serializers import SkillSerializer
from .serializers import SocBroadSerializer
from .serializers import SocMajorSerializer
from .serializers import SocMinorSerializer
from .serializers import StackElementSerializer
Expand Down Expand Up @@ -436,6 +438,20 @@ class UserPermissionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = UserPermissionSerializer


@extend_schema_view(
list=extend_schema(description="Return a list of all SOC broad occupations"),
create=extend_schema(description="Create a new SOC broad occupation"),
retrieve=extend_schema(description="Return the details of a SOC broad occupation"),
destroy=extend_schema(description="Delete a SOC broad occupation"),
update=extend_schema(description="Update a SOC broad occupation"),
partial_update=extend_schema(description="Patch a SOC broad occupation"),
)
class SocBroadViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = SocBroad.objects.select_related("soc_minor").all().order_by("title")
serializer_class = SocBroadSerializer


@extend_schema_view(
list=extend_schema(description="Return a list of all the soc majors"),
create=extend_schema(description="Create a new soc major"),
Expand Down
29 changes: 29 additions & 0 deletions app/core/migrations/0046_socbroad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.16 on 2025-12-03 21:40

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


class Migration(migrations.Migration):

dependencies = [
('core', '0045_wintype'),
]

operations = [
migrations.CreateModel(
name='SocBroad',
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')),
('occ_code', models.CharField(max_length=255)),
('title', models.CharField(max_length=255)),
('soc_minor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='soc_broads', to='core.socminor')),
],
options={
'abstract': False,
},
),
]
2 changes: 1 addition & 1 deletion app/core/migrations/max_migration.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0045_wintype
0046_socbroad
17 changes: 17 additions & 0 deletions app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,23 @@ def __str__(self):
return f"{self.name}"


class SocBroad(AbstractBaseModel):
"""
Broad SOC category tied to a SocMinor.
"""

soc_minor = models.ForeignKey(
"SocMinor",
on_delete=models.CASCADE,
related_name="soc_broads",
)
occ_code = models.CharField(max_length=255)
title = models.CharField(max_length=255)

def __str__(self) -> str:
return self.title


class SocMajor(AbstractBaseModel):
occ_code = models.CharField(max_length=255)
title = models.CharField(max_length=255)
Expand Down
10 changes: 10 additions & 0 deletions app/core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ..models import ReferrerType
from ..models import Sdg
from ..models import Skill
from ..models import SocBroad
from ..models import SocMajor
from ..models import SocMinor
from ..models import StackElement
Expand Down Expand Up @@ -335,6 +336,15 @@ def project_status():
)


@pytest.fixture
def soc_broad(soc_minor):
return SocBroad.objects.create(
soc_minor=soc_minor,
occ_code="15-1252",
title="Software Developers",
)


@pytest.fixture
def soc_major():
return SocMajor.objects.create(occ_code="22-2222", title="Test Soc Major")
Expand Down
28 changes: 28 additions & 0 deletions app/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from core.models import ProgramArea
from core.models import ProjectStackElementXref
from core.models import ProjectUrl
from core.models import SocBroad
from core.models import UrlStatusType
from core.models import UserCheck
from core.models import UserPermission
Expand Down Expand Up @@ -43,6 +44,7 @@
CHECK_TYPE_URL = reverse("check-type-list")
PROJECT_STATUSES_URL = reverse("project-status-list")
PROJECT_URLS_URL = reverse("project-url-list")
SOC_BROAD_URL = reverse("soc-broad-list")
SOC_MAJOR_URL = reverse("soc-major-list")
SOC_MINORS_URL = reverse("soc-minor-list")
URL_TYPE_URL = reverse("url-type-list")
Expand Down Expand Up @@ -436,6 +438,32 @@ def test_create_project_status(auth_client):
assert res.data["name"] == payload["name"]


def test_list_soc_broads(auth_client, soc_broad):
res = auth_client.get(SOC_BROAD_URL)

assert res.status_code == status.HTTP_200_OK
assert len(res.data) == 1
assert res.data[0]["title"] == soc_broad.title
assert res.data[0]["soc_minor"] == soc_broad.soc_minor.pk


def test_create_soc_broad(auth_client, soc_minor):
payload = {
"soc_minor": soc_minor.pk,
"occ_code": "15-1211",
"title": "Data Analysts",
}

res = auth_client.post(SOC_BROAD_URL, payload)

assert res.status_code == status.HTTP_201_CREATED

created = SocBroad.objects.get(uuid=res.data["uuid"])
assert created.soc_minor == soc_minor
assert created.occ_code == payload["occ_code"]
assert created.title == payload["title"]


def test_create_soc_major(auth_client):
"""Test that we can create a soc major"""

Expand Down
12 changes: 12 additions & 0 deletions app/core/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ def test_leadership_type_project_relationship(project, leadership_type):
assert project.leadership_type == leadership_type


def test_soc_broad_str(soc_broad):
assert str(soc_broad) == soc_broad.title


def test_soc_broad_relationships(soc_broad, soc_minor):
# forward relation
assert soc_broad.soc_minor == soc_minor

# reverse relation via related_name
assert list(soc_minor.soc_broads.all()) == [soc_broad]


def test_soc_major(soc_major):
assert str(soc_major) == "Test Soc Major"

Expand Down