diff --git a/zapisy/apps/schedule/assets/reservation.js b/zapisy/apps/schedule/assets/reservation.js index 1488df54fa..72a789d23d 100644 --- a/zapisy/apps/schedule/assets/reservation.js +++ b/zapisy/apps/schedule/assets/reservation.js @@ -31,9 +31,15 @@ const listOfEmpty = []; function setFormDisplay() { if ($("#form-type").val() === "2") { $("#form-course").addClass("d-none"); + $("#form-thesis").addClass("d-none"); $(".form-event").removeClass("d-none"); + } else if ($("#form-type").val() === "5") { + $("#form-course").addClass("d-none"); + $("#form-thesis").removeClass("d-none"); + $(".form-event").addClass("d-none"); } else { $("#form-course").removeClass("d-none"); + $("#form-thesis").addClass("d-none"); $(".form-event").addClass("d-none"); } } diff --git a/zapisy/apps/schedule/forms.py b/zapisy/apps/schedule/forms.py index 468421ff15..d125cb6ad3 100644 --- a/zapisy/apps/schedule/forms.py +++ b/zapisy/apps/schedule/forms.py @@ -13,6 +13,8 @@ from apps.schedule.models.event import Event from apps.schedule.models.message import EventMessage, EventModerationMessage from apps.schedule.models.term import Term +from apps.theses.enums import ThesisStatus +from apps.theses.models import Thesis class TermForm(forms.ModelForm): @@ -104,6 +106,15 @@ class Meta: model = Event exclude = ('status', 'author', 'created', 'edited', 'group', 'interested') + def clean(self): + cleaned_data = super().clean() + if cleaned_data['type'] != Event.TYPE_DEFENCE: + cleaned_data['thesis'] = None + if cleaned_data['type'] not in (Event.TYPE_EXAM, Event.TYPE_TEST): + cleaned_data['course'] = None + if cleaned_data['type'] != Event.TYPE_GENERIC: + cleaned_data['title'] = None + title = forms.CharField(label="Nazwa", required=False) description = forms.CharField( label="Opis", @@ -122,6 +133,9 @@ class Meta: course = forms.ModelChoiceField(queryset=CourseInstance.objects.none(), label="Przedmiot", required=False) + thesis = forms.ModelChoiceField(queryset=Thesis.objects.none(), + label="Praca dyplomowa", + required=False) def __init__(self, user, *args, **kwargs): super(EventForm, self).__init__(*args, **kwargs) @@ -150,9 +164,19 @@ def __init__(self, user, *args, **kwargs): self.fields['course'].queryset = queryset + if not user.has_perm('schedule.manage_events'): + thesis_queryset = Thesis.objects.filter( + advisor=user.employee, + status=ThesisStatus.IN_PROGRESS) + else: + thesis_queryset = Thesis.objects.filter(status=ThesisStatus.IN_PROGRESS) + + self.fields['thesis'].queryset = thesis_queryset.order_by('title') + self.helper.layout = Layout( 'type', Div('course', css_id="form-course"), + Div('thesis', css_id="form-thesis", css_class="d-none"), Div(CustomVisibleCheckbox('visible'), css_class="d-none form-event"), Div('title', css_class='d-none form-event'), 'description', diff --git a/zapisy/apps/schedule/migrations/0011_auto_20240510_0122.py b/zapisy/apps/schedule/migrations/0011_auto_20240510_0122.py new file mode 100644 index 0000000000..66d755630b --- /dev/null +++ b/zapisy/apps/schedule/migrations/0011_auto_20240510_0122.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.14 on 2024-05-10 01:22 + +from django.db import migrations, models +import django.db.models.deletion + +def update_entry_names(apps, schema_editor): + Event = apps.get_model('schedule', 'Event') + for event in Event.objects.all(): + if event.type == '5': + event.title = (event.get_type_display() + ": " + event.thesis.title)[:255] + elif event.type in ('0', '1') and event.course: + event.title = (event.get_type_display() + ": " + event.course.name)[:255] + event.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('theses', '0006_thesis_max_number_of_students'), + ('schedule', '0010_auto_20220801_2026'), + ('courses', '0036_auto_20211022_1641'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='thesis', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='theses.thesis'), + ), + migrations.AlterField( + model_name='event', + name='type', + field=models.CharField(choices=[('0', 'Egzamin'), ('1', 'Kolokwium'), ('5', 'Obrona pracy dyplomowej'), ('2', 'Wydarzenie'), ('3', 'Zajęcia'), ('4', 'Inne')], max_length=1, verbose_name='Typ'), + ), + migrations.RunPython(update_entry_names), + ] diff --git a/zapisy/apps/schedule/models/event.py b/zapisy/apps/schedule/models/event.py index 26c326b364..b42e6a346a 100644 --- a/zapisy/apps/schedule/models/event.py +++ b/zapisy/apps/schedule/models/event.py @@ -9,6 +9,9 @@ from apps.enrollment.courses.models.course_instance import CourseInstance from apps.enrollment.courses.models.group import Group from apps.enrollment.records.models import Record, RecordStatus +from apps.theses.models import Thesis + +MAX_EVENT_TITLE_LEN = 255 class Event(models.Model): @@ -17,6 +20,7 @@ class Event(models.Model): TYPE_GENERIC = '2' TYPE_CLASS = '3' TYPE_OTHER = '4' + TYPE_DEFENCE = '5' STATUS_PENDING = '0' STATUS_ACCEPTED = '1' @@ -28,6 +32,7 @@ class Event(models.Model): TYPES = [(TYPE_EXAM, 'Egzamin'), (TYPE_TEST, 'Kolokwium'), + (TYPE_DEFENCE, 'Obrona pracy dyplomowej'), (TYPE_GENERIC, 'Wydarzenie'), (TYPE_CLASS, 'Zajęcia'), (TYPE_OTHER, 'Inne')] @@ -36,14 +41,16 @@ class Event(models.Model): TYPES_FOR_TEACHER = [(TYPE_EXAM, 'Egzamin'), (TYPE_TEST, 'Kolokwium'), + (TYPE_DEFENCE, 'Obrona pracy dyplomowej'), (TYPE_GENERIC, 'Wydarzenie')] - title = models.CharField(max_length=255, verbose_name='Tytuł', null=True, blank=True) + title = models.CharField(max_length=MAX_EVENT_TITLE_LEN, verbose_name='Tytuł', null=True, blank=True) description = models.TextField(verbose_name='Opis', blank=True) type = models.CharField(choices=TYPES, max_length=1, verbose_name='Typ') visible = models.BooleanField(verbose_name='Wydarzenie jest publiczne', default=False) status = models.CharField(choices=STATUSES, max_length=1, verbose_name='Stan', default='0') course = models.ForeignKey(CourseInstance, null=True, blank=True, on_delete=models.CASCADE) + thesis = models.ForeignKey(Thesis, null=True, blank=True, on_delete=models.CASCADE) group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.CASCADE) reservation = models.ForeignKey( 'schedule.SpecialReservation', @@ -86,10 +93,27 @@ def clean(self, *args, **kwargs): self.author.has_perm('schedule.manage_events')): self.status = self.STATUS_ACCEPTED - # all exams and tests should be public + # all exams, tests & defenses should be public, we also need to create titles for them - if self.type in [Event.TYPE_EXAM, Event.TYPE_TEST]: + if self.type in [Event.TYPE_EXAM, Event.TYPE_TEST, Event.TYPE_DEFENCE]: self.visible = True + if self.title: + pass + elif self.type in (Event.TYPE_EXAM, Event.TYPE_TEST): + self.title = (self.get_type_display() + ": " + self.course.name)[:MAX_EVENT_TITLE_LEN] + elif self.type == Event.TYPE_DEFENCE: + self.title = (self.get_type_display() + ": " + self.thesis.title)[:MAX_EVENT_TITLE_LEN] + elif self.type == Event.TYPE_CLASS: + self.title = self.group.course.get_short_name() + else: + self.title = "Wydarzenie bez tytułu" + + # only the advisor and supporting advisor should be able to schedule a thesis defence + + if self.type == Event.TYPE_DEFENCE and self.author.employee in \ + (self.thesis.advisor, self.thesis.supporting_advisor) or \ + self.author.has_perm('schedule.manage_events'): + self.status = self.STATUS_ACCEPTED # students can only add generic events that have to be accepted first diff --git a/zapisy/apps/schedule/templates/schedule/event.html b/zapisy/apps/schedule/templates/schedule/event.html index a8b100d705..0d472ec40a 100644 --- a/zapisy/apps/schedule/templates/schedule/event.html +++ b/zapisy/apps/schedule/templates/schedule/event.html @@ -10,13 +10,7 @@ {% block content %}

- {% if event.type == '2' %} - {{ event.title }} - {% elif event.type == '3' %} - {{ event.group }} - {% else %} - {{ event.course }} - {{ event.get_type_display }} - {% endif %} + {{ event.title }}

diff --git a/zapisy/apps/schedule/templates/schedule/includes/reservation_view.html b/zapisy/apps/schedule/templates/schedule/includes/reservation_view.html index 3e7c10d0eb..172c7fc936 100644 --- a/zapisy/apps/schedule/templates/schedule/includes/reservation_view.html +++ b/zapisy/apps/schedule/templates/schedule/includes/reservation_view.html @@ -4,11 +4,7 @@ @@ -51,4 +47,4 @@ -
Tytuł - {% if event.course %} - {{ event.course.name }} - {% else %} - {{ event.title }} - {% endif %} + {{ event.title }}
\ No newline at end of file + diff --git a/zapisy/apps/schedule/utils.py b/zapisy/apps/schedule/utils.py index ad16daef64..b330d23ff9 100644 --- a/zapisy/apps/schedule/utils.py +++ b/zapisy/apps/schedule/utils.py @@ -17,7 +17,7 @@ def get_backgroundColor(self, item): if not item.event.visible: return "#D06B64" - if item.event.type in ['0', '1']: + if item.event.type in ['0', '1', '5']: return "#7BD148" if item.event.type == '2': @@ -30,7 +30,7 @@ def get_borderColor(self, item): if not item.event.visible: return "#924420" - if item.event.type in ['0', '1']: + if item.event.type in ['0', '1', '5']: return "#7BD148" if item.event.type == '2': @@ -43,9 +43,6 @@ def get_title(self, item): if not item.event.visible and not self.request.user.has_perm('schedule.manage_events'): return "Sala zajęta" - if item.event.type in ['0', '1']: - return str(item.event.course) + " " + str(item.event.get_type_display()) - return super(EventAdapter, self).get_title(item) def get_url(self, item): diff --git a/zapisy/apps/schedule/views.py b/zapisy/apps/schedule/views.py index 92aaf36ad7..3140a9865e 100644 --- a/zapisy/apps/schedule/views.py +++ b/zapisy/apps/schedule/views.py @@ -64,6 +64,11 @@ def new_reservation(request, event_id=None): if formset.is_valid(): event.save() formset.save() + if event.type == Event.TYPE_DEFENCE: + event.interested.set(event.thesis.students.all().values_list('user', flat=True)) + event.interested.add(event.thesis.advisor.user) + if event.thesis.supporting_advisor: + event.interested.add(event.thesis.supporting_advisor.user) return redirect(event) else: @@ -429,7 +434,7 @@ class ListEvent(NamedTuple): begin=term.start, end=term.end, room=term.room, - title=term.event.title or str(term.event.course) or "", + title=str(term.event.course) or term.thesis.title or term.event.title or "", type=term.event.group.get_type_display() if term.event.group else term.event.get_type_display(), author=term.event.author.get_full_name())) diff --git a/zapisy/apps/theses/models.py b/zapisy/apps/theses/models.py index 3708c1e1be..d7811a802c 100644 --- a/zapisy/apps/theses/models.py +++ b/zapisy/apps/theses/models.py @@ -125,6 +125,9 @@ def is_supporting_advisor_assigned(self, user): def is_among_advisors(self, user): return self.is_mine(user) or self.is_supporting_advisor_assigned(user) + def __str__(self): + return self.title + @property def has_no_students_assigned(self): return self.students is not None and not self.students.exists()