Création de l'application de faq
This commit is contained in:
parent
f63dc2db55
commit
a4cadca34c
15 changed files with 348 additions and 3 deletions
0
faqs/__init__.py
Normal file
0
faqs/__init__.py
Normal file
28
faqs/forms.py
Normal file
28
faqs/forms.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from translated_fields import language_code_formfield_callback
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .models import Faq
|
||||||
|
|
||||||
|
|
||||||
|
class FaqForm(forms.ModelForm):
|
||||||
|
formfield_callback = language_code_formfield_callback
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Faq
|
||||||
|
fields = [
|
||||||
|
*Faq.title.fields,
|
||||||
|
"anchor",
|
||||||
|
*Faq.description.fields,
|
||||||
|
*Faq.content.fields,
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"description_en": forms.Textarea(
|
||||||
|
attrs={"rows": 4, "class": "is-family-monospace"}
|
||||||
|
),
|
||||||
|
"description_fr": forms.Textarea(
|
||||||
|
attrs={"rows": 4, "class": "is-family-monospace"}
|
||||||
|
),
|
||||||
|
"content_en": forms.Textarea(attrs={"class": "is-family-monospace"}),
|
||||||
|
"content_fr": forms.Textarea(attrs={"class": "is-family-monospace"}),
|
||||||
|
}
|
66
faqs/migrations/0001_initial.py
Normal file
66
faqs/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated by Django 3.2.4 on 2021-06-15 08:34
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Faq",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title_fr", models.CharField(max_length=255, verbose_name="titre")),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(blank=True, max_length=255, verbose_name="titre"),
|
||||||
|
),
|
||||||
|
("description_fr", models.TextField(verbose_name="description")),
|
||||||
|
(
|
||||||
|
"description_en",
|
||||||
|
models.TextField(blank=True, verbose_name="description"),
|
||||||
|
),
|
||||||
|
("content_fr", models.TextField(blank=True, verbose_name="contenu")),
|
||||||
|
("content_en", models.TextField(blank=True, verbose_name="contenu")),
|
||||||
|
(
|
||||||
|
"last_modified",
|
||||||
|
models.DateField(auto_now=True, verbose_name="mise à jour"),
|
||||||
|
),
|
||||||
|
("anchor", models.CharField(max_length=20, verbose_name="ancre")),
|
||||||
|
(
|
||||||
|
"author",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="faqs",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"permissions": [("is_author", "Can create faqs")],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="faq",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("anchor",), name="unique_faq_anchor"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
faqs/migrations/__init__.py
Normal file
0
faqs/migrations/__init__.py
Normal file
14
faqs/mixins.py
Normal file
14
faqs/mixins.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
|
class AdminOnlyMixin(PermissionRequiredMixin):
|
||||||
|
"""Restreint l'accès aux admins"""
|
||||||
|
|
||||||
|
permission_required = "faqs.is_author"
|
||||||
|
|
||||||
|
|
||||||
|
class CreatorOnlyMixin(AdminOnlyMixin):
|
||||||
|
"""Restreint l'accès à l'auteur"""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(author=self.request.user)
|
32
faqs/models.py
Normal file
32
faqs/models.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from translated_fields import TranslatedFieldWithFallback
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class Faq(models.Model):
|
||||||
|
title = TranslatedFieldWithFallback(
|
||||||
|
models.CharField(_("titre"), blank=False, max_length=255)
|
||||||
|
)
|
||||||
|
description = TranslatedFieldWithFallback(
|
||||||
|
models.TextField(_("description"), blank=False)
|
||||||
|
)
|
||||||
|
content = TranslatedFieldWithFallback(models.TextField(_("contenu"), blank=True))
|
||||||
|
|
||||||
|
author = models.ForeignKey(
|
||||||
|
User, related_name="faqs", null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
last_modified = models.DateField(_("mise à jour"), auto_now=True)
|
||||||
|
|
||||||
|
anchor = models.CharField(_("ancre"), max_length=20)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = [
|
||||||
|
("is_author", "Can create faqs"),
|
||||||
|
]
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["anchor"], name="unique_faq_anchor")
|
||||||
|
]
|
43
faqs/templates/faqs/faq.html
Normal file
43
faqs/templates/faqs/faq.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n markdown %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="level">
|
||||||
|
{# Titre de la FAQ #}
|
||||||
|
<div class="level-left is-flex-shrink-1">
|
||||||
|
<h1 class="title">{{ faq.title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-right">
|
||||||
|
{# Date de dernière modification #}
|
||||||
|
<div class="level-item">
|
||||||
|
<span class="tag is-primary is-light is-outlined">{% blocktrans with maj=faq.last_modified|date:'d/m/Y' %}Mis à jour le {{ maj }}{% endblocktrans %}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Lien vers la page d'édition #}
|
||||||
|
{% if faq.author == user %}
|
||||||
|
<div class="level-item">
|
||||||
|
<a class="button has-tooltip-primary" href="{% url 'faq.edit' faq.anchor %}" data-tooltip="{% trans "Modifier" %}">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{# Description #}
|
||||||
|
<div class="message is-primary">
|
||||||
|
<div class="message-body content">{{ faq.description|markdown|safe }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Contenu #}
|
||||||
|
<div class="content">
|
||||||
|
{{ faq.content|markdown|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
20
faqs/templates/faqs/faq_create.html
Normal file
20
faqs/templates/faqs/faq_create.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h1 class="title">{% trans "Nouvelle FAQ" %}</h1>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% url 'faq.list' as r_url %}
|
||||||
|
{% include "forms/common-form.html" with c_size="is-9" errors=False %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
20
faqs/templates/faqs/faq_edit.html
Normal file
20
faqs/templates/faqs/faq_edit.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h1 class="title">{% trans "Modification de la FAQ" %}</h1>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% url 'faq.view' faq.anchor as r_url %}
|
||||||
|
{% include "forms/common-form.html" with c_size="is-9" errors=False %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
48
faqs/templates/faqs/faq_list.html
Normal file
48
faqs/templates/faqs/faq_list.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n markdown %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<h1 class="title">{% trans "Liste des FAQ" %}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if perms.faqs.is_author %}
|
||||||
|
<div class="level-right">
|
||||||
|
<div class="level-item">
|
||||||
|
<a class="button is-light is-outlined is-primary" href={% url 'faq.create' %}>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</span>
|
||||||
|
<span>{% trans "Créer une FAQ" %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% for f in faq_list %}
|
||||||
|
<div class="panel is-primary is-radiusless">
|
||||||
|
<div class="panel-heading is-size-6 is-radiusless">
|
||||||
|
<a class="has-text-primary-light" href="{% url 'faq.view' f.anchor %}"><u>{{ f.title }}</u></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if f.description %}
|
||||||
|
<div class="panel-block">
|
||||||
|
<div class="content is-flex-grow-1">
|
||||||
|
{{ f.description|markdown|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if not forloop.last %}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endblock %}
|
12
faqs/urls.py
Normal file
12
faqs/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Admin views
|
||||||
|
path("create", views.FaqCreateView.as_view(), name="faq.create"),
|
||||||
|
path("edit/<slug:slug>", views.FaqEditView.as_view(), name="faq.edit"),
|
||||||
|
# Public views
|
||||||
|
path("", views.FaqListView.as_view(), name="faq.list"),
|
||||||
|
path("view/<slug:slug>", views.FaqView.as_view(), name="faq.view"),
|
||||||
|
]
|
54
faqs/views.py
Normal file
54
faqs/views.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import CreateView, DetailView, ListView, UpdateView
|
||||||
|
|
||||||
|
from .forms import FaqForm
|
||||||
|
from .mixins import AdminOnlyMixin, CreatorOnlyMixin
|
||||||
|
from .models import Faq
|
||||||
|
|
||||||
|
# #############################################################################
|
||||||
|
# Administration Views
|
||||||
|
# #############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class FaqCreateView(AdminOnlyMixin, SuccessMessageMixin, CreateView):
|
||||||
|
model = Faq
|
||||||
|
form_class = FaqForm
|
||||||
|
success_message = _("Faq créée avec succès !")
|
||||||
|
template_name = "faqs/faq_create.html"
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse("faq.view", args=[self.object.anchor])
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.author = self.request.user
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class FaqEditView(CreatorOnlyMixin, SuccessMessageMixin, UpdateView):
|
||||||
|
model = Faq
|
||||||
|
form_class = FaqForm
|
||||||
|
slug_field = "anchor"
|
||||||
|
success_message = _("Faq modifiée avec succès !")
|
||||||
|
template_name = "faqs/faq_edit.html"
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse("faq.view", args=[self.object.anchor])
|
||||||
|
|
||||||
|
|
||||||
|
# #############################################################################
|
||||||
|
# Public Views
|
||||||
|
# #############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class FaqListView(ListView):
|
||||||
|
model = Faq
|
||||||
|
template_name = "faqs/faq_list.html"
|
||||||
|
|
||||||
|
|
||||||
|
class FaqView(DetailView):
|
||||||
|
model = Faq
|
||||||
|
template_name = "faqs/faq.html"
|
||||||
|
slug_field = "anchor"
|
|
@ -55,6 +55,7 @@ INSTALLED_APPS = [
|
||||||
"kadenios.apps.IgnoreSrcStaticFilesConfig",
|
"kadenios.apps.IgnoreSrcStaticFilesConfig",
|
||||||
"shared",
|
"shared",
|
||||||
"elections",
|
"elections",
|
||||||
|
"faqs",
|
||||||
"authens",
|
"authens",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ urlpatterns = [
|
||||||
path("", HomeView.as_view(), name="kadenios"),
|
path("", HomeView.as_view(), name="kadenios"),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("elections/", include("elections.urls")),
|
path("elections/", include("elections.urls")),
|
||||||
|
path("faqs/", include("faqs.urls")),
|
||||||
path("auth/", include("shared.auth.urls")),
|
path("auth/", include("shared.auth.urls")),
|
||||||
path("authens/", include("authens.urls")),
|
path("authens/", include("authens.urls")),
|
||||||
path("i18n/", include("django.conf.urls.i18n")),
|
path("i18n/", include("django.conf.urls.i18n")),
|
||||||
|
|
|
@ -112,16 +112,22 @@
|
||||||
<nav class="level has-background-primary">
|
<nav class="level has-background-primary">
|
||||||
<div class="level-left px-4">
|
<div class="level-left px-4">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<a href="{% url "kadenios" %}">
|
<a href="{% url 'kadenios' %}">
|
||||||
<h1 class="has-text-primary-light is-size-1 is-family-secondary">Kadenios</h1>
|
<h1 class="has-text-primary-light is-size-1 is-family-secondary">Kadenios</h1>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="level-item px-4">
|
<div class="level-item pl-4">
|
||||||
<a href="{% url "election.list" %}">
|
<a href="{% url 'election.list' %}">
|
||||||
<h3 class="has-text-primary-light has-text-weight-semibold is-size-3">{% trans "Élections" %}</h3>
|
<h3 class="has-text-primary-light has-text-weight-semibold is-size-3">{% trans "Élections" %}</h3>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item pl-4">
|
||||||
|
<a href="{% url 'faq.list' %}">
|
||||||
|
<h3 class="has-text-primary-light has-text-weight-semibold is-size-3">{% trans "FAQ" %}</h3>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="level-right px-5">
|
<div class="level-right px-5">
|
||||||
|
|
Loading…
Reference in a new issue