diff --git a/backend/api/serializers/checks_serializer.py b/backend/api/serializers/checks_serializer.py index eeafcf17..1ff44824 100644 --- a/backend/api/serializers/checks_serializer.py +++ b/backend/api/serializers/checks_serializer.py @@ -7,9 +7,14 @@ class FileExtensionSerializer(serializers.ModelSerializer): + extension = serializers.CharField( + required=True, + max_length=10 + ) + class Meta: model = FileExtension - fields = ["extension"] + fields = ["id", "extension"] class StructureCheckSerializer(serializers.ModelSerializer): @@ -33,24 +38,41 @@ def validate(self, attrs): """Validate the structure check""" project: Project = self.context["project"] + # The structure check path should not exist already if project.structure_checks.filter(path=attrs["path"]).exists(): raise ValidationError(_("project.error.structure_checks.already_existing")) + # The same extension should not be in both blocked and obligated + blocked = set([ext["extension"] for ext in attrs["blocked_extensions"]]) + obligated = set([ext["extension"] for ext in attrs["obligated_extensions"]]) + + if blocked.intersection(obligated): + raise ValidationError(_("project.error.structure_checks.blocked_obligated")) + return attrs - def create(self, validated_data): + def create(self, validated_data: dict) -> StructureCheck: """Create a new structure check""" blocked = validated_data.pop("blocked_extensions") obligated = validated_data.pop("obligated_extensions") - check: StructureCheck = StructureCheck.objects.create(**validated_data) + + check: StructureCheck = StructureCheck.objects.create( + path=validated_data.pop("path"), + **validated_data + ) for ext in obligated: - check.obligated_extensions.create(**ext) + ext, _ = FileExtension.objects.get_or_create( + extension=ext["extension"] + ) + check.obligated_extensions.add(ext) # Add blocked extensions - for ext in blocked: - check.blocked_extensions.create(**ext) + ext, _ = FileExtension.objects.get_or_create( + extension=ext["extension"] + ) + check.blocked_extensions.add(ext) return check diff --git a/backend/api/views/project_view.py b/backend/api/views/project_view.py index f13fedde..4d01e5e4 100644 --- a/backend/api/views/project_view.py +++ b/backend/api/views/project_view.py @@ -1,5 +1,6 @@ import logging +from rest_framework.exceptions import ValidationError from rest_framework.parsers import MultiPartParser from api.models.group import Group diff --git a/frontend/src/components/forms/ErrorMessage.vue b/frontend/src/components/forms/ErrorMessage.vue index c6c0e487..b6af146d 100644 --- a/frontend/src/components/forms/ErrorMessage.vue +++ b/frontend/src/components/forms/ErrorMessage.vue @@ -3,7 +3,6 @@ import Message from 'primevue/message'; /* Props */ defineProps<{ field: any }>(); - diff --git a/frontend/src/test/unit/services/project_service.test.ts b/frontend/src/test/unit/services/project_service.test.ts index 7dd17ce5..a39fe9f2 100644 --- a/frontend/src/test/unit/services/project_service.test.ts +++ b/frontend/src/test/unit/services/project_service.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, it, expect } from 'vitest'; +import { describe, it } from 'vitest'; describe('placeholder', (): void => { it('aaaaaaaa', () => {}); diff --git a/frontend/src/test/unit/types/structureCheck.test.ts b/frontend/src/test/unit/types/structureCheck.test.ts index 7f41fde0..f423a590 100644 --- a/frontend/src/test/unit/types/structureCheck.test.ts +++ b/frontend/src/test/unit/types/structureCheck.test.ts @@ -5,7 +5,7 @@ import { describe, it } from 'vitest'; // import { createStructureCheck } from './helper'; describe('structureCheck type', () => { - it('aaaa') + it('aaaa'); // it('create instance of structureCheck with correct properties', () => { // const structureCheck = createStructureCheck(structureCheckData); // diff --git a/frontend/src/types/ApiResponse.ts b/frontend/src/types/ApiResponse.ts new file mode 100644 index 00000000..c8d0ec66 --- /dev/null +++ b/frontend/src/types/ApiResponse.ts @@ -0,0 +1 @@ +export type HyperlinkedRelation = string; \ No newline at end of file diff --git a/frontend/src/types/Course.ts b/frontend/src/types/Course.ts index 6f9b3ca8..fa7d79e4 100644 --- a/frontend/src/types/Course.ts +++ b/frontend/src/types/Course.ts @@ -9,17 +9,17 @@ export class Course { public id: string = '', public name: string = '', public excerpt: string = '', - public description: string | null = '', + public description: string = '', public academic_startyear: number = getAcademicYear(), public private_course: boolean = false, - public invitation_link: string | null = null, - public invitation_link_expires: Date | null = null, + public invitation_link: string = '', + public invitation_link_expires: Date = new Date(), public parent_course: Course | null = null, - public faculty: Faculty | null = null, - public teachers: Teacher[] | null = null, - public assistants: Assistant[] | null = null, - public students: Student[] | null = null, - public projects: Project[] | null = null, + public faculty: Faculty = new Faculty(), + public teachers: Teacher[] = [], + public assistants: Assistant[] = [], + public students: Student[] = [], + public projects: Project[] = [], ) {} /** @@ -63,9 +63,6 @@ export class Course { * @param course */ static fromJSON(course: Course): Course { - const faculty = - course.faculty !== undefined && course.faculty !== null ? Faculty.fromJSON(course.faculty) : null; - return new Course( course.id, course.name, @@ -76,7 +73,7 @@ export class Course { course.invitation_link, course.invitation_link_expires, course.parent_course, - faculty, + course.faculty, ); } } diff --git a/frontend/src/types/Faculty.ts b/frontend/src/types/Faculty.ts index b4d78e08..db503bed 100644 --- a/frontend/src/types/Faculty.ts +++ b/frontend/src/types/Faculty.ts @@ -1,7 +1,7 @@ export class Faculty { constructor( - public id: string, - public name: string, + public id: string = '', + public name: string = '', ) {} /** diff --git a/frontend/src/types/FileExtension.ts b/frontend/src/types/FileExtension.ts index c5e9f985..94117bf0 100644 --- a/frontend/src/types/FileExtension.ts +++ b/frontend/src/types/FileExtension.ts @@ -1,5 +1,8 @@ export class FileExtension { - constructor(public extension: string) {} + constructor( + public id: string = '', + public extension: string = '', + ) {} /** * Convert a file extension object to a file extension instance. @@ -7,6 +10,6 @@ export class FileExtension { * @param extension */ static fromJSON(extension: FileExtension): FileExtension { - return new FileExtension(extension.extension); + return new FileExtension(extension.id, extension.extension); } } diff --git a/frontend/src/types/StructureCheck.ts b/frontend/src/types/StructureCheck.ts index 334a690e..bfa32933 100644 --- a/frontend/src/types/StructureCheck.ts +++ b/frontend/src/types/StructureCheck.ts @@ -1,5 +1,5 @@ import { FileExtension } from './FileExtension.ts'; -import { type Project } from './Project.ts'; +import { Project } from './Project.ts'; export class StructureCheck { constructor( @@ -7,7 +7,7 @@ export class StructureCheck { public path: string = '', public obligated_extensions: FileExtension[] = [], public blocked_extensions: FileExtension[] = [], - public project: Project | null = null, + public project: Project = new Project(), ) {} /** @@ -51,7 +51,7 @@ export class StructureCheck { * @param extensions */ public setBlockedExtensionList(extensions: string[]): void { - this.blocked_extensions = extensions.map((extension) => new FileExtension(extension)); + this.blocked_extensions = extensions.map((extension) => new FileExtension('', extension)); } /** @@ -60,7 +60,7 @@ export class StructureCheck { * @param extensions */ public setObligatedExtensionList(extensions: string[]): void { - this.obligated_extensions = extensions.map((extension) => new FileExtension(extension)); + this.obligated_extensions = extensions.map((extension) => new FileExtension('', extension)); } /** @@ -103,6 +103,11 @@ export class StructureCheck { * @param structureCheck */ static fromJSON(structureCheck: StructureCheck): StructureCheck { - return new StructureCheck(structureCheck.id, structureCheck.path); + return new StructureCheck( + structureCheck.id, + structureCheck.path, + structureCheck.obligated_extensions.map(FileExtension.fromJSON), + structureCheck.blocked_extensions.map(FileExtension.fromJSON), + ); } } diff --git a/frontend/src/views/projects/UpdateProjectView.vue b/frontend/src/views/projects/UpdateProjectView.vue index 3926f2ee..7ce381dc 100644 --- a/frontend/src/views/projects/UpdateProjectView.vue +++ b/frontend/src/views/projects/UpdateProjectView.vue @@ -53,8 +53,9 @@ async function saveProject(newProject: Project): Promise { await setExtraChecks(newProject.extra_checks ?? [], project.value.id); // Delete the deleted checks - const deletedChecks = (project.value.extra_checks ?? []).filter( - (check) => !(newProject.extra_checks ?? []).find((newCheck: ExtraCheck) => newCheck.id === check.id), + const deletedChecks = project.value.extra_checks.filter( + (check) => + newProject.extra_checks.find((newCheck: ExtraCheck) => newCheck.id === check.id) === undefined, ); for (const check of deletedChecks) {