From e31ac3fab18b6e44a8809ce74584729309e70d97 Mon Sep 17 00:00:00 2001 From: Mathias Aas <54811233+Mathias-a@users.noreply.github.com> Date: Mon, 25 Sep 2023 21:51:25 +0200 Subject: [PATCH] Create backend for interview rooms (#695) * Create backend for interview rooms --- backend/root/utils/routes.py | 13 ++++++- backend/samfundet/admin.py | 11 +++++- ...ntadmission_interview_location_and_more.py | 36 +++++++++++++++++++ backend/samfundet/models/recruitment.py | 28 ++++++++++++++- backend/samfundet/serializers.py | 9 ++++- backend/samfundet/urls.py | 1 + backend/samfundet/views.py | 17 +++++++++ frontend/src/routes/backend.ts | 15 ++++++-- 8 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 backend/samfundet/migrations/0038_alter_recruitmentadmission_interview_location_and_more.py diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 7bc51695e..31abead04 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -5,7 +5,7 @@ DO NOT WRITE IN THIS FILE, AS IT WILL BE OVERWRITTEN ON NEXT UPDATE. THIS FILE WAS GENERATED BY: root.management.commands.generate_routes -LAST UPDATE: 2023-09-14 19:45:16.057665+00:00 +LAST UPDATE: 2023-09-24 15:12:38.294245+00:00 """ ############################################################ @@ -344,6 +344,15 @@ admin__samfundet_organization_delete = 'admin:samfundet_organization_delete' admin__samfundet_organization_change = 'admin:samfundet_organization_change' adminsamfundetorganization__objectId = '' +admin__samfundet_interviewroom_permissions = 'admin:samfundet_interviewroom_permissions' +admin__samfundet_interviewroom_permissions_manage_user = 'admin:samfundet_interviewroom_permissions_manage_user' +admin__samfundet_interviewroom_permissions_manage_group = 'admin:samfundet_interviewroom_permissions_manage_group' +admin__samfundet_interviewroom_changelist = 'admin:samfundet_interviewroom_changelist' +admin__samfundet_interviewroom_add = 'admin:samfundet_interviewroom_add' +admin__samfundet_interviewroom_history = 'admin:samfundet_interviewroom_history' +admin__samfundet_interviewroom_delete = 'admin:samfundet_interviewroom_delete' +admin__samfundet_interviewroom_change = 'admin:samfundet_interviewroom_change' +adminsamfundetinterviewroom__objectId = '' admin__samfundet_notification_changelist = 'admin:samfundet_notification_changelist' admin__samfundet_notification_add = 'admin:samfundet_notification_add' admin__samfundet_notification_history = 'admin:samfundet_notification_history' @@ -404,6 +413,8 @@ samfundet__table_detail = 'samfundet:table-detail' samfundet__text_item_list = 'samfundet:text_item-list' samfundet__text_item_detail = 'samfundet:text_item-detail' +samfundet__interview_rooms_list = 'samfundet:interview_rooms-list' +samfundet__interview_rooms_detail = 'samfundet:interview_rooms-detail' samfundet__infobox_list = 'samfundet:infobox-list' samfundet__infobox_detail = 'samfundet:infobox-detail' samfundet__key_value_list = 'samfundet:key_value-list' diff --git a/backend/samfundet/admin.py b/backend/samfundet/admin.py index 9f5c1e650..15d37f79f 100644 --- a/backend/samfundet/admin.py +++ b/backend/samfundet/admin.py @@ -14,7 +14,7 @@ CustomGuardedModelAdmin, ) from .models.event import (Event, EventGroup, EventRegistration) -from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission) +from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission, InterviewRoom) from .models.general import ( Tag, User, @@ -567,4 +567,13 @@ class OrganizationAdmin(CustomGuardedModelAdmin): list_select_related = True +@admin.register(InterviewRoom) +class InterviewRoomAdmin(CustomGuardedModelAdmin): + list_filter = ['name', 'location', 'recruitment', 'gang', 'start_time', 'end_time'] + list_display = ['name', 'location', 'recruitment', 'gang', 'start_time', 'end_time'] + search_fields = ['name', 'location', 'recruitment__name', 'gang__name'] + list_display_links = ['name', 'location'] + list_select_related = ['recruitment', 'gang'] + + ### End: Our models ### diff --git a/backend/samfundet/migrations/0038_alter_recruitmentadmission_interview_location_and_more.py b/backend/samfundet/migrations/0038_alter_recruitmentadmission_interview_location_and_more.py new file mode 100644 index 000000000..8d98bb541 --- /dev/null +++ b/backend/samfundet/migrations/0038_alter_recruitmentadmission_interview_location_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.3 on 2023-09-24 15:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0037_alter_user_options'), + ] + + operations = [ + migrations.AlterField( + model_name='recruitmentadmission', + name='interview_location', + field=models.CharField(blank=True, help_text='Where the intevjuee should wait', max_length=100, null=True), + ), + migrations.CreateModel( + name='InterviewRoom', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Name of the room', max_length=255)), + ('location', models.CharField(help_text='Physical location, eg. campus', max_length=255)), + ('start_time', models.DateTimeField(help_text='Start time of availability')), + ('end_time', models.DateTimeField(help_text='End time of availability')), + ('gang', models.ForeignKey(blank=True, help_text='The gang that booked the room', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='samfundet.gang')), + ('recruitment', models.ForeignKey(help_text='The recruitment that is recruiting', on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='samfundet.recruitment')), + ], + ), + migrations.AddField( + model_name='recruitmentadmission', + name='room', + field=models.ForeignKey(blank=True, help_text='Room where the interview is held', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interviews', to='samfundet.interviewroom'), + ), + ] diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index 51186a87d..38db6033c 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -94,6 +94,24 @@ def __str__(self) -> str: return f'Position: {self.name_en} in {self.recruitment}' +class InterviewRoom(models.Model): + name = models.CharField(max_length=255, help_text='Name of the room') + location = models.CharField(max_length=255, help_text='Physical location, eg. campus') + start_time = models.DateTimeField(help_text='Start time of availability') + end_time = models.DateTimeField(help_text='End time of availability') + recruitment = models.ForeignKey(Recruitment, on_delete=models.CASCADE, help_text='The recruitment that is recruiting', related_name='rooms') + gang = models.ForeignKey(to=Gang, on_delete=models.CASCADE, help_text='The gang that booked the room', related_name='rooms', blank=True, null=True) + + def __str__(self) -> str: + return self.name + + def clean(self) -> None: + if self.start_time > self.end_time: + raise ValidationError('Start time should be before end time') + + super().clean() + + class RecruitmentAdmission(models.Model): admission_text = models.TextField(help_text='Admission text for the admission') recruitment_position = models.ForeignKey( @@ -104,7 +122,15 @@ class RecruitmentAdmission(models.Model): applicant_priority = models.IntegerField(help_text='The priority of the admission') interview_time = models.DateTimeField(help_text='The time of the interview', null=True, blank=True) - interview_location = models.CharField(max_length=100, help_text='The location of the interview', null=True, blank=True) + interview_location = models.CharField(max_length=100, help_text='Where the intevjuee should wait', null=True, blank=True) + room = models.ForeignKey( + InterviewRoom, + on_delete=models.SET_NULL, + null=True, + blank=True, + help_text='Room where the interview is held', + related_name='interviews', + ) PRIORITY_CHOICES = [ (0, 'Not Set'), diff --git a/backend/samfundet/serializers.py b/backend/samfundet/serializers.py index 83331eab5..30e186bc3 100644 --- a/backend/samfundet/serializers.py +++ b/backend/samfundet/serializers.py @@ -9,7 +9,7 @@ from rest_framework import serializers from .models.billig import BilligEvent, BilligTicketGroup, BilligPriceGroup -from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission) +from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission, InterviewRoom) from .models.event import (Event, EventGroup, EventCustomTicket) from .models.general import ( Tag, @@ -561,3 +561,10 @@ class RecruitmentAdmissionForGangSerializer(serializers.ModelSerializer): class Meta: model = RecruitmentAdmission fields = '__all__' + + +class InterviewRoomSerializer(serializers.ModelSerializer): + + class Meta: + model = InterviewRoom + fields = '__all__' diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index d9ae92432..482467ea9 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -27,6 +27,7 @@ router.register('booking', views.BookingView, 'booking') router.register('table', views.TableView, 'table') router.register('textitem', views.TextItemView, 'text_item') +router.register('interview-rooms', views.InterviewRoomView, 'interview_rooms') router.register('infobox', views.InfoboxView, 'infobox') router.register('key-value', views.KeyValueView, 'key_value') router.register('organizations', views.OrganizationView, 'organizations') diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 70acf1a1f..f9e22be7f 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -30,6 +30,7 @@ Recruitment, RecruitmentPosition, RecruitmentAdmission, + InterviewRoom, ) from .models.general import ( Tag, @@ -81,6 +82,7 @@ OrganizationSerializer, FoodCategorySerializer, ClosedPeriodSerializer, + InterviewRoomSerializer, FoodPreferenceSerializer, UserPreferenceSerializer, InformationPageSerializer, @@ -595,3 +597,18 @@ def get_queryset(self) -> Response: Returns all active recruitment positions. """ return RecruitmentPosition.objects.filter(recruitment__visible_from__lte=timezone.now(), recruitment__actual_application_deadline__gte=timezone.now()) + + +class InterviewRoomView(ModelViewSet): + permission_classes = [AllowAny] + serializer_class = InterviewRoomSerializer + queryset = InterviewRoom.objects.all() + + def list(self, request: Request) -> Response: + recruitment = request.query_params.get('recruitment') + if not recruitment: + return Response({'error': 'A recruitment parameter is required'}, status=status.HTTP_400_BAD_REQUEST) + + filtered_rooms = InterviewRoom.objects.filter(recruitment__id=recruitment) + serialized_rooms = self.get_serializer(filtered_rooms, many=True) + return Response(serialized_rooms.data) diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index 490bb5c47..17b5d56f9 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -4,7 +4,7 @@ THIS FILE IS AUTOGENERATED. DO NOT WRITE IN THIS FILE, AS IT WILL BE OVERWRITTEN ON NEXT UPDATE. THIS FILE WAS GENERATED BY: root.management.commands.generate_routes -LAST UPDATE: 2023-09-22 09:21:59.070385+00:00 +LAST UPDATE: 2023-09-24 15:12:38.294245+00:00 """ */ // ############################################################ @@ -343,6 +343,15 @@ export const ROUTES_BACKEND = { admin__samfundet_organization_delete: '/admin/samfundet/organization/:objectId/delete/', admin__samfundet_organization_change: '/admin/samfundet/organization/:objectId/change/', adminsamfundetorganization__objectId: '/admin/samfundet/organization/:objectId/', + admin__samfundet_interviewroom_permissions: '/admin/samfundet/interviewroom/:objectPk/permissions/', + admin__samfundet_interviewroom_permissions_manage_user: '/admin/samfundet/interviewroom/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_interviewroom_permissions_manage_group: '/admin/samfundet/interviewroom/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_interviewroom_changelist: '/admin/samfundet/interviewroom/', + admin__samfundet_interviewroom_add: '/admin/samfundet/interviewroom/add/', + admin__samfundet_interviewroom_history: '/admin/samfundet/interviewroom/:objectId/history/', + admin__samfundet_interviewroom_delete: '/admin/samfundet/interviewroom/:objectId/delete/', + admin__samfundet_interviewroom_change: '/admin/samfundet/interviewroom/:objectId/change/', + adminsamfundetinterviewroom__objectId: '/admin/samfundet/interviewroom/:objectId/', admin__samfundet_notification_changelist: '/admin/samfundet/notification/', admin__samfundet_notification_add: '/admin/samfundet/notification/add/', admin__samfundet_notification_history: '/admin/samfundet/notification/:objectId/history/', @@ -403,6 +412,8 @@ export const ROUTES_BACKEND = { samfundet__table_detail: '/api/table/:pk/', samfundet__text_item_list: '/api/textitem/', samfundet__text_item_detail: '/api/textitem/:pk/', + samfundet__interview_rooms_list: '/api/interview-rooms/', + samfundet__interview_rooms_detail: '/api/interview-rooms/:pk/', samfundet__infobox_list: '/api/infobox/', samfundet__infobox_detail: '/api/infobox/:pk/', samfundet__key_value_list: '/api/key-value/', @@ -436,4 +447,4 @@ export const ROUTES_BACKEND = { samfundet__applicants_without_interviews: '/applicants-without-interviews/', static__path: '/static/:path', media__path: '/media/:path', -} as const; +} as const; \ No newline at end of file