From 757a8d671249aad460c30a558c1e874d8dc978fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vebj=C3=B8rn=20Nyvoll?= Date: Thu, 2 Nov 2023 19:59:11 +0100 Subject: [PATCH] Changed to using ModelForm, still need to fix FileUpload. Also added Spreadsheet support. --- src/make_queue/forms/reservation.py | 24 ++- src/make_queue/models/reservation.py | 31 +++- .../static/make_queue/js/sla_form.js | 160 ++++++++++++++++++ .../make_queue/sla_request_form.html | 13 +- 4 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 src/make_queue/static/make_queue/js/sla_form.js diff --git a/src/make_queue/forms/reservation.py b/src/make_queue/forms/reservation.py index 570d98abc..65b7ba4a0 100644 --- a/src/make_queue/forms/reservation.py +++ b/src/make_queue/forms/reservation.py @@ -1,13 +1,13 @@ +from datetime import datetime + from django import forms from django.db.models import TextChoices from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ -from django.core.validators import FileExtensionValidator - from news.models import TimePlace from web.widgets import SemanticChoiceInput from ..models.machine import Machine, MachineType - +from ..models.reservation import SLARequest class ReservationForm(forms.Form): """Form for creating or changing a reservation.""" @@ -79,7 +79,17 @@ class Owner(TextChoices): owner = forms.TypedChoiceField(choices=Owner.choices, coerce=Owner) -class SLARequestForm(forms.Form): - description = forms.CharField(widget=forms.Textarea, required=True, label=_("Description"), help_text=_("Provide a description of the object you want us to print and why it should be printed using one of the SLA printers.")) - file = forms.FileField(validators=[FileExtensionValidator(allowed_extensions=["stl"])], required=True, label=_("Upload STL")) - final_date = forms.DateField(label=_("Final date"), widget=forms.SelectDateWidget, help_text="This field is not required, but if you have a date you need the object within you may provide it here. We do not guarantee to print it within the chosen date, but if we are unable to print within the selected date we will refrain from printing the object at all to save materials.") +class SLARequestForm(forms.ModelForm): + description = forms.CharField(widget=forms.Textarea, required=True, label=_("Description"), help_text=_( + "Provide a description of the object you want us to print and why it should be printed using one of the SLA printers." + )) + file = forms.FileField(required=True, label=_("Upload STL files")) + final_date = forms.DateField(required=False, widget=forms.DateInput(attrs=dict(type='date', min=datetime.today().strftime('%Y-%m-%d'))), + help_text=_( + "Only provide a date if you will not use the print if it's printed after your selected date. We do not " + "guarantee that your print will be printed within the selected date, but we won't waste material " + "printing it after."), label=_("Final date (optional)")) + + class Meta: + model = SLARequest + fields = ['title', 'purpose', 'description', 'file', 'final_date'] diff --git a/src/make_queue/models/reservation.py b/src/make_queue/models/reservation.py index 01ae66a37..22662f2d5 100644 --- a/src/make_queue/models/reservation.py +++ b/src/make_queue/models/reservation.py @@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError from django.db import models -from django.db.models import Q +from django.db.models import Q, TextChoices from django.utils import timezone from django.utils.formats import time_format from django.utils.text import capfirst @@ -18,6 +18,7 @@ from util.model_utils import ComparisonType, comparison_boilerplate from web.modelfields import UnlimitedCharField from .machine import Machine, MachineType +from util.storage import UploadToUtils class Quota(models.Model): @@ -482,3 +483,31 @@ def overlap(self, other: 'ReservationRule.Period'): (other.exact_start_weekday, other.exact_end_weekday) ) return hours_overlap > 0 + + +class Purpose(TextChoices): + BACHELOR = "Bachelor", "Bachelor's thesis" + MASTER = "Master", "Master's thesis" + PHD = "PhD", "PhD thesis" + ORGANIZATION = "Org", "Student organization" + HOBBY = "Hobby", "Hobby project" + STUDY = "Study", "Other study related project" + OTHER = "Other", "Other project (specify in description)" + + +class SLARequest(models.Model): + # title = models.CharField(required=True, label=_("Title")) + # purpose = models.CharField(choices=Purpose.choices, initial="Hobby", required=True) + # description = models.CharField(widget=forms.Textarea, required=True, label=_("Description"), help_text=_( + # "Provide a description of the object you want us to print and why it should be printed using one of the SLA printers.")) + # file = models.FileField(required=True, label=_("Upload STL files"), upload_to=UploadToUtils.get_pk_prefixed_filename_func('sla-request')) + # final_date = models.DateField(widget=forms.DateInput(attrs=dict(type='date', min=datetime.today().strftime('%Y-%m-%d'))), + # help_text=_( + # "Only provide a date if you will not use the print if it's printed after your selected date. We do not " + # "guarantee that your print will be printed within the selected date, but we won't waste material " + # "printing it after."), label=_("Final date (optional)")) + title = models.CharField(max_length=128) + purpose = models.CharField(choices=Purpose.choices, max_length=256, default="Hobby") + description = models.CharField(max_length=256) + file = models.FileField(upload_to='sla-request') + final_date = models.DateField() diff --git a/src/make_queue/static/make_queue/js/sla_form.js b/src/make_queue/static/make_queue/js/sla_form.js new file mode 100644 index 000000000..0ea7a4607 --- /dev/null +++ b/src/make_queue/static/make_queue/js/sla_form.js @@ -0,0 +1,160 @@ +/* + * Linking `reservation_form.css` is required when linking this script. + * The `var` variables below must also be defined. +*/ +// noinspection ES6ConvertVarToLetConst +var maximumDay; +// noinspection ES6ConvertVarToLetConst +var shouldForceNewTime; + +const reservations = []; +const reservationRules = []; +let canIgnoreRules = false; + +const $startTimeField = $("#start-time"); +const $startTimeFieldInput = $startTimeField.find("input"); +const $endTimeField = $("#end-time"); +const $eventField = $("#event-pk"); + + + +function isNonReservedDate(date) { + /** + * Checks if the given date is inside any reservation + */ + for (const reservation of reservations) { + if (date >= reservation.startTime && date < reservation.endTime) + return false; + } + return true; +} + +function isCompletelyReserved(start, end) { + /** + * Checks if the given time period is completely reserved (ignores open slots of less than 5 minutes) + */ + const maxDifference = 5 * 60 * 1000; + const reservationsInPeriod = getReservationsInPeriod(start, end); + if (!reservationsInPeriod.length) + return false; + reservationsInPeriod.sort((a, b) => a.startTime - b.startTime); + let currentTime = start; + for (const reservation of reservationsInPeriod) { + if (reservation.startTime > new Date(currentTime.valueOf() + maxDifference)) + return false; + currentTime = reservation.endTime; + } + return currentTime >= new Date(end.valueOf() - maxDifference); +} + +function getReservationsInPeriod(start, end) { + /** + * Returns all reservations that are at least partially within the given time period. + */ + let reservationsInPeriod = []; + for (const reservation of reservations) { + if ((reservation.startTime <= start && reservation.endTime > start) || + (reservation.startTime > start && reservation.endTime < end) || + (reservation.startTime < end && reservation.endTime > end)) { + reservationsInPeriod.push(reservation); + } + } + return reservationsInPeriod; +} + +let minDateStartTime = new Date(); + +$startTimeField.calendar({ + minDate: minDateStartTime, + maxDate: maximumDay, + mode: "day", + endCalendar: $endTimeField, + initialDate: new Date($startTimeFieldInput.val()), + firstDayOfWeek: 1, + isDisabled: function (date, mode) { + if (!date) + return true; + /* if (mode === "minute") + return !isNonReservedDate(date); + if (mode === "hour") { + date = new Date(date.valueOf()); + date.setMinutes(0, 0, 0); + return isCompletelyReserved(date, new Date(date.valueOf() + 60 * 60 * 1000)); + }*/ + if (mode === "day") { + date = new Date(date.valueOf()); + date.setHours(0, 0, 0, 0); + return isCompletelyReserved(date, new Date(date.valueOf() + 24 * 60 * 60 * 1000)); + } + return false; + }, + onChange: function (value) { + if (value === undefined) + return true; + const shouldChange = isNonReservedDate(value); + if (shouldChange) { + $endTimeField.calendar("setting", "maxDate", getMaxDateReservation(value)); + } + return shouldChange; + }, + }, +); + +$("form").submit(function (event) { + let is_valid = true; + $machineNameDropdown.toggleClass("error-border", false); + $startTimeFieldInput.toggleClass("error-border", false); + $endTimeFieldInput.toggleClass("error-border", false); + $eventField.toggleClass("error-border", false); + + if ($machineNameDropdown.dropdown("get value") === "default") { + $machineNameDropdown.toggleClass("error-border", true); + is_valid = false; + } + + if ($startTimeField.calendar("get date") === null) { + $startTimeFieldInput.toggleClass("error-border", true); + is_valid = false; + } + + if ($endTimeField.calendar("get date") === null) { + $endTimeFieldInput.toggleClass("error-border", true); + is_valid = false; + } + + if ($("#event-checkbox input").is(":checked") && $eventField.dropdown("get value") === "") { + $eventField.toggleClass("error-border", true); + is_valid = false; + } + + if (!is_valid) + return event.preventDefault(); +}); + +function timeSelectionPopupHTML() { + /** + * Creates a valid popup for the time selection utility in the reservation calendar + */ + const $button = $("
").addClass("ui fluid make-bg-yellow button").html(gettext("Choose time")); + $button.on("mousedown touchstart", () => { + $startTimeField.calendar("set date", calendar.getSelectionStartTime()); + $endTimeField.calendar("set date", calendar.getSelectionEndTime()); + calendar.resetSelection(); + }); + return $button; +} + +const calendar = new ReservationCalendar($(".reservation-calendar"), { + date: new Date(), + machineType: getMachineType(), + machine: getMachine(), + selection: true, + canIgnoreRules: false, + selectionPopupContent: timeSelectionPopupHTML, +}); + +if ($startTimeField.calendar("get date") !== null) { + calendar.showDate($startTimeField.calendar("get date")); +} + +getFutureReservations($machineNameDropdown.dropdown("get value"), shouldForceNewTime); diff --git a/src/make_queue/templates/make_queue/sla_request_form.html b/src/make_queue/templates/make_queue/sla_request_form.html index 05a40a817..dfe04642e 100644 --- a/src/make_queue/templates/make_queue/sla_request_form.html +++ b/src/make_queue/templates/make_queue/sla_request_form.html @@ -2,16 +2,21 @@ {% load static %} {% load i18n %} +{% load datetime_tags %} +{% load string_tags %} + + + + {% block body %} -
+ {% csrf_token %}

{% trans "Request an SLA print" %}

- - {{form}} +
-
+ {% endblock body %}