From b6b24ebe16ee9c91f0582b4d56adb52c59e0f1ef Mon Sep 17 00:00:00 2001 From: Ahmed Gouda Date: Wed, 25 Jun 2025 19:12:42 +0300 Subject: [PATCH 1/3] Add health score as a ranking factor. Move ProjectLevel and ProjectType outside the Project Class to avoid circular imports --- backend/apps/owasp/index/project.py | 2 + ...wasp_update_project_health_requirements.py | 12 ++-- backend/apps/owasp/models/enums.py | 34 ++++++++++ backend/apps/owasp/models/mixins/project.py | 5 ++ backend/apps/owasp/models/project.py | 63 ++++++++----------- .../models/project_health_requirements.py | 4 +- ...update_project_health_requirements_test.py | 12 ++-- .../project_health_requirements_test.py | 14 ++--- .../tests/apps/owasp/models/project_test.py | 23 +++---- 9 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 backend/apps/owasp/models/enums.py diff --git a/backend/apps/owasp/index/project.py b/backend/apps/owasp/index/project.py index 4452d3955b..5ec9bcd7ed 100644 --- a/backend/apps/owasp/index/project.py +++ b/backend/apps/owasp/index/project.py @@ -27,6 +27,7 @@ class ProjectIndex(IndexBase): "idx_organizations", "idx_repositories", "idx_repositories_count", + "idx_health_score", "idx_stars_count", "idx_summary", "idx_tags", @@ -48,6 +49,7 @@ class ProjectIndex(IndexBase): "indexLanguages": ["en"], "customRanking": [ "desc(idx_level_raw)", + "desc(idx_health_score)", "desc(idx_stars_count)", "desc(idx_contributors_count)", "desc(idx_forks_count)", diff --git a/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py b/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py index d99b37fc8a..66f157c56f 100644 --- a/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py +++ b/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand -from apps.owasp.models.project import Project +from apps.owasp.models.enums import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements @@ -10,7 +10,7 @@ class Command(BaseCommand): help = "Set project health requirements for each level." LEVEL_REQUIREMENTS = { - Project.ProjectLevel.INCUBATOR: { + ProjectLevel.INCUBATOR: { "age_days": 15, "contributors_count": 1, "forks_count": 2, @@ -30,7 +30,7 @@ class Command(BaseCommand): "unanswered_issues_count": 5, "unassigned_issues_count": 5, }, - Project.ProjectLevel.LAB: { + ProjectLevel.LAB: { "age_days": 20, "contributors_count": 3, "forks_count": 5, @@ -50,7 +50,7 @@ class Command(BaseCommand): "unanswered_issues_count": 4, "unassigned_issues_count": 4, }, - Project.ProjectLevel.PRODUCTION: { + ProjectLevel.PRODUCTION: { "age_days": 30, "contributors_count": 4, "forks_count": 7, @@ -70,7 +70,7 @@ class Command(BaseCommand): "unanswered_issues_count": 2, "unassigned_issues_count": 2, }, - Project.ProjectLevel.FLAGSHIP: { + ProjectLevel.FLAGSHIP: { "age_days": 30, "contributors_count": 5, "forks_count": 10, @@ -120,7 +120,7 @@ def get_level_requirements(self, level): def handle(self, *args, **options) -> None: """Handle the command execution.""" - for level_code, level_name in sorted(Project.ProjectLevel.choices): + for level_code, level_name in sorted(ProjectLevel.choices): _, created = ProjectHealthRequirements.objects.update_or_create( level=level_code, defaults=self.get_level_requirements(level_code), diff --git a/backend/apps/owasp/models/enums.py b/backend/apps/owasp/models/enums.py new file mode 100644 index 0000000000..b39ab45838 --- /dev/null +++ b/backend/apps/owasp/models/enums.py @@ -0,0 +1,34 @@ +"""Enums for OWASP projects.""" + +from django.db.models import TextChoices + + +class ProjectType(TextChoices): + """Enum for OWASP project types.""" + + # These projects provide tools, libraries, and frameworks that can be leveraged by + # developers to enhance the security of their applications. + CODE = "code", "Code" + + # These projects seek to communicate information or raise awareness about a topic in + # application security. Note that documentation projects should focus on an online-first + # deliverable, where appropriate, but can take any media form. + DOCUMENTATION = "documentation", "Documentation" + + # Some projects fall outside the above categories. Most are created to offer OWASP + # operational support. + OTHER = "other", "Other" + + # These are typically software or utilities that help developers and security + # professionals test, secure, or monitor applications. + TOOL = "tool", "Tool" + + +class ProjectLevel(TextChoices): + """Enum for OWASP project levels.""" + + OTHER = "other", "Other" + INCUBATOR = "incubator", "Incubator" + LAB = "lab", "Lab" + PRODUCTION = "production", "Production" + FLAGSHIP = "flagship", "Flagship" diff --git a/backend/apps/owasp/models/mixins/project.py b/backend/apps/owasp/models/mixins/project.py index 62199bf1b1..ec45140bff 100644 --- a/backend/apps/owasp/models/mixins/project.py +++ b/backend/apps/owasp/models/mixins/project.py @@ -97,6 +97,11 @@ def idx_repositories_count(self) -> int: """Return repositories count for indexing.""" return self.repositories.count() + @property + def idx_health_score(self) -> float | None: + """Return score for indexing.""" + return self.health_score + @property def idx_stars_count(self) -> int: """Return stars count for indexing.""" diff --git a/backend/apps/owasp/models/project.py b/backend/apps/owasp/models/project.py index 268855d31a..e187c6c3eb 100644 --- a/backend/apps/owasp/models/project.py +++ b/backend/apps/owasp/models/project.py @@ -17,8 +17,10 @@ from apps.github.models.pull_request import PullRequest from apps.github.models.release import Release from apps.owasp.models.common import RepositoryBasedEntityModel +from apps.owasp.models.enums import ProjectLevel, ProjectType from apps.owasp.models.managers.project import ActiveProjectManager from apps.owasp.models.mixins.project import ProjectIndexMixin +from apps.owasp.models.project_health_metrics import ProjectHealthMetrics class Project( @@ -39,31 +41,6 @@ class Meta: ] verbose_name_plural = "Projects" - class ProjectLevel(models.TextChoices): - OTHER = "other", "Other" - INCUBATOR = "incubator", "Incubator" - LAB = "lab", "Lab" - PRODUCTION = "production", "Production" - FLAGSHIP = "flagship", "Flagship" - - class ProjectType(models.TextChoices): - # These projects provide tools, libraries, and frameworks that can be leveraged by - # developers to enhance the security of their applications. - CODE = "code", "Code" - - # These projects seek to communicate information or raise awareness about a topic in - # application security. Note that documentation projects should focus on an online-first - # deliverable, where appropriate, but can take any media form. - DOCUMENTATION = "documentation", "Documentation" - - # Some projects fall outside the above categories. Most are created to offer OWASP - # operational support. - OTHER = "other", "Other" - - # These are typically software or utilities that help developers and security - # professionals test, secure, or monitor applications. - TOOL = "tool", "Tool" - level = models.CharField( verbose_name="Level", max_length=20, @@ -132,15 +109,20 @@ def __str__(self) -> str: """Project human readable representation.""" return f"{self.name or self.key}" + @property + def health_score(self) -> float | None: + """Return project health score.""" + return self.last_health_metrics.score if self.last_health_metrics else None + @property def is_code_type(self) -> bool: """Indicate whether project has CODE type.""" - return self.type == self.ProjectType.CODE + return self.type == ProjectType.CODE @property def is_documentation_type(self) -> bool: """Indicate whether project has DOCUMENTATION type.""" - return self.type == self.ProjectType.DOCUMENTATION + return self.type == ProjectType.DOCUMENTATION @property def is_funding_requirements_compliant(self) -> bool: @@ -157,7 +139,7 @@ def is_leader_requirements_compliant(self) -> bool: @property def is_tool_type(self) -> bool: """Indicate whether project has TOOL type.""" - return self.type == self.ProjectType.TOOL + return self.type == ProjectType.TOOL @property def issues(self): @@ -183,6 +165,17 @@ def nest_url(self) -> str: """Get Nest URL for project.""" return get_absolute_url(f"projects/{self.nest_key}") + @property + def last_health_metrics(self) -> ProjectHealthMetrics | None: + """Return last health metrics for the project.""" + return ( + ProjectHealthMetrics.objects.filter(project=self) + .order_by( + "-nest_created_at", + ) + .first() + ) + @property def leaders_count(self) -> int: """Return the count of leaders.""" @@ -298,20 +291,18 @@ def from_github(self, repository) -> None: project_level = project_metadata.get("level") if project_level: level_mapping = { - 2: self.ProjectLevel.INCUBATOR, - 3: self.ProjectLevel.LAB, - 3.5: self.ProjectLevel.PRODUCTION, - 4: self.ProjectLevel.FLAGSHIP, + 2: ProjectLevel.INCUBATOR, + 3: ProjectLevel.LAB, + 3.5: ProjectLevel.PRODUCTION, + 4: ProjectLevel.FLAGSHIP, } - self.level = level_mapping.get(project_level) or self.ProjectLevel.OTHER + self.level = level_mapping.get(project_level) or ProjectLevel.OTHER self.level_raw = project_level # Type. project_type = project_metadata.get("type") if project_type: - self.type = ( - project_type if project_type in self.ProjectType.values else self.ProjectType.OTHER - ) + self.type = project_type if project_type in ProjectType.values else ProjectType.OTHER self.type_raw = project_type self.created_at = repository.created_at diff --git a/backend/apps/owasp/models/project_health_requirements.py b/backend/apps/owasp/models/project_health_requirements.py index 296a995718..cf16ad045f 100644 --- a/backend/apps/owasp/models/project_health_requirements.py +++ b/backend/apps/owasp/models/project_health_requirements.py @@ -3,7 +3,7 @@ from django.db import models from apps.common.models import TimestampedModel -from apps.owasp.models.project import Project +from apps.owasp.models.enums import ProjectLevel class ProjectHealthRequirements(TimestampedModel): @@ -16,7 +16,7 @@ class Meta: level = models.CharField( max_length=10, - choices=Project.ProjectLevel.choices, + choices=ProjectLevel.choices, unique=True, verbose_name="Project Level", ) diff --git a/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py b/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py index 0bad1c1101..171193f20e 100644 --- a/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py +++ b/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py @@ -6,7 +6,7 @@ from django.core.management.base import CommandError from apps.owasp.management.commands.owasp_update_project_health_requirements import Command -from apps.owasp.models.project import Project +from apps.owasp.models.enums import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements @@ -61,11 +61,11 @@ def test_handle_exception(self): @pytest.mark.parametrize( "level", [ - Project.ProjectLevel.FLAGSHIP, - Project.ProjectLevel.INCUBATOR, - Project.ProjectLevel.LAB, - Project.ProjectLevel.PRODUCTION, - Project.ProjectLevel.OTHER, + ProjectLevel.FLAGSHIP, + ProjectLevel.INCUBATOR, + ProjectLevel.LAB, + ProjectLevel.PRODUCTION, + ProjectLevel.OTHER, ], ) def test_get_level_requirements(self, level): diff --git a/backend/tests/apps/owasp/models/project_health_requirements_test.py b/backend/tests/apps/owasp/models/project_health_requirements_test.py index 92340ce5c0..51877766ab 100644 --- a/backend/tests/apps/owasp/models/project_health_requirements_test.py +++ b/backend/tests/apps/owasp/models/project_health_requirements_test.py @@ -1,14 +1,14 @@ import pytest from django.core.exceptions import ValidationError -from apps.owasp.models.project import Project +from apps.owasp.models.enums import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements class TestProjectHealthRequirementsModel: """Unit tests for ProjectHealthRequirements model validation and behavior.""" - VALID_LEVELS = Project.ProjectLevel.values + VALID_LEVELS = ProjectLevel.values INVALID_LEVEL = "invalid_level" POSITIVE_INTEGER_FIELDS = [ "contributors_count", @@ -32,11 +32,11 @@ class TestProjectHealthRequirementsModel: @pytest.mark.parametrize( ("level", "expected"), [ - (Project.ProjectLevel.FLAGSHIP, "Health Requirements for Flagship Projects"), - (Project.ProjectLevel.INCUBATOR, "Health Requirements for Incubator Projects"), - (Project.ProjectLevel.LAB, "Health Requirements for Lab Projects"), - (Project.ProjectLevel.OTHER, "Health Requirements for Other Projects"), - (Project.ProjectLevel.PRODUCTION, "Health Requirements for Production Projects"), + (ProjectLevel.FLAGSHIP, "Health Requirements for Flagship Projects"), + (ProjectLevel.INCUBATOR, "Health Requirements for Incubator Projects"), + (ProjectLevel.LAB, "Health Requirements for Lab Projects"), + (ProjectLevel.OTHER, "Health Requirements for Other Projects"), + (ProjectLevel.PRODUCTION, "Health Requirements for Production Projects"), ("", "Health Requirements for Projects"), ], ) diff --git a/backend/tests/apps/owasp/models/project_test.py b/backend/tests/apps/owasp/models/project_test.py index 4729bbc8f4..c9170dc137 100644 --- a/backend/tests/apps/owasp/models/project_test.py +++ b/backend/tests/apps/owasp/models/project_test.py @@ -4,6 +4,7 @@ from apps.github.models.repository import Repository from apps.github.models.user import User +from apps.owasp.models.enums import ProjectLevel, ProjectType from apps.owasp.models.project import Project @@ -36,9 +37,9 @@ def test_project_str(self, level, project_type, name, key, expected_str): @pytest.mark.parametrize( ("project_type", "expected_result"), [ - (Project.ProjectType.CODE, True), - (Project.ProjectType.DOCUMENTATION, False), - (Project.ProjectType.TOOL, False), + (ProjectType.CODE, True), + (ProjectType.DOCUMENTATION, False), + (ProjectType.TOOL, False), ], ) def test_is_code_type(self, project_type, expected_result): @@ -48,9 +49,9 @@ def test_is_code_type(self, project_type, expected_result): @pytest.mark.parametrize( ("project_type", "expected_result"), [ - (Project.ProjectType.CODE, False), - (Project.ProjectType.DOCUMENTATION, True), - (Project.ProjectType.TOOL, False), + (ProjectType.CODE, False), + (ProjectType.DOCUMENTATION, True), + (ProjectType.TOOL, False), ], ) def test_is_documentation_type(self, project_type, expected_result): @@ -60,9 +61,9 @@ def test_is_documentation_type(self, project_type, expected_result): @pytest.mark.parametrize( ("project_type", "expected_result"), [ - (Project.ProjectType.CODE, False), - (Project.ProjectType.DOCUMENTATION, False), - (Project.ProjectType.TOOL, True), + (ProjectType.CODE, False), + (ProjectType.DOCUMENTATION, False), + (ProjectType.TOOL, True), ], ) def test_is_tool_type(self, project_type, expected_result): @@ -127,6 +128,6 @@ def test_from_github(self): ) assert project.created_at == owasp_repository.created_at - assert project.level == Project.ProjectLevel.LAB - assert project.type == Project.ProjectType.TOOL + assert project.level == ProjectLevel.LAB + assert project.type == ProjectType.TOOL assert project.updated_at == owasp_repository.updated_at From a79dcd2d39c362e60b4b2cb71726c18bf8a73259 Mon Sep 17 00:00:00 2001 From: Ahmed Gouda Date: Thu, 26 Jun 2025 09:25:45 +0300 Subject: [PATCH 2/3] Apply suggestion --- backend/apps/owasp/models/project.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/apps/owasp/models/project.py b/backend/apps/owasp/models/project.py index e187c6c3eb..19288aa4c5 100644 --- a/backend/apps/owasp/models/project.py +++ b/backend/apps/owasp/models/project.py @@ -169,11 +169,7 @@ def nest_url(self) -> str: def last_health_metrics(self) -> ProjectHealthMetrics | None: """Return last health metrics for the project.""" return ( - ProjectHealthMetrics.objects.filter(project=self) - .order_by( - "-nest_created_at", - ) - .first() + ProjectHealthMetrics.objects.filter(project=self).order_by("-nest_created_at").first() ) @property From 868a74476c5ac1671ae2c84ecd8b027b4485bb51 Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Thu, 26 Jun 2025 12:35:11 -0700 Subject: [PATCH 3/3] Update code --- backend/apps/owasp/index/project.py | 4 ++-- ...wasp_update_project_health_requirements.py | 2 +- backend/apps/owasp/models/enums/__init__.py | 0 .../models/{enums.py => enums/project.py} | 0 backend/apps/owasp/models/mixins/project.py | 13 ++++++----- backend/apps/owasp/models/project.py | 22 +++++++++---------- .../models/project_health_requirements.py | 2 +- ...update_project_health_requirements_test.py | 2 +- .../project_health_requirements_test.py | 2 +- .../tests/apps/owasp/models/project_test.py | 2 +- 10 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 backend/apps/owasp/models/enums/__init__.py rename backend/apps/owasp/models/{enums.py => enums/project.py} (100%) diff --git a/backend/apps/owasp/index/project.py b/backend/apps/owasp/index/project.py index 5ec9bcd7ed..df71df5330 100644 --- a/backend/apps/owasp/index/project.py +++ b/backend/apps/owasp/index/project.py @@ -16,8 +16,9 @@ class ProjectIndex(IndexBase): "idx_custom_tags", "idx_description", "idx_forks_count", - "idx_issues_count", + "idx_health_score", "idx_is_active", + "idx_issues_count", "idx_key", "idx_languages", "idx_leaders", @@ -27,7 +28,6 @@ class ProjectIndex(IndexBase): "idx_organizations", "idx_repositories", "idx_repositories_count", - "idx_health_score", "idx_stars_count", "idx_summary", "idx_tags", diff --git a/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py b/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py index 66f157c56f..1ab1759fd4 100644 --- a/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py +++ b/backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand -from apps.owasp.models.enums import ProjectLevel +from apps.owasp.models.enums.project import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements diff --git a/backend/apps/owasp/models/enums/__init__.py b/backend/apps/owasp/models/enums/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/apps/owasp/models/enums.py b/backend/apps/owasp/models/enums/project.py similarity index 100% rename from backend/apps/owasp/models/enums.py rename to backend/apps/owasp/models/enums/project.py diff --git a/backend/apps/owasp/models/mixins/project.py b/backend/apps/owasp/models/mixins/project.py index ec45140bff..c9c80cbf49 100644 --- a/backend/apps/owasp/models/mixins/project.py +++ b/backend/apps/owasp/models/mixins/project.py @@ -2,6 +2,8 @@ from __future__ import annotations +from django.conf import settings + from apps.common.utils import join_values from apps.github.models.repository_contributor import RepositoryContributor from apps.owasp.models.mixins.common import RepositoryBasedEntityModelMixin @@ -34,6 +36,12 @@ def idx_forks_count(self) -> int: """Return forks count for indexing.""" return self.forks_count + @property + def idx_health_score(self) -> float | None: + """Return health score for indexing.""" + # TODO(arkid15r): Enable real health score in production when ready. + return 100 if settings.ENVIRONMENT == "Production" else self.health_score + @property def idx_is_active(self) -> bool: """Return active status for indexing.""" @@ -97,11 +105,6 @@ def idx_repositories_count(self) -> int: """Return repositories count for indexing.""" return self.repositories.count() - @property - def idx_health_score(self) -> float | None: - """Return score for indexing.""" - return self.health_score - @property def idx_stars_count(self) -> int: """Return stars count for indexing.""" diff --git a/backend/apps/owasp/models/project.py b/backend/apps/owasp/models/project.py index 19288aa4c5..99bbe6c943 100644 --- a/backend/apps/owasp/models/project.py +++ b/backend/apps/owasp/models/project.py @@ -17,7 +17,7 @@ from apps.github.models.pull_request import PullRequest from apps.github.models.release import Release from apps.owasp.models.common import RepositoryBasedEntityModel -from apps.owasp.models.enums import ProjectLevel, ProjectType +from apps.owasp.models.enums.project import ProjectLevel, ProjectType from apps.owasp.models.managers.project import ActiveProjectManager from apps.owasp.models.mixins.project import ProjectIndexMixin from apps.owasp.models.project_health_metrics import ProjectHealthMetrics @@ -155,16 +155,6 @@ def issues_count(self) -> int: """Return count of issues.""" return self.issues.count() - @property - def nest_key(self) -> str: - """Get Nest key.""" - return self.key.replace("www-project-", "") - - @property - def nest_url(self) -> str: - """Get Nest URL for project.""" - return get_absolute_url(f"projects/{self.nest_key}") - @property def last_health_metrics(self) -> ProjectHealthMetrics | None: """Return last health metrics for the project.""" @@ -177,6 +167,16 @@ def leaders_count(self) -> int: """Return the count of leaders.""" return len(self.leaders_raw) + @property + def nest_key(self) -> str: + """Get Nest key.""" + return self.key.replace("www-project-", "") + + @property + def nest_url(self) -> str: + """Get Nest URL for project.""" + return get_absolute_url(f"projects/{self.nest_key}") + @property def open_issues(self): """Return open issues.""" diff --git a/backend/apps/owasp/models/project_health_requirements.py b/backend/apps/owasp/models/project_health_requirements.py index cf16ad045f..0feaa7b1bd 100644 --- a/backend/apps/owasp/models/project_health_requirements.py +++ b/backend/apps/owasp/models/project_health_requirements.py @@ -3,7 +3,7 @@ from django.db import models from apps.common.models import TimestampedModel -from apps.owasp.models.enums import ProjectLevel +from apps.owasp.models.enums.project import ProjectLevel class ProjectHealthRequirements(TimestampedModel): diff --git a/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py b/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py index 171193f20e..b75f2277ea 100644 --- a/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py +++ b/backend/tests/apps/owasp/management/commands/owasp_update_project_health_requirements_test.py @@ -6,7 +6,7 @@ from django.core.management.base import CommandError from apps.owasp.management.commands.owasp_update_project_health_requirements import Command -from apps.owasp.models.enums import ProjectLevel +from apps.owasp.models.enums.project import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements diff --git a/backend/tests/apps/owasp/models/project_health_requirements_test.py b/backend/tests/apps/owasp/models/project_health_requirements_test.py index 51877766ab..ed70b15e02 100644 --- a/backend/tests/apps/owasp/models/project_health_requirements_test.py +++ b/backend/tests/apps/owasp/models/project_health_requirements_test.py @@ -1,7 +1,7 @@ import pytest from django.core.exceptions import ValidationError -from apps.owasp.models.enums import ProjectLevel +from apps.owasp.models.enums.project import ProjectLevel from apps.owasp.models.project_health_requirements import ProjectHealthRequirements diff --git a/backend/tests/apps/owasp/models/project_test.py b/backend/tests/apps/owasp/models/project_test.py index c9170dc137..3179866da8 100644 --- a/backend/tests/apps/owasp/models/project_test.py +++ b/backend/tests/apps/owasp/models/project_test.py @@ -4,7 +4,7 @@ from apps.github.models.repository import Repository from apps.github.models.user import User -from apps.owasp.models.enums import ProjectLevel, ProjectType +from apps.owasp.models.enums.project import ProjectLevel, ProjectType from apps.owasp.models.project import Project