Commit 0021504f authored by GuiΩ's avatar GuiΩ
Browse files

feat(Question): when duplicate, set default position for question

the new question has the position after the original one
parent aea634bf
Pipeline #512385 failed with stages
in 3 minutes and 27 seconds
......@@ -152,6 +152,32 @@ class Theme(models.Model):
path.reverse()
return path
def get_next_position(self, position: float, before=False) -> float:
"""
:param position:
:param before: if True, a position before the given one is returned
:return: the position between the given position and the position of the next item.
position + 100 if there's no such item
:rtype: float
"""
# TODO: check if there' no pb with multiple theme for one question (which position is considered ?)
if before:
next_question = self.questions.filter(questionorder__position__lt=position)
else:
next_question = self.questions.filter(questionorder__position__gt=position)
if len(next_question):
if before:
next_position = next_question.order_by('questionorder__position').last().questionorder_set.get(theme__id=self.id).position
else:
next_position = next_question.order_by('questionorder__position').first().questionorder_set.get(theme__id=self.id).position
else:
if before:
next_position = position - 200
else:
next_position = position + 200
return (position + next_position)/2
def test_cycle(pk=0):
if pk == 0:
Theme.ids_list = []
......
......@@ -37,19 +37,22 @@ class LatexModelSerializer(ModelSerializer):
model = LatexModel
fields = ['id', 'label', 'description', 'file_name', 'position', 'type', 'parts']
class QuestionOrderSerializer(ModelSerializer):
class Meta:
model = QuestionOrder
fields = ['theme', 'position']
class QuestionSerializer(ModelSerializer):
editable = SerializerMethodField()
auteur = SerializerMethodField()
# to allow themes update :
themes = PrimaryKeyRelatedField(queryset=Theme.objects.all(), many=True, read_only=False)
themes = QuestionOrderSerializer(source='questionorder_set', many=True)
thumb_name = CharField(required=False)
positions = SerializerMethodField()
class Meta:
model = Question
fields = ['id', 'code', 'themes', 'thumb_name', 'editable', 'avalider', 'validee', 'positions', 'auteur']
read_only_fields = ['id', 'editable', 'positions', 'auteur']
fields = ['id', 'code', 'themes', 'thumb_name', 'editable', 'avalider', 'validee', 'auteur']
read_only_fields = ['id', 'editable', 'auteur']
def get_editable(self, question):
user = self.context['request'].user
......@@ -60,16 +63,46 @@ class QuestionSerializer(ModelSerializer):
def get_used(self, question):
pass
def get_positions(self, question):
def get_themes(self, question):
themes = question.themes.values('id', 'questionorder__position')
positions = {}
for theme in themes:
positions[theme['id']] = theme['questionorder__position']
positions[theme['id']] = self.initial_data['positions'][theme['id']]
# positions[theme['id']] = theme['questionorder__position']
return positions
#
# def get_positions(self, question):
# themes = question.themes.values('id', 'questionorder__position')
# positions = {}
# for theme in themes:
# positions[theme['id']] = self.initial_data['positions'][theme['id']]
# # positions[theme['id']] = theme['questionorder__position']
# return positions
def get_auteur(self, question):
return question.auteur.username
def create(self, validated_data):
questionOrders = validated_data.pop('questionorder_set')
question = Question.objects.create(**validated_data)
for questionOrder in questionOrders:
QuestionOrder.objects.create(question=question, **questionOrder)
return question
def update(self, instance, validated_data):
#fields = ['id', 'code', 'themes', 'thumb_name', 'editable', 'avalider', 'validee', 'auteur']
questionOrders = validated_data.pop('questionorder_set')
instance.code = validated_data.get('code', instance.code)
instance.thumb_name = validated_data.get('thumb_name', instance.thumb_name)
instance.avalider = validated_data.get('avalider', instance.avalider)
instance.validee = validated_data.get('editable', instance.validee)
instance.save()
QuestionOrder.objects.filter(question=instance.id).delete();
for qo in questionOrders:
QuestionOrder.objects.create(question=instance, theme=qo['theme'], position=qo['position']).save()
return instance
def validate(self, data):
question = self.instance
errors = {}
......@@ -107,7 +140,7 @@ class QuestionSerializer(ModelSerializer):
if not value or len(value) == 0:
raise ValidationError("Au moins un thème est obligatoire.")
for theme in value:
if theme.enfants.count() != 0:
if theme['theme'].enfants.count() != 0:
raise ValidationError("Une question ne peut être affectée qu'à des sous-thèmes.")
return value
......
......@@ -322,4 +322,13 @@ 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))
return Response(questions.values_list('id', flat=True)) \
@action(detail=True, methods=['get'])
def get_next_position(self, request, pk):
theme = Theme.objects.get(pk=pk)
newPosition = theme.get_next_position(
float(request.query_params.get('position')),
'before' in request.query_params)
return Response(newPosition)
......@@ -61,9 +61,9 @@ Events:
theme-tree(
ref="themeTree"
:multiple="true"
:selection_ids="question.themes"
:selection_ids="themes_selection"
:show-empty="true"
@selection-changed="question.themes = $event")/
@selection-changed="themes_selection = $event")/
</template>
<script>
......@@ -71,7 +71,7 @@ import CodeEditor from "@/components/CodeEditor";
import ThemeTree from "@/components/ThemeTree";
import VModal from 'vue-js-modal';
import Vue from 'vue';
//
Vue.use(VModal, { name: 'modal'});
export default {
......@@ -79,7 +79,8 @@ export default {
components: {CodeEditor, ThemeTree},
data: () => {
return {
question: {themes: [], positions: {}},
themes_selection: [],
question: {themes: {}},
oldQuestion: null,
show: false,
}
......@@ -92,11 +93,22 @@ export default {
return permissions.includes('Validateur')
},
validationPossible() {
if (this.question.themes.length === 0)
const selection = this.themes_selection;
if (selection.length === 0)
return false;
for (const theme_id of this.question.themes) {
if (!this.question.positions.hasOwnProperty(theme_id) ||
this.question.positions[theme_id] === null)
for (let i=0; i<selection.length; i++) {
const theme_id = selection[i];
let found = false
for (let j=0; j<this.question.themes.length; j++) {
if (this.question.themes[j].theme === theme_id) {
found = true;
if (this.question.themes[j].position === null)
return false;
else
break;
}
}
if (! found)
return false;
}
return true;
......@@ -106,28 +118,59 @@ export default {
duplicate(question_id) {
this.$store.commit('questions/QUESTION_SAVE_INIT')
this.oldQuestion = null;
this.question = {themes: []};
this.$modal.show('question-editor');
// this.question = {};
this.question = JSON.parse(JSON.stringify(this.$store.getters["questions/question"](question_id)))
this.question.id = null
this.question.id = null;
for (const index in Object.keys(this.question.themes)) {
let theme = this.question.themes[index];
const theme_id = theme.theme;
const position = theme.position;
this.$store.dispatch('themes/getNextPosition', {theme_id, position}).then(newPos => {
theme.position = newPos;
});
}
this.setThemesSelection(this.question.themes);
this.question.avalider = false
this.question.validee = false
this.$modal.show('question-editor');
},
edit(question_id = null) {
this.$store.commit("questions/QUESTION_SAVE_INIT")
this.oldQuestion = null
this.question = {themes: [], positions: {}};
this.question = {themes: {}};
this.$modal.show('question-editor');
if (question_id) {
this.oldQuestion = this.$store.getters['questions/question'](question_id)
this.question = JSON.parse(JSON.stringify(this.oldQuestion))
}
this.setThemesSelection(this.question.themes);
},
opened(event) {
this.$refs.themeTree.initOpenThemes();
},
setThemesSelection(themes){
let ids = [];
for (const index in themes)
if (themes.hasOwnProperty(index))
ids.push(themes[index].theme);
this.themes_selection = ids;
},
save() {
this.$el.style['cursor'] = 'wait';
let themes = [];
for (const theme_id in this.themes_selection){
let found = false
for (const i in this.question.themes) {
if (this.question.themes.hasOwnProperty(i) && this.question.themes[i].theme === theme_id) {
themes.push(this.question.themes[i]);
found = true;
break;
}
}
if (! found)
themes.push({theme: theme_id, position: null});
}
this.question.themes = themes;
this.$store.dispatch('questions/saveQuestion', {oldQuestion: this.oldQuestion, newQuestion: this.question})
.then(() => {
if (this.$store.getters["questions/questionSaveStatus"])
......
......@@ -87,7 +87,7 @@ export default {
},
changePosition() {
const indexSource = this.dropEvent.dataTransfer.getData('index');
const positionDest = this.$refs.questions[this.dropIndex].question.positions[this.theme_id]
const positionDest = this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id);
this.$el.style.cursor = 'wait'
......@@ -95,27 +95,28 @@ export default {
// on unordered question => after last unodered
if (positionDest === null) {
const firstOrdered = this.findFirstOrdered()
const firstOrdered = this.findFirstOrdered();
if (firstOrdered) {
newPosition = firstOrdered.question.positions[this.theme_id] - 10
newPosition = firstOrdered.question.getPositionInTheme(this.theme_id) - 128;
} else {
newPosition = 0
}
} else if (this.dropIndex < indexSource) { // before the actual position
if (this.dropIndex === 0 || !this.$refs.questions[this.dropIndex - 1].question.positions[this.theme_id])
if (this.dropIndex === 0 || !this.$refs.questions[this.dropIndex - 1].question.getPositionInTheme(this.theme_id))
// before the first ordored question
newPosition = this.$refs.questions[this.dropIndex].question.positions[this.theme_id] - 10;
newPosition = this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id) - 128;
else
newPosition = (this.$refs.questions[this.dropIndex].question.positions[this.theme_id]
+ this.$store.getters['questions/question'](this.question_ids[this.dropIndex - 1]).positions[this.theme_id]) / 2;
newPosition = (this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id)
+ this.$store.getters['questions/question'](this.question_ids[this.dropIndex - 1])
.getPositionInTheme(this.theme_id))/ 2;
} else if (this.dropIndex > indexSource) { // after the actual position
if (this.dropIndex === this.question_ids.length - 1) {
newPosition = this.$refs.questions[this.dropIndex].question.positions[this.theme_id] + 10 // after the last question
newPosition = this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id) + 128 // after the last question
} else {
console.log(this.$refs.questions[this.dropIndex].question.id
, this.$store.getters['questions/question'](this.question_ids[this.dropIndex + 1]).positions[this.theme_id])
newPosition = (this.$refs.questions[this.dropIndex].question.positions[this.theme_id]
+ this.$store.getters['questions/question'](this.question_ids[this.dropIndex + 1]).positions[this.theme_id]) / 2
, this.$store.getters['questions/question'](this.question_ids[this.dropIndex + 1]).getPositionInTheme(this.theme_id))
newPosition = (this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id)
+ this.$store.getters['questions/question'](this.question_ids[this.dropIndex + 1]).getPositionInTheme(this.theme_id)) / 2
}
} else { // on itself (probably a bug)
consol.warn('drop on itself')
......@@ -132,7 +133,7 @@ export default {
this.endDrop()
},
endDrop() {
const positionDest = this.$refs.questions[this.dropIndex].question.positions[this.theme_id]
const positionDest = this.$refs.questions[this.dropIndex].question.getPositionInTheme(this.theme_id)
let element
if (positionDest === null)
......@@ -152,7 +153,7 @@ export default {
findFirstOrdered() {
let index = 0;
while (this.$refs.questions[index]
&& this.$refs.questions[index].question.positions[this.theme_id] === null)
&& this.$refs.questions[index].question.getPositionInTheme(this.theme_id) === null)
index++
return this.$refs.questions[index]
},
......@@ -163,7 +164,7 @@ export default {
findLastUnordered() {
let index = 0;
while (this.$refs.questions[index + 1]
&& this.$refs.questions[index + 1].question.positions[this.theme_id] === null)
&& this.$refs.questions[index + 1].question.getPositionInTheme(this.theme_id) === null)
index++
return this.$refs.questions[index]
},
......
......@@ -10,8 +10,8 @@
:class="{ \
editable: question.editable,\
avalider: question.avalider && ! question.validee,\
atrier: question.positions[this.$store.state.themes.curr_theme_id] === null}"
:position="question.position"
atrier: atrier}"
:position="position"
:src="('../static/thumbs/' + question.thumb_name)"
draggable
@click="click($event, question_id)"
......@@ -43,6 +43,12 @@ export default {
},
scores() {
return this.$store.getters["feuilles/scores"](this.question_id)
},
position() {
return this.question.getPositionInTheme(this.$store.state.themes.curr_theme_id);
},
atrier() {
return this.position == null
}
},
data() {
......@@ -94,7 +100,7 @@ export default {
if (questionSource.avalider && !permissions.includes('Validateur'))
return
const positionDest = this.question.positions[this.$store.state.themes.curr_theme_id]
const positionDest = this.question.getPositionInTheme(this.$store.state.themes.curr_theme_id);
// on unordered question => after last unodered
dragEvent.dataTransfer.dropEffect = 'move'
......@@ -116,7 +122,7 @@ export default {
onDragLeave(event, index) {
this.$emit('hideDrop')
let element
const positionDest = this.question.positions[this.$store.state.themes.curr_theme_id]
const positionDest = this.question.getPositionInTheme(this.$store.state.themes.curr_theme_id);
if (positionDest === null)
element = this.$parent.findLastUnordered().$el
else
......
......@@ -5,9 +5,9 @@
|{{ question.validee ? 'validée' : (question.avalider ? 'en attente de validation' : 'en cours de rédaction') }})&nbsp;
.themes Themes&nbsp;:
div.link(v-for="theme_id in question.themes" @click="gotoTheme(theme_id)") {{ themePath(theme_id) }}
span(:class="{atrier: question.positions[theme_id] === null}")
|{{ question.positions[theme_id] !== null ? ('(position: ' + question.positions[theme_id] + ')') : '(non positionnée)' }}
div.link(v-for="question_order in question.themes" @click="gotoTheme(question_order.theme)") {{ themePath(question_order.theme) }}
span(:class="{atrier: question_order.position === null}")
|{{ question_order.position !== null ? ('(position: ' + question_order.position + ')') : '(non positionnée)' }}
</template>
<script>
......
......@@ -39,13 +39,12 @@ const getters = {
questionSaveStatus: (state) => {
return state.questionSaveStatus.status
},
}
// mutations
const mutations = {
'QUESTION'(state, data) {
data.getPositionInTheme = getQuestionPositionInTheme;
Vue.set(state.questions, data.id, data)
},
'QUESTION_SAVE_INIT'(state) {
......@@ -82,18 +81,21 @@ const actions = {
},
// data = {oldQuestion:..., newQuestion:...}
questionChanged({commit, state}, data) {
for (const theme_id of data.newQuestion.themes) {
if (!data.oldQuestion || !data.oldQuestion.themes.includes(theme_id))
this.dispatch('questions/loadQuestionsOfTheme', theme_id)
for (const index in data.newQuestion.themes) {
if (data.newQuestion.themes.hasOwnProperty(index)) {
const theme = data.newQuestion.themes[index];
if (!data.oldQuestion || data.oldQuestion.getPositionInTheme(theme.theme) !== theme.position)
this.dispatch('questions/loadQuestionsOfTheme', theme.theme)
}
}
if (data.oldQuestion) {
for (const theme_id of data.oldQuestion.themes) {
if (!data.newQuestion.themes.includes(theme_id)) {
this.dispatch('questions/loadQuestionsOfTheme', theme_id)
for (const index in data.oldQuestion.themes.values()) {
if (data.oldQuestion.themes.hasOwnProperty(index)) {
const theme = data.oldQuestion.themes[index];
this.dispatch('questions/loadQuestionsOfTheme', theme.theme);
}
}
}
},
/**
*
......@@ -162,4 +164,15 @@ export default {
getters,
actions,
mutations,
}
function getQuestionPositionInTheme(theme_id) {
for (let index in this.themes) {
if (this.themes.hasOwnProperty(index)) {
const question_order = this.themes[index]
if (question_order.theme === theme_id)
return question_order.position;
}
}
return null;
}
\ No newline at end of file
......@@ -80,6 +80,12 @@ const actions = {
}
},
getNextPosition({commit}, {theme_id, position, before=false}) {
return $axios.get("api/theme/" + theme_id + '/get_next_position/?position=' + position + (before ? '&before':''))
.then(response => { return response.data})
.catch(error => console.error(error));
},
loadTheme({commit, state}, theme_id) {
axios.get('api/theme/' + theme_id + '/')
.then(response => {
......@@ -87,6 +93,7 @@ const actions = {
})
.catch(error => console.error(error))
},
loadThemes({commit, state}) {
$axios.get("api/theme/")
.then(response => {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment