Création de l'application de faq

This commit is contained in:
Tom Hubrecht 2021-06-15 11:41:04 +02:00
parent f63dc2db55
commit a4cadca34c
15 changed files with 348 additions and 3 deletions

0
faqs/__init__.py Normal file
View file

28
faqs/forms.py Normal file
View 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"}),
}

View 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"
),
),
]

View file

14
faqs/mixins.py Normal file
View 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
View 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")
]

View 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 %}

View 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 %}

View 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 %}

View 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
View 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
View 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"

View file

@ -55,6 +55,7 @@ INSTALLED_APPS = [
"kadenios.apps.IgnoreSrcStaticFilesConfig", "kadenios.apps.IgnoreSrcStaticFilesConfig",
"shared", "shared",
"elections", "elections",
"faqs",
"authens", "authens",
] ]

View file

@ -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")),

View file

@ -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">