Skip to content

Commit

Permalink
lepsze rozwiązanie
Browse files Browse the repository at this point in the history
  • Loading branch information
Acors24 committed Nov 26, 2024
1 parent 40ff99f commit 69ec9fb
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Methods = {
clearFilter: () => void;
clearAll: () => void;
updateDropdownWidth: () => void;
onSearchChange: () => void;
};
export default defineComponent<Props, any, Data, Computed, Methods>({
Expand Down Expand Up @@ -133,6 +134,9 @@ export default defineComponent<Props, any, Data, Computed, Methods>({
}
});
},
onSearchChange() {
this.$emit("search-change", ...arguments);
},
},
computed: {
selectionDescription(): string {
Expand Down Expand Up @@ -181,6 +185,7 @@ export default defineComponent<Props, any, Data, Computed, Methods>({
:track-by="trackBy"
:label="propAsLabel"
:placeholder="selectionDescription"
@search-change="onSearchChange"
>
<template slot="option" slot-scope="props">
<div class="option-row">
Expand Down
118 changes: 93 additions & 25 deletions zapisy/apps/theses/assets/components/StudentFilter.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import Vue from "vue";
import { debounce } from "lodash";
import MultiSelectFilter from "@/enrollment/timetable/assets/components/filters/MultiSelectFilter.vue";
import {
Expand All @@ -20,47 +21,113 @@ export default Vue.extend({
students: [] as MultiselectFilterData<number>,
};
},
mounted: function () {
const djangoField = document.getElementById("id_students");
mounted: async function () {
const djangoField = document.getElementById(
"id_students"
) as HTMLSelectElement | null;
if (djangoField === null) {
return;
}
const djangoOptions = djangoField.querySelectorAll("option");
this.students = Array.from(djangoOptions).map(
(option) =>
({
value: Number(option.value),
label: option.text,
} as MultiselectFilterDataItem<number>)
);
const selectedOptions = Array.from(djangoOptions).filter(
(option) => option.selected
);
const multiselectOptions = this.students.filter((dataItem) =>
selectedOptions.some(
(selectedOption) => Number(selectedOption.value) === dataItem.value
)
);
const options = djangoField.options;
if (options.length === 0) {
return;
}
const assigned_students: MultiselectFilterData<number> = Array.from(
options
).map((element) => ({
value: Number(element.value),
label: element.text,
}));
this.students = assigned_students;
const filter = this.$refs["student-filter"] as Vue &
MultiSelectFilterWithSelected;
if (filter) {
filter.selected = multiselectOptions;
filter.selected = this.students;
}
},
methods: {
onSelect: function (selectedIds: number[]) {
this.updateDjangoField(selectedIds);
},
clearData: function () {
const filter = this.$refs["student-filter"] as Vue &
MultiSelectFilterWithSelected;
if (filter) {
this.students = Array.from(filter.selected);
}
},
updateDjangoField: function (selectedIds: number[]) {
const djangoField = document.getElementById("id_students");
console.log("Selected ids:", selectedIds);
const djangoField = document.getElementById(
"id_students"
) as HTMLSelectElement | null;
if (djangoField === null) {
console.log("No field");
return;
}
const optionArray = Array.from(djangoField.options);
const newId = selectedIds.find((id) =>
optionArray.every((option) => option.value !== String(id))
);
const removedIndex = optionArray.findIndex(
(option) => !selectedIds.includes(Number(option.value))
);
if (newId !== undefined) {
const newOption = document.createElement("option");
newOption.value = newId.toString();
newOption.text = this.students.find((s) => s.value === newId)!.label;
newOption.selected = true;
djangoField.options.add(newOption);
}
if (removedIndex !== -1) {
djangoField.options.remove(removedIndex);
}
},
fetchStudents: async function (
params: Record<string, string>
): Promise<{ students: MultiselectFilterData<number> }> {
const ajaxUrlInput = document.querySelector(
"input#ajax-url"
) as HTMLInputElement | null;
if (ajaxUrlInput === null) {
throw new Error("#ajax-url not found.");
}
const ajaxUrl = ajaxUrlInput.value;
const URLParams = new URLSearchParams(params).toString();
const response = await fetch(`${ajaxUrl}?${URLParams}`);
return response.json();
},
onSearchChange: debounce(function (
this: { updateStudents: (search: string) => void },
search: string
) {
return this.updateStudents(search);
},
300),
updateStudents: async function (search: string) {
this.clearData();
if (search.length === 0) {
return;
}
const options = djangoField.querySelectorAll("option");
options.forEach((option) => {
option.selected = selectedIds.includes(Number(option.value));
const { students: fetchedStudents } = await this.fetchStudents({
q: search,
});
const notSelectedStudents = fetchedStudents.filter((fetchedStudent) =>
this.students.every((s) => s.value !== fetchedStudent.value)
);
this.students.push(...notSelectedStudents);
},
},
});
Expand All @@ -75,7 +142,8 @@ export default Vue.extend({
title="Przypisani studenci"
placeholder="Szukaj po imieniu, nazwisku, numerze indeksu..."
ref="student-filter"
@select="updateDjangoField"
@select="onSelect"
@search-change="onSearchChange"
/>
</div>
</template>
Expand Down
16 changes: 15 additions & 1 deletion zapisy/apps/theses/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from crispy_forms.layout import Column, Layout, Row, Submit
from django import forms
from django.utils import timezone
from django.db.models import Q

from apps.common import widgets as common_widgets
from apps.theses.enums import ThesisKind, ThesisStatus, ThesisVote
Expand Down Expand Up @@ -43,7 +44,7 @@ class Meta:
required=False)
kind = forms.TypedChoiceField(choices=ThesisKind.choices, label="Typ", coerce=int)
students = forms.ModelMultipleChoiceField(
queryset=Student.objects.all(),
queryset=Student.objects.none(),
required=False,
label="Przypisani studenci",
widget=forms.SelectMultiple(attrs={'size': '10'}))
Expand Down Expand Up @@ -73,6 +74,10 @@ def __init__(self, user, *args, **kwargs):

self.fields['status'].required = False

if 'students' in self.initial:
assigned_ids = [s.id for s in self.initial['students']]
self.fields['students'].queryset = Student.objects.filter(Q(id__in=assigned_ids))

self.helper = FormHelper()
self.helper.form_method = 'POST'
self.helper.layout = Layout(
Expand All @@ -93,6 +98,15 @@ def __init__(self, user, *args, **kwargs):

def clean(self):
super().clean()
if 'students' in self.data:
if 'students' in self.errors:
self.errors.pop('students')
ids_or_students = self.data.getlist('students') if 'getlist' in dir(self.data) else self.data['students']
if len(ids_or_students) != 0 and isinstance(ids_or_students[0], str):
self.cleaned_data['students'] = Student.objects.filter(Q(id__in=ids_or_students))
else:
self.cleaned_data['students'] = ids_or_students

students = self.cleaned_data['students']
max_number_of_students = int(self.cleaned_data['max_number_of_students'])
if ('students' in self.changed_data or 'max_number_of_students' in self.changed_data) \
Expand Down
2 changes: 1 addition & 1 deletion zapisy/apps/theses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def visible(self, user):
(~Q(status=ThesisStatus.BEING_EVALUATED) & ~Q(status=ThesisStatus.RETURNED_FOR_CORRECTIONS)) |
Q(advisor__user=user) |
Q(supporting_advisor__user=user) |
Q(students__user=user))
Q(students__user=user)).distinct()


class Thesis(models.Model):
Expand Down
1 change: 1 addition & 0 deletions zapisy/apps/theses/templates/theses/thesis_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h1 class="d-inline-block">
{% csrf_token %}
{% crispy thesis_form %}
<div id="student-filter"></div>
<input type="hidden" id="ajax-url" value='{% url "theses:students" %}' />
{% render_bundle 'theses-student-filter' %}
</form>
{% if confirm_changes %}
Expand Down
1 change: 1 addition & 0 deletions zapisy/apps/theses/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
path('<int:id>/form/<int:studentid>', views.gen_form, name="gen_form"),
path('<int:id>/rejecter', views.rejecter_decision, name="rejecter_thesis"),
path('<int:id>/delete', views.delete_thesis, name="delete_thesis"),
path('students', views.students, name="students"),
]
31 changes: 30 additions & 1 deletion zapisy/apps/theses/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.http import Http404, HttpResponseNotAllowed, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.http import require_POST
from django.forms.models import model_to_dict
from django.db.models import Q

from apps.theses.enums import ThesisStatus, ThesisVote
from apps.theses.forms import EditThesisForm, RejecterForm, RemarkForm, ThesisForm, VoteForm
Expand Down Expand Up @@ -281,3 +282,31 @@ def delete_thesis(request, id):
thesis.delete()
messages.success(request, 'Pomyślnie usunięto pracę dyplomową')
return redirect('theses:main')


@employee_required
def students(request):
if request.method != 'GET':
return HttpResponseNotAllowed(['GET'])

if 'q' not in request.GET and 'ids' not in request.GET:
return HttpResponseBadRequest()

if 'q' in request.GET:
query = request.GET['q']
conditions = (
Q(user__first_name__icontains=query) |
Q(user__last_name__icontains=query) |
Q(matricula__icontains=query)
)
else:
ids = request.GET['ids'].split(',')
conditions = Q(id__in=ids)

matching_students = Student.objects.filter(conditions)
return JsonResponse({'students': [
{
'value': s.id,
'label': str(s)
} for s in matching_students
]})

0 comments on commit 69ec9fb

Please sign in to comment.