feat(core): Ajoute les nouveaux modèles de fiches Réponse + permet leur saisie depuis le ModelAdmin des fiches Question

main
Raphaël Charles 2023-05-17 20:57:25 +02:00
Parent c5d6953564
révision eea8773cfa
8 fichiers modifiés avec 242 ajouts et 25 suppressions

Voir le fichier

@ -0,0 +1,73 @@
'use strict';
{
function initTinyMCE(el) {
if (el.closest('.empty-form') === null) { // Don't do empty inlines
var mce_conf = JSON.parse(el.dataset.mceConf);
// There is no way to pass a JavaScript function as an option
// because all options are serialized as JSON.
const fns = [
'color_picker_callback',
'file_browser_callback',
'file_picker_callback',
'images_dataimg_filter',
'images_upload_handler',
'paste_postprocess',
'paste_preprocess',
'setup',
'urlconverter_callback',
];
fns.forEach((fn_name) => {
if (typeof mce_conf[fn_name] != 'undefined') {
if (mce_conf[fn_name].includes('(')) {
mce_conf[fn_name] = eval('(' + mce_conf[fn_name] + ')');
}
else {
mce_conf[fn_name] = window[mce_conf[fn_name]];
}
}
});
const id = el.id;
if ('elements' in mce_conf && mce_conf['mode'] == 'exact') {
mce_conf['elements'] = id;
}
if (el.dataset.mceGzConf) {
tinyMCE_GZ.init(JSON.parse(el.dataset.mceGzConf));
}
if (!tinyMCE.get(id)) {
tinyMCE.init(mce_conf);
}
}
}
function initializeTinyMCE(element, formsetName) {
Array.from(element.querySelectorAll('.tinymce')).forEach(area => initTinyMCE(area));
}
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function () {
// Au clic sur les boutons d'ajout des InlinePanel, on recharge TinyMCE pour que l'éditeur s'affiche correctement.
const addAgir = document.getElementById('id_fiches_agir-ADD');
const addComprendre = document.getElementById('id_fiches_comprendre-ADD');
if (addAgir !== null) {
addAgir.addEventListener('click', () => {
initializeTinyMCE(document);
});
}
if (addComprendre !== null) {
addComprendre.addEventListener('click', () => {
initializeTinyMCE(document);
});
}
});
}

Voir le fichier

@ -18,6 +18,7 @@ const CONFIG = {
main: ['./assets/js/main.js', './assets/scss/main.scss'],
'file-input-custom': './assets/js/file-input-custom.js',
'words-count': './assets/js/words-count.js',
'reload_tinymce': './assets/js/reload_tinymce.js',
},
// Folders to create aliases for in JavaScript and SCSS

Voir le fichier

@ -1,10 +1,13 @@
from django.forms import Textarea
from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
from wagtail.admin.panels import (
FieldPanel,
FieldRowPanel,
InlinePanel,
MultiFieldPanel,
)
from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup
from tinymce.widgets import TinyMCE
from . import models
@ -24,20 +27,16 @@ class FicheQuestionAdmin(ModelAdmin):
menu_icon = 'form'
panels = [
FieldPanel('question'),
FieldPanel('description', widget=TinyMCE()),
FieldPanel('description'),
FieldPanel('thematique'),
FieldPanel('published'),
]
class FicheMethodeAdmin(ModelAdmin):
model = models.FicheMethode
menu_icon = 'form'
panels = [
FieldPanel('name'),
FieldPanel('fiche_question'),
FieldPanel('contacts'),
FieldPanel('contenu', widget=TinyMCE()),
MultiFieldPanel(
[
InlinePanel('fiches_agir', label="Fiche Agir"),
InlinePanel('fiches_comprendre', label="Fiche Comprendre"),
],
heading="Fiches Réponse",
),
]
@ -49,7 +48,7 @@ class StructureAdmin(ModelAdmin):
FieldPanel('nom'),
MultiFieldPanel(
[
FieldPanel('description', widget=TinyMCE()),
FieldPanel('description'),
FieldPanel('logo'),
],
heading='Identité',
@ -125,7 +124,6 @@ class WWCAdminGroup(ModelAdminGroup):
items = (
ThematiqueAdmin,
FicheQuestionAdmin,
FicheMethodeAdmin,
StructureAdmin,
ContactAdmin,
RessourceAdmin,
@ -133,6 +131,6 @@ class WWCAdminGroup(ModelAdminGroup):
models.SiteSettings.panels = [
FieldPanel('site_description', widget=TinyMCE()),
FieldPanel('questions_description', widget=TinyMCE()),
FieldPanel('site_description'),
FieldPanel('questions_description'),
]

Voir le fichier

@ -4,3 +4,14 @@ from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'wwc.core'
verbose_name = "Core"
def ready(self):
from django.db.models import TextField
from wagtail.admin.forms.models import register_form_field_override
from tinymce.widgets import TinyMCE
register_form_field_override(
TextField, override={'widget': TinyMCE}, exact_class=True
)

Voir le fichier

@ -0,0 +1,62 @@
# Generated by Django 3.2.18 on 2023-05-17 18:54
from django.db import migrations, models
import django.db.models.deletion
import modelcluster.fields
class Migration(migrations.Migration):
dependencies = [
('core', '0006_sitesettings_questions_description'),
]
operations = [
migrations.AlterField(
model_name='fichequestion',
name='description',
field=models.TextField(null=True, verbose_name='Description'),
),
migrations.CreateModel(
name='FicheComprendre',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='titre')),
('contexte', models.TextField(null=True, verbose_name='Contexte de la question')),
('pdv', models.TextField(null=True, verbose_name='Points de vue')),
('eclairages', models.TextField(null=True, verbose_name='Éclairages')),
('portee', models.TextField(null=True, verbose_name='Portée et limites')),
('fiche_question', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='fiches_comprendre', to='core.fichequestion')),
],
options={
'verbose_name': 'Fiche Comprendre',
'verbose_name_plural': 'Fiches Comprendre',
},
),
migrations.CreateModel(
name='FicheAgir',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='titre')),
('situation', models.TextField(null=True, verbose_name='Situation de départ')),
('action', models.TextField(null=True, verbose_name='Action mise en oeuvre')),
('moyens', models.TextField(null=True, verbose_name="Moyens nécessaires à l'action")),
('preconisations', models.TextField(null=True, verbose_name='Préconisations')),
('fiche_question', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='fiches_agir', to='core.fichequestion')),
],
options={
'verbose_name': 'Fiche Agir',
'verbose_name_plural': 'Fiches Agir',
},
),
migrations.AddField(
model_name='ressource',
name='fiche_agir',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ressources', to='core.ficheagir', verbose_name='Ressource(s)'),
),
migrations.AddField(
model_name='ressource',
name='fiche_comprendre',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ressources', to='core.fichecomprendre', verbose_name='Ressource(s)'),
),
]

Voir le fichier

@ -110,9 +110,7 @@ class FicheQuestion(index.Indexed, ClusterableModel):
"""Une fiche qui pose un problème à résoudre"""
question = models.CharField(max_length=255)
description = models.TextField(
"Description",
)
description = models.TextField("Description", null=True)
thematique = models.ManyToManyField(
'Thematique',
blank=True,
@ -141,6 +139,53 @@ class FicheQuestion(index.Indexed, ClusterableModel):
verbose_name_plural = "fiches Question"
class FicheAgir(ClusterableModel):
"""Une fiche qui propose une solution concrète à un problème."""
name = models.CharField("titre", max_length=128)
fiche_question = ParentalKey(
'core.FicheQuestion',
on_delete=models.CASCADE,
related_name='fiches_agir',
)
situation = models.TextField("Situation de départ", null=True)
action = models.TextField("Action mise en oeuvre", null=True)
moyens = models.TextField("Moyens nécessaires à l'action", null=True)
preconisations = models.TextField("Préconisations", null=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Fiche Agir"
verbose_name_plural = "Fiches Agir"
class FicheComprendre(ClusterableModel):
"""
Une fiche qui vise à contextualiser un problème pour mieux le
comprendre.
"""
name = models.CharField("titre", max_length=128)
fiche_question = ParentalKey(
'core.FicheQuestion',
on_delete=models.CASCADE,
related_name='fiches_comprendre',
)
contexte = models.TextField("Contexte de la question", null=True)
pdv = models.TextField("Points de vue", null=True)
eclairages = models.TextField("Éclairages", null=True)
portee = models.TextField("Portée et limites", null=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Fiche Comprendre"
verbose_name_plural = "Fiches Comprendre"
class FicheMethode(ClusterableModel):
"""Une fiche qui propose des solutions à un problème"""
@ -258,6 +303,21 @@ class Ressource(index.Indexed, ClusterableModel):
verbose_name="Thématique(s)",
)
fiche_agir = ParentalKey(
'core.FicheAgir',
blank=True,
null=True,
related_name='ressources',
verbose_name="Ressource(s)",
)
fiche_comprendre = ParentalKey(
'core.FicheComprendre',
blank=True,
null=True,
related_name='ressources',
verbose_name='Ressource(s)',
)
published = models.BooleanField("publié", default=True)
publish_date = models.DateTimeField(
"date de publication",
@ -307,6 +367,11 @@ class Ressource(index.Indexed, ClusterableModel):
"URL",
code='document_and_url',
)
if self.fiche_agir and self.fiche_comprendre:
raise ValidationError(
"Une ressource ne peut être liée qu'à une fiche type Réponse.",
code='too_many_fiches',
)
@register_setting(icon="view")
@ -329,5 +394,5 @@ class SiteSettings(BaseSetting):
help_text=(
"Un ou plusieurs paragraphes qui seront affichés en haut "
"de la page de liste des fiches Question."
)
),
)

Voir le fichier

@ -298,8 +298,14 @@ TINYMCE_DEFAULT_CONFIG = {
"formats": {
"italic": {"inline": 'i'},
},
"plugins": "autolink,link,paste,",
"toolbar": "undo redo | bold italic | link",
"plugins": "autolink,link,lists,paste,",
"toolbar": "undo redo | bold italic | bullist | link",
"branding": False,
"extended_valid_elements": "i[*],em",
}
TINYMCE_EXTRA_MEDIA = {
'js': [
'reload_tinymce.js',
],
}

Voir le fichier

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block title %}Fiche Question "{{ object.question }}"{% endblock %}