Skip to content

Commit

Permalink
feat(questions): new QuestionRelationship model to make links between…
Browse files Browse the repository at this point in the history
… questions (#2009)
  • Loading branch information
raphodn authored May 28, 2024
1 parent 8470174 commit f38fe8c
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 5 deletions.
5 changes: 5 additions & 0 deletions core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
]
QUESTION_ANSWER_CHOICES = [(a, a) for a in QUESTION_ANSWER_CHOICE_LIST]

QUESTION_RELATIONSHIP_CHOICE_LIST = [
"remplacement",
"traduction",
]

QUIZ_RELATIONSHIP_CHOICE_LIST = [
"suivant",
# "précédent",
Expand Down
65 changes: 61 additions & 4 deletions questions/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from io import StringIO

from django.conf import settings
from django.contrib import admin
from django.core import management
from django.utils.html import mark_safe
from fieldsets_with_inlines import FieldsetsInlineMixin
from import_export import fields, resources
from import_export.admin import ImportMixin
from import_export.widgets import ForeignKeyWidget, ManyToManyWidget
Expand All @@ -13,7 +15,7 @@
from core.admin import ExportMixin, admin_site
from core.models import Configuration
from core.utils import notion
from questions.models import Question
from questions.models import Question, QuestionRelationship
from quizs.models import Quiz
from tags.models import Tag

Expand Down Expand Up @@ -87,7 +89,60 @@ class Meta:
report_skipped = False


class QuestionAdmin(ImportMixin, ExportMixin, SimpleHistoryAdmin):
class QuestionRelationshipFromInline(admin.StackedInline): # TabularInline
model = QuestionRelationship
verbose_name = "Question Relationship (sortant)"
fk_name = "from_question"
# autocomplete_fields = ["to_question"]
extra = 0

def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
"""
Hide the current question in the relationship form
Doesn't work with autocomplete_fields
"""
field = super(QuestionRelationshipFromInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == "to_question":
if "object_id" in request.resolver_match.kwargs:
current_question_id = int(request.resolver_match.kwargs["object_id"])
# remove the current question from the list
field.queryset = Question.objects.exclude(id=current_question_id)
# remove the current question's relationship questions
current_question_from_relationships = QuestionRelationship.objects.filter(
from_question__id=current_question_id
).values_list("to_question_id", flat=True)
current_question_to_relationships = QuestionRelationship.objects.filter(
to_question__id=current_question_id
).values_list("from_question_id", flat=True)
field.queryset = field.queryset.exclude(
id__in=(list(current_question_from_relationships) + list(current_question_to_relationships))
)
# order queryset
field.queryset = field.queryset.order_by("-id")
return field

def has_change_permission(self, request, obj=None):
return False


class QuestionRelationshipToInline(admin.StackedInline): # TabularInline
model = QuestionRelationship
verbose_name = "Question Relationship (entrant)"
fk_name = "to_question"
extra = 0
max_num = 0

# def has_add_permission(self, request, obj=None):
# return False

def has_change_permission(self, request, obj=None):
return False

def has_delete_permission(self, request, obj=None):
return False


class QuestionAdmin(ImportMixin, ExportMixin, FieldsetsInlineMixin, SimpleHistoryAdmin):
resource_class = QuestionResource
list_display = [
"id",
Expand Down Expand Up @@ -143,7 +198,7 @@ class QuestionAdmin(ImportMixin, ExportMixin, SimpleHistoryAdmin):
"updated",
]

fieldsets = (
fieldsets_with_inlines = [
(
"Infos de base",
{
Expand Down Expand Up @@ -209,6 +264,8 @@ class QuestionAdmin(ImportMixin, ExportMixin, SimpleHistoryAdmin):
"Validation",
{"fields": ("validation_status", "validator", "validation_date")},
),
QuestionRelationshipFromInline,
QuestionRelationshipToInline,
(
"Stats",
{
Expand All @@ -231,7 +288,7 @@ class QuestionAdmin(ImportMixin, ExportMixin, SimpleHistoryAdmin):
},
),
("Dates", {"fields": ("created", "updated")}),
)
]

change_list_template = "admin/questions/question/change_list_with_import.html"

Expand Down
46 changes: 46 additions & 0 deletions questions/migrations/0030_questionrelationship.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 4.2.9 on 2024-05-28 11:29

import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("questions", "0029_question_author_certify_and_author_agree"),
]

operations = [
migrations.CreateModel(
name="QuestionRelationship",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"status",
models.CharField(
choices=[("remplacement", "remplacement"), ("traduction", "traduction")],
max_length=50,
verbose_name="Relationship type",
),
),
("created", models.DateTimeField(default=django.utils.timezone.now, verbose_name="Creation date")),
("updated", models.DateTimeField(auto_now=True, verbose_name="Last update date")),
(
"from_question",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="from_questions",
to="questions.question",
),
),
(
"to_question",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="to_questions",
to="questions.question",
),
),
],
),
]
51 changes: 51 additions & 0 deletions questions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,54 @@ def question_create_agg_stat_instance(sender, instance, created, **kwargs):
from stats.models import QuestionAggStat

QuestionAggStat.objects.create(question=instance)


class QuestionRelationship(models.Model):
from_question = models.ForeignKey(to=Question, on_delete=models.CASCADE, related_name="from_questions")
to_question = models.ForeignKey(to=Question, on_delete=models.CASCADE, related_name="to_questions")
status = models.CharField(
verbose_name=_("Relationship type"),
max_length=50,
choices=zip(
constants.QUESTION_RELATIONSHIP_CHOICE_LIST,
constants.QUESTION_RELATIONSHIP_CHOICE_LIST,
),
)

created = models.DateTimeField(verbose_name=_("Creation date"), default=timezone.now)
updated = models.DateTimeField(verbose_name=_("Last update date"), auto_now=True)

def __str__(self):
return f"{self.from_question} >>> {self.status} >>> {self.to_question}"

def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)

def clean(self):
"""
Rules on QuestionRelationship
- cannot have the same from & to
- status must be one of the choices
- cannot have 2 relationships between 2 quizs
- cannot have reverse ?
"""
if self.status not in constants.QUESTION_RELATIONSHIP_CHOICE_LIST:
raise ValidationError({"status": "doit être une valeur de la liste"})
if self.from_question_id and self.to_question_id:
if self.from_question_id == self.to_question_id:
raise ValidationError({"to_question": "ne peut pas être la même question"})
# check there isn't any existing relationships # status ?
existing_identical_relationships = QuestionRelationship.objects.filter(
from_question=self.from_question, to_question=self.to_question
)
if len(existing_identical_relationships):
raise ValidationError({"to_question": "il y a déjà une relation avec cette question dans ce sens"})
# check there isn't any existing symmetrical relationships
existing_symmetrical_relationships = QuestionRelationship.objects.filter(
from_question=self.to_question, to_question=self.from_question
)
if len(existing_symmetrical_relationships):
raise ValidationError(
{"to_question": "il y a déjà une relation avec cette question dans l'autre sens"}
)
2 changes: 1 addition & 1 deletion quizs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ def status_full(self, quiz_id=None) -> str:
def clean(self):
"""
Rules on QuizRelationship
- cannot have the same from_quiz & to_quiz
- cannot have the same from & to
- status must be one of the choices
- cannot have 2 relationships between 2 quizs
- cannot have reverse ?
Expand Down

0 comments on commit f38fe8c

Please sign in to comment.