Commit e7fd7a9e authored by GuiΩ's avatar GuiΩ
Browse files

perf(feuille): feuille position shifting is done server side

parent 555b23be
......@@ -101,6 +101,36 @@ class FeuilleViewSet(ModelViewSet):
else:
return queryset
def get_object(self):
if self.request.method == 'PUT':
feuille = Feuille.objects.filter(id=self.kwargs.get('pk')).first()
if feuille:
return feuille
else:
return Feuille(id=self.kwargs.get('pk'))
else:
return super().get_object()
def perform_create(self, serializer):
feuille = serializer.save()
for feuilleTmp in Feuille.objects.filter(classe=feuille.classe, position__gte=feuille.position).exclude(
id=feuille.id).all():
feuilleTmp.position += 1
feuilleTmp.save()
def perform_update(self, serializer):
feuille = serializer.save()
for feuilleTmp in Feuille.objects.filter(classe=feuille.classe, position__gte=feuille.position).exclude(
id=feuille.id).all():
feuilleTmp.position += 1
feuilleTmp.save()
def perform_destroy(self, feuille):
for feuilleTmp in Feuille.objects.filter(classe=feuille.classe, position__gt=feuille.position).all():
feuilleTmp.position -= 1
feuilleTmp.save()
feuille.delete()
@action(detail=True, methods=['get'])
def get_pdf(self, request, pk):
latexModel = LatexModel.objects.get(pk=request.query_params['model_id'])
......@@ -164,8 +194,8 @@ class QuestionViewSet(ModelViewSet):
def perform_update(self, serializer):
question = self.get_object()
if self.request.user.has_perm('am.question_valider') \
or (question.auteur == self.request.user \
and question.validee == False
or (question.auteur == self.request.user
and not question.validee
and question.questionsPosees.count() == 0):
super().perform_update(serializer)
else:
......@@ -173,7 +203,7 @@ class QuestionViewSet(ModelViewSet):
def perform_destroy(self, instance):
if instance.auteur == self.request.user \
and instance.validee == False \
and not instance.validee \
and instance.questionsPosees.count() == 0:
super().perform_destroy(instance)
else:
......@@ -280,14 +310,19 @@ class SeanceViewSet(ModelViewSet):
return FileResponse(open(path, 'rb'), as_attachment=True, content_type='application/pdf',
filename=latexModel.get_file_name(seance=seance) + '.pdf')
# def perform_create(self, serializer):
# print(self.request)
# newPosition = Seance.objects.filter(feuille=self.request.data['feuille']).aggregate(Max('position'))['position__max']
# if newPosition is None:
# newPosition = 1
# else:
# newPosition += 1
# serializer.save(position = newPosition)
def perform_create(self, serializer):
seance = serializer.save()
for seanceTmp in Seance.objects.filter(feuille=seance.feuille, position__gte=seance.position).exclude(
id=seance.id).all():
seanceTmp.position += 1
seanceTmp.save()
def perform_update(self, serializer):
seance = serializer.save()
for seanceTmp in Seance.objects.filter(feuille=seance.feuille, position__gte=seance.position).exclude(
id=seance.id).all():
seanceTmp.position += 1
seanceTmp.save()
def destroy(self, request, *args, **kwargs):
seance = self.get_object()
......@@ -321,8 +356,8 @@ class ThemeViewSet(ModelViewSet):
@action(methods=['get'], detail=True)
def question_list(self, request, pk):
questions = QuestionViewSet.get_queryset(self).filter(themes__id=pk)
return Response(questions.values_list('id', flat=True)) \
questions = Question.objects.filter(themes__id=pk)
return Response(questions.values_list('id', flat=True))
@action(detail=True, methods=['get'])
def get_next_position(self, request, pk):
......@@ -331,4 +366,3 @@ class ThemeViewSet(ModelViewSet):
float(request.query_params.get('position')),
'before' in request.query_params)
return Response(newPosition)
......@@ -20,12 +20,18 @@ Events:
import {useFeuillesStore} from "@/store/feuilles";
import {useClassesStore} from "@/store/classes";
import {computed} from "vue";
import {useUndoablesStore} from "@/store/undoables";
const feuilles = useFeuillesStore()
const classes = useClassesStore()
const undoables = useUndoablesStore()
const currFeuilles = computed(() => feuilles.getCurrFeuilles )
function addFeuille() {
undoables.addFeuille(classes.getSelectedId!)
}
</script>
<template lang="pug">
......@@ -48,7 +54,7 @@ Events:
button.FeuilleList_newFeuilleBtn(
v-if="classes.hasSelection"
type="button"
@click="feuilles.addFeuille()"
@click="addFeuille"
) Ajouter une feuille
span(v-if="! classes.hasSelection") Commencez par ajouter vos classes en cliquant sur "Configuration" en haut à gauche.
a#FeuilleListBottom
......
......@@ -31,21 +31,40 @@ Events:
const feuille = computed(() => feuilles.get(props.feuilleId) )
function addSeance(): void {
const feuille = feuilles.get(props.feuilleId)
let position = 1
for (const seanceId of feuille.seances) {
const seance = feuilles.getSeance(seanceId) as ISeanceR
if (seance.position > position)
position = seance.position
const isDeletable = computed(() => {
console.log('isDeletable', feuille.value)
for (const seanceId of feuille.value.seances) {
const seance = feuilles.getSeance(seanceId)!
if (seance)
for (const questionPosee of seance.questionsPosees)
if (questionPosee.score !== null)
return false
}
undoables.addSeance(feuille.id, position + 1)
return true
})
function addSeance() {
undoables.addSeance(props.feuilleId)
}
function deleteFeuille() {
console.debug('FLF.deleteFeuille', feuille.value)
if (isDeletable.value)
undoables.deleteFeuille(feuille.value)
}
</script>
<template lang="pug">
.feuille(v-if="feuille" :data-testid="'feuille_' + feuille.id")
.feuille-titre feuille {{ feuille.position }}
.Feuille(v-if="feuille" :data-testid="'feuille_' + feuille.id")
.FeuilleHeader
span.FeuilleHeader_deleteFeuilleBtn(
:class="{disabled: ! isDeletable}"
title="Supprimer"
@click="deleteFeuille") &#x1F5D9;
|
| feuille {{ feuille.position }}
|
template(v-for="model of latex.getAllModels")
template(v-if="model.type === 'FE'")
a(:href="'api/feuille/' + feuille.id + '/get_pdf/?model_id=' + model.id") {{model.label}}.pdf
......@@ -65,27 +84,38 @@ Events:
@seance-changed="$emit('seance-changed', $event)"
@show-question-tooltip="$emit('show-question-tooltip', $event)"
@show-context-menu="$emit('show-context-menu', $event)")/
button.newfeuille-btn(
button.Feuille_newFeuilleBtn(
type="button"
@click="addSeance(props.feuilleId)"
) Ajouter une séance
</template>
<style scoped>
.feuille {
<style>
.Feuille {
min-width: 1062px;
border-bottom: solid 2px dimgray;
padding: 0;
margin: 0;
}
.feuille-titre {
.FeuilleHeader {
display: block;
font-size: 24px;
margin: 10px;
}
.newfeuille-btn {
.FeuilleHeader_deleteFeuilleBtn {
color: red;
cursor: pointer;
}
.FeuilleHeader_deleteFeuilleBtn.disabled {
color: gray;
cursor: default;
}
.Feuille_newFeuilleBtn {
margin: 20px;
}
......
......@@ -66,18 +66,16 @@ Events:
isDatePickerVisible: false,
})
const deletable = computed(() => {
const isDeletable = computed(() => {
if (seance.value) {
for (let questionPosee of seance.value.questionsPosees) {
if (questionPosee.score !== undefined)
if (questionPosee.score !== null)
return false
}
}
return true
})
const seance = computed(() => feuilles.getSeance(props.seanceId))
const dateStr = computed(() => {
......@@ -87,11 +85,6 @@ Events:
return 'Date'
})
// watch(state.dateTmp, (newDate, oldDate) =>{
// // if (newDate !== 'Date' && newDate !== oldDate)
// dateSave(newDate)
// })
function focusNext(position: number) {
console.debug('FLFS.focusNext', position, seance.value)
let nextPosition: number | undefined = undefined;
......@@ -188,6 +181,7 @@ Events:
<template lang="pug">
.seance(:data-testid="'seance_' + props.seanceId")
div
| Seance {{ seance.position }}
FeuilleListFeuilleSeanceSlot(
v-for="position in 5"
:key="'slot' + props.seanceId + '_' + position"
......@@ -244,7 +238,7 @@ Events:
template(v-if="model.type === 'SE'")
a(:href="'api/seance/' + props.seanceId +'/get_pdf/?model_id=' + model.id") {{model.label}}.pdf
br
a(v-if="deletable" @click="undoables.deleteSeance(seance)") Supprimer la séance
a(v-if="isDeletable" @click="undoables.deleteSeance(seance)") Supprimer la séance
</template>
<style scoped>
......
......@@ -48,7 +48,8 @@ Events:
'question-drop',
'show-context-menu',
'update:newScore',
'focus-next'
'focus-next',
'loaded'
])
const props = defineProps<{
......
......@@ -142,6 +142,7 @@
}
function onQuestionEdit({questionId:questionId, action:action}: {questionId: QuestionID, action: 'edit' | 'duplicate'}) {
questionTooltip.value.close()
state.onLoaded = undefined
switch (action) {
case "edit":
......
......@@ -60,7 +60,7 @@ import type {QuestionID, ThemeID} from "@/types";
return path
}
defineExpose({show})
defineExpose({show, close})
</script>
<template lang="pug">
......
......@@ -96,11 +96,26 @@ export const useClassesStore = defineStore('classesStore', {
return Promise.all(promises)
},
/**
* Loads and sets the list of the classes for the currently selected year
* @returns {Promise<void>}
*/
load(classeId: ClasseID, force: boolean = false) {
console.debug('classes.load', classeId, force)
return $axios.get('api/classe/' + classeId)
.then((response) => {
const classe = response.data
this._classes.set(classe.id, classe)
})
.catch((error: any) => console.error(error));
},
/**
* Loads and sets the list of the classes for the currently selected year
* @returns {Promise<void>}
*/
loadCurrClasses() {
console.debug('classes.loadCurrClasses')
if (this._selectedAnnee) {
return $axios.get("api/classe/?annee=" + this._selectedAnnee)
.then((response: {data: IClasse[]}) => {
......@@ -118,11 +133,13 @@ export const useClassesStore = defineStore('classesStore', {
return Promise.resolve()
}
},
/**
* Load all the classes for the current user
* @returns {Promise<void>}
*/
loadAllClasses() {
console.debug('classes.loadAllClasses')
return $axios.get('api/classe/')
.then((response: {data: IClasse[]}) => {
let ids = []
......
......@@ -27,6 +27,7 @@ interface IFeuillesStore {
_loadingSeancesForClasse: Map<ClasseID, Promise<any>>,
}
export const useFeuillesStore = defineStore('feuillesStore',{
state: (): IFeuillesStore => ({
// the already loaded sheets
......@@ -105,12 +106,17 @@ export const useFeuillesStore = defineStore('feuillesStore',{
this._scores.set(classeId, new Map<QuestionID, IScore[]>)
},
/************************
* Feuilles *
************************/
/**
* load the sheet of the given id and its sessions
* @param feuilleID
* @returns {Promise<void>}
*/
loadFeuille(feuilleId: FeuilleID, force = false){
loadFeuille(feuilleId: FeuilleID, force = false) {
console.debug('feuilles.loadFeuille', feuilleId, force)
if (this._feuilles.has(feuilleId) && !force)
return Promise.resolve()
......@@ -144,102 +150,66 @@ export const useFeuillesStore = defineStore('feuillesStore',{
.catch(error => console.error('feuilles.loadFeuilles', error));
return promise
}
return Promise.resolve()
},
/**
* Add a new empty sheet to the current class (after the last one)
* Add a new empty sheet to the current class
* @param commit
* @returns {Promise<void>}
*/
addFeuille() {
_addFeuille(feuille: Partial<IFeuille>) {
console.debug('feuilles._addFeuille', feuille)
const classes = useClassesStore()
return $axios.post('api/classe/' + classes.getSelectedId + '/feuille_add/')
.then(response => {
const classe = response.data
classes._classes.set(classe.id, classe)
let promises = []
for (const feuilleId of classe.feuilles)
promises.push(this.loadFeuille(feuilleId))
return Promise.all(promises)
})
.catch(error => { console.log(error) })
},
/**
* private action
* @param data = {seance, position, question}
* @returns Promise<QuestionPosee>
* @private
*/
_addQuestionPosee(questionPosee: Partial<IQuestionPosee>) {
const feuilles = useFeuillesStore()
let request
if (questionPosee.hasOwnProperty('id') && questionPosee.id)
request = $axios.put("api/question-posee/" + questionPosee.id + '/', questionPosee)
if (feuille.id)
request = $axios.put("api/feuille/" + feuille.id + '/', feuille)
else
request = $axios.post("api/question-posee/", questionPosee)
return request
.then(response => {
let promises = []
promises.push(feuilles.loadSeance(questionPosee.seance!))
// promises.push(this.dispatch('questions/loadQuestion', questionPosee.question))
promises.push(feuilles.loadScoresOfQuestion(questionPosee.question!))
return Promise.all(promises).then(() => {
return response.data
})
})
.catch(error => console.error(error.response))
},
request = $axios.post("api/feuille/", feuille)
_deleteQuestionPosee(questionPosee: IQuestionPosee) {
return $axios.delete('api/question-posee/' + questionPosee.id + '/')
.then(response => {
let promises = []
promises.push(this.loadSeance(questionPosee.seance))
promises.push(this.loadScoresOfQuestion(questionPosee.question))
return Promise.all(promises)
return request.then(response => {
const feuille = response.data
this._feuilles.set(feuille.id, feuille)
return feuille
})
.catch(error => {
console.log(error)
})
.catch(error => console.log(error.response))
},
_patchQuestionPosee(questionPosee:IQuestionPosee, newValues:Partial<IQuestionPosee>) {
return $axios.patch('api/question-posee/' + questionPosee.id + '/', newValues)
_deleteFeuille(feuille: IFeuille) {
console.debug('feuilles._deleteFeuille', feuille)
const classes = useClassesStore()
return $axios.delete('api/feuille/' + feuille.id + '/')
.then(() => {
if (newValues.hasOwnProperty('seance')
&& newValues.seance !== questionPosee.seance) {
this.loadSeance(newValues.seance!)
}
this.loadSeance(questionPosee.seance!)
if (newValues.hasOwnProperty('question')
&& newValues.question !== questionPosee.question) {
this.loadScoresOfQuestion(newValues.question!)
}
this.loadScoresOfQuestion(questionPosee.question)
const classe = classes.get(feuille.classe)
const index = classe.feuilles.indexOf(feuille.id)
classe.feuilles.splice(index, 1)
this._feuilles.delete(feuille.id)
})
.catch(error => console.log(error))
.catch(error => console.log(error.response))
},
loadScoresOfQuestion(questionId: QuestionID) {
const themes = useThemesStore()
const classes = useClassesStore()
if (!themes.hasSelection || !classes.hasSelection)
return
return $axios.get("api/question-posee/?theme_id=" + themes.getSelectedId
+ "&classe_id=" + classes.getSelectedId
+ "&question_id=" + questionId)
.then(response => {
const questionPosees = response.data
let scores = []
for (let questionPosee of questionPosees) {
scores.push(questionPosee.score);
}
(this._scores.get(classes.getSelectedId as ClasseID) as Map<QuestionID, IScore[]>)
.set(questionId, scores)
_patchFeuille(feuilleId: FeuilleID, newValues: Partial<IFeuille>) {
console.debug('feuilles.patchFeuille', feuilleId, newValues)
const feuilles = useFeuillesStore()
return $axios.patch('api/feuille/' + feuilleId + '/', newValues)
.then((response) => {
const feuille = response.data
console.log('feuilles._patchFeuille', feuille)
this._feuilles.set(feuille.id, feuille)
})
.catch(error => console.log(error.response.data))
},
_addSeance(seance: Partial<ISeance>) {
console.log('feuilles._addSeance', seance)
/********************
* Seances *
********************/
_addSeance(seance: Partial<ISeanceR>) {
console.debug('feuilles._addSeance', seance)
let request
if (seance.hasOwnProperty('id'))
request = $axios.put('api/seance/' + seance.id + '/', seance)
......@@ -249,25 +219,26 @@ export const useFeuillesStore = defineStore('feuillesStore',{
.then(response => {
const newSeance = response.data
this._seances.set(newSeance.id, newSeance)
this.loadFeuille(seance.feuille!, true)
return newSeance
})
.catch(error => console.log(error.data))
},
_deleteSeance(seance: ISeance) {
_deleteSeance(seance: ISeanceR) {
console.debug('feuilles._deleteSeance', seance)
return $axios.delete('api/seance/' + seance.id + '/')
.then(() => {
// for (const questionPosee of state.seances[seanceID].questionsPosees) {
// this.dispatch('feuilles/loadScoresOfQuestion', questionPosee.question)
// }
return this.loadFeuille(seance.feuille, true)
.then(() => this._seances.delete(seance.id))
const feuille = this.get(seance.feuille)
const index = feuille.seances.indexOf(seance.id)
feuille.seances.splice(index, 1)
this._seances.delete(seance.id)
})
.catch(error => console.log(error.data))
},
loadSeance(seanceId: SeanceID, force = false) {
console.debug('feuilles.LoadSeance', seanceId)
// console.trace()
const promise = $axios.get('api/seancer/' + seanceId + '/')
.then((response: { data: ISeanceR }) => {
const seance = response.data
......@@ -278,6 +249,7 @@ export const useFeuillesStore = defineStore('feuillesStore',{
},
loadSeances() {
console.debug('feuilles.LoadSeances')
const classes = useClassesStore()
const classeId = classes.getSelectedId
let promise = Promise.resolve()
......@@ -293,6 +265,7 @@ export const useFeuillesStore = defineStore('feuillesStore',{
return promise
},
/**
*
* @param commit
......@@ -301,55 +274,97 @@ export const useFeuillesStore = defineStore('feuillesStore',{
* @returns {Promise<void>}
*/
patchSeance(seanceId: SeanceID, newValues: Partial<ISeance>) {
console.debug('feuilles.patchSeance', seanceId, newValues)
const feuilles = useFeuillesStore()
return $axios.patch('api/seance/' + seanceId + '/', newValues)
.then(() => feuilles.loadSeance(seanceId, true))
.catch(error => console.log(error.response.data))
},
},
})
// // mutations
// const mutations = {
// /**
// * Adds a sheet in the list of the loaded ones
// * @param feuille
// */
// 'FEUILLE'(state, feuille) {
// Vue.set(state.feuilles, feuille.id, feuille)
// },
//
// /**
// * inits the scores list for the current class
// */
// 'INIT_SCORES'(state, {classeID, themeID}) {
// if (!state.scores.hasOwnProperty(classeID))
// Vue.set(state.scores, classeID, {})
// if (!state.scores[classeID].hasOwnProperty(themeID))
// Vue.set(state.scores[classeID], themeID, {})
// },
//
// /**
// * Set the list of the scores for the given question
// * @param data = {questionID: integer, scores: list of reals}
// */
// 'SCORES_OF_QUESTION'(state, {classeID, themeID, questionID, scores}) {
// Vue.set(state.scores[classeID][themeID], questionID, scores)
// },