forked from DGNum/gestiojeux
Add the game suggestion system
This commit is contained in:
parent
0ad6c7543e
commit
88a6f06f0d
17 changed files with 505 additions and 1 deletions
|
@ -32,6 +32,7 @@ INSTALLED_APPS = [
|
|||
"accounts",
|
||||
"comments",
|
||||
"inventory",
|
||||
"suggestions",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
|
|
@ -22,6 +22,7 @@ urlpatterns = [
|
|||
path("admin/", admin.site.urls),
|
||||
path("markdownx/", include("markdownx.urls")),
|
||||
path("inventory/", include("inventory.urls")),
|
||||
path("suggestions/", include("suggestions.urls")),
|
||||
path("account/", include("accounts.urls")),
|
||||
path("", include("website.urls")),
|
||||
]
|
||||
|
|
1
suggestions/__init__.py
Normal file
1
suggestions/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
default_app_config = "suggestions.apps.SuggestionsConfig"
|
32
suggestions/admin.py
Normal file
32
suggestions/admin.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from django.contrib import admin
|
||||
from .models import Suggestion, SuggestionComment
|
||||
from comments.admin import CommentAdmin
|
||||
|
||||
|
||||
class SuggestionAdmin(admin.ModelAdmin):
|
||||
exclude = ("upvoting_users",)
|
||||
list_display = ("title", "num_upvotes", "price")
|
||||
actions = ["reset_upvotes"]
|
||||
|
||||
def num_upvotes(self, obj):
|
||||
return obj.upvoting_users.all().count()
|
||||
|
||||
num_upvotes.short_description = "Nombre de votes"
|
||||
|
||||
def reset_upvotes(self, request, queryset):
|
||||
SuggestionVote = Suggestion.upvoting_users.through
|
||||
SuggestionVote.objects.filter(suggestion__in=queryset).delete()
|
||||
self.message_user(
|
||||
request,
|
||||
"Les votes pour {} suggestions ont été réinitialisés".format(
|
||||
queryset.count()
|
||||
),
|
||||
)
|
||||
|
||||
reset_upvotes.short_description = (
|
||||
"Remettre à zero les votes pour les suggestions sélectionnées"
|
||||
)
|
||||
|
||||
|
||||
admin.site.register(Suggestion, SuggestionAdmin)
|
||||
admin.site.register(SuggestionComment, CommentAdmin)
|
5
suggestions/apps.py
Normal file
5
suggestions/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SuggestionsConfig(AppConfig):
|
||||
name = 'suggestions'
|
30
suggestions/forms.py
Normal file
30
suggestions/forms.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from django import forms
|
||||
from .models import Suggestion
|
||||
|
||||
|
||||
class SuggestionForm(forms.ModelForm):
|
||||
error_css_class = "errorfield"
|
||||
required_css_class = "requiredfield"
|
||||
|
||||
class Meta:
|
||||
model = Suggestion
|
||||
fields = [
|
||||
"title",
|
||||
"price",
|
||||
"buy_link",
|
||||
"nb_player_min",
|
||||
"nb_player_max",
|
||||
"player_range_precisions",
|
||||
"duration",
|
||||
"category",
|
||||
"tags",
|
||||
"game_designer",
|
||||
"illustrator",
|
||||
"editor",
|
||||
"image",
|
||||
"description",
|
||||
]
|
||||
|
||||
|
||||
class AddSuggestionForm(SuggestionForm):
|
||||
comment = forms.CharField(widget=forms.Textarea, label="Commentaire personnel")
|
64
suggestions/migrations/0001_initial.py
Normal file
64
suggestions/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Generated by Django 3.1.2 on 2020-12-29 23:43
|
||||
|
||||
import autoslug.fields
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import website.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Suggestion',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=256, unique=True, verbose_name='titre du jeu')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=6, validators=[django.core.validators.MinValueValidator(0)], verbose_name='prix en euros')),
|
||||
('buy_link', models.URLField(verbose_name="lien vers un site d'achat")),
|
||||
('nb_player_min', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s minimum')),
|
||||
('nb_player_max', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s maximum')),
|
||||
('player_range_precisions', models.CharField(blank=True, help_text='Pour indiquer une éventuelle contrainte (ex. parité) ou information sur le nombre de joueur·se·s', max_length=256, verbose_name='précisions sur le nombre de joueur·se·s')),
|
||||
('duration', models.CharField(max_length=256, verbose_name='durée de partie')),
|
||||
('game_designer', models.CharField(blank=True, max_length=256, verbose_name='game designer')),
|
||||
('illustrator', models.CharField(blank=True, max_length=256, verbose_name='illustrateur·trice')),
|
||||
('editor', models.CharField(blank=True, max_length=256, verbose_name='éditeur')),
|
||||
('description', models.TextField(blank=True, help_text="Peut correspondre à celle de l'éditeur et ne doit pas contenir d'avis personnel", verbose_name='description')),
|
||||
('image', models.ImageField(blank=True, help_text='Image du jeu de moins de 512 Kio à téléverser (par exemple une photo de sa boite)', upload_to='suggestion_img/', validators=[website.validators.MaxFileSizeValidator(512)], verbose_name='image')),
|
||||
('category', models.ForeignKey(blank=True, help_text='Idée de catégorie dans laquelle ranger ce jeu', null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.category', verbose_name='catégorie')),
|
||||
('tags', models.ManyToManyField(blank=True, help_text="Vous pouvez en sélectionner plusieurs ou aucun (sur ordinateur Ctrl+Clic change l'état de selection d'un tag)", to='inventory.Tag', verbose_name='tags qui correspondent à ce jeu')),
|
||||
('upvoting_users', models.ManyToManyField(blank=True, related_name='upvoted_suggestions', to=settings.AUTH_USER_MODEL, verbose_name='personnes intéressées')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'suggestion de jeu',
|
||||
'verbose_name_plural': 'suggestions de jeux',
|
||||
'ordering': ['title'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SuggestionComment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('text', models.TextField(verbose_name='texte')),
|
||||
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='date de publication')),
|
||||
('modified_on', models.DateTimeField(auto_now=True, verbose_name='date de modification')),
|
||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='auteur·ice')),
|
||||
('commented_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='suggestions.suggestion', verbose_name='suggestion')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'commentaire sur une suggestion',
|
||||
'verbose_name_plural': 'commentaires sur des suggestions',
|
||||
'ordering': ['created_on'],
|
||||
},
|
||||
),
|
||||
]
|
0
suggestions/migrations/__init__.py
Normal file
0
suggestions/migrations/__init__.py
Normal file
133
suggestions/models.py
Normal file
133
suggestions/models.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
from autoslug import AutoSlugField
|
||||
from website.validators import MaxFileSizeValidator
|
||||
from accounts.models import User
|
||||
from inventory.models import Category, Tag
|
||||
from comments.models import AbstractComment
|
||||
|
||||
|
||||
class Suggestion(models.Model):
|
||||
title = models.CharField(verbose_name="titre du jeu", max_length=256, unique=True)
|
||||
slug = AutoSlugField(populate_from="title", unique=True)
|
||||
|
||||
price = models.DecimalField(
|
||||
max_digits=6,
|
||||
decimal_places=2,
|
||||
verbose_name="prix en euros",
|
||||
validators=[MinValueValidator(0)],
|
||||
)
|
||||
buy_link = models.URLField(verbose_name="lien vers un site d'achat")
|
||||
|
||||
nb_player_min = models.PositiveSmallIntegerField(
|
||||
verbose_name="nombre de joueur·se·s minimum"
|
||||
)
|
||||
nb_player_max = models.PositiveSmallIntegerField(
|
||||
verbose_name="nombre de joueur·se·s maximum"
|
||||
)
|
||||
player_range_precisions = models.CharField(
|
||||
max_length=256,
|
||||
blank=True,
|
||||
verbose_name="précisions sur le nombre de joueur·se·s",
|
||||
help_text="Pour indiquer une éventuelle contrainte (ex. parité) ou information sur le nombre de joueur·se·s",
|
||||
)
|
||||
|
||||
duration = models.CharField(max_length=256, verbose_name="durée de partie")
|
||||
|
||||
game_designer = models.CharField(
|
||||
max_length=256, blank=True, verbose_name="game designer"
|
||||
)
|
||||
illustrator = models.CharField(
|
||||
max_length=256, blank=True, verbose_name="illustrateur·trice"
|
||||
)
|
||||
editor = models.CharField(max_length=256, blank=True, verbose_name="éditeur")
|
||||
|
||||
category = models.ForeignKey(
|
||||
Category,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="catégorie",
|
||||
help_text="Idée de catégorie dans laquelle ranger ce jeu",
|
||||
)
|
||||
tags = models.ManyToManyField(
|
||||
Tag,
|
||||
blank=True,
|
||||
verbose_name="tags qui correspondent à ce jeu",
|
||||
help_text="Vous pouvez en sélectionner plusieurs ou aucun (sur ordinateur Ctrl+Clic change l'état de selection d'un tag)",
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
verbose_name="description",
|
||||
help_text="Peut correspondre à celle de l'éditeur et ne doit pas contenir d'avis personnel",
|
||||
)
|
||||
|
||||
image = models.ImageField(
|
||||
upload_to="suggestion_img/",
|
||||
blank=True,
|
||||
verbose_name="image",
|
||||
help_text="Image du jeu de moins de 512 Kio à téléverser (par exemple une photo de sa boite)",
|
||||
validators=[MaxFileSizeValidator(512)],
|
||||
)
|
||||
|
||||
upvoting_users = models.ManyToManyField(
|
||||
User,
|
||||
blank=True,
|
||||
related_name="upvoted_suggestions",
|
||||
verbose_name="personnes intéressées",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["title"]
|
||||
verbose_name = "suggestion de jeu"
|
||||
verbose_name_plural = "suggestions de jeux"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def clean(self):
|
||||
if not self.nb_player_min or not self.nb_player_max:
|
||||
return
|
||||
if self.nb_player_min > self.nb_player_max:
|
||||
raise ValidationError(
|
||||
{
|
||||
"nb_player_max": "Le nombre de joueur·se·s maximum doit être supérieur au nombre de joueur·se·s minimum"
|
||||
}
|
||||
)
|
||||
|
||||
def get_player_range(self):
|
||||
precisions = ""
|
||||
if self.player_range_precisions:
|
||||
precisions = " ({})".format(self.player_range_precisions)
|
||||
if self.nb_player_min != self.nb_player_max:
|
||||
return "{} à {} joueur·se·s{}".format(
|
||||
self.nb_player_min, self.nb_player_max, precisions
|
||||
)
|
||||
else:
|
||||
return "{} joueur·se·s{}".format(self.nb_player_min, precisions)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("suggestions:suggestion", args=(self.slug,))
|
||||
|
||||
|
||||
class SuggestionComment(AbstractComment):
|
||||
commented_object = models.ForeignKey(
|
||||
Suggestion,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="comments",
|
||||
verbose_name="suggestion",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["created_on"]
|
||||
verbose_name = "commentaire sur une suggestion"
|
||||
verbose_name_plural = "commentaires sur des suggestions"
|
||||
|
||||
def get_modification_url(self):
|
||||
return reverse(
|
||||
"suggestions:modify_suggestion_comment",
|
||||
args=(self.commented_object.slug, self.id),
|
||||
)
|
10
suggestions/templates/suggestions/add_suggestion.html
Normal file
10
suggestions/templates/suggestions/add_suggestion.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>Ajout d'une suggestion</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Envoyer la suggestion</button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,26 @@
|
|||
<a class="inventory_item suggestion" href="{{ suggestion.get_absolute_url }}">
|
||||
<span class="title">{{ suggestion.title }}</span>
|
||||
<span class="details">
|
||||
<span><i class="fa fa-fw fa-thumbs-up" aria-hidden="true"></i> {{ suggestion.upvoting_users.count }}</span>
|
||||
<span><i class="fa fa-fw fa-money" aria-hidden="true"></i> {{ suggestion.price }} €</span>
|
||||
<span><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ suggestion.get_player_range }}</span>
|
||||
{% if suggestion.duration %}
|
||||
<span><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ suggestion.duration }}</span>
|
||||
{% endif %}
|
||||
{% if suggestion.category %}
|
||||
<span><i class="fa fa-fw fa-bookmark"></i> {{ suggestion.category }}</span>
|
||||
{% endif %}
|
||||
{% for tag in suggestion.tags.all %}
|
||||
<span><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</span>
|
||||
{% endfor %}
|
||||
{% if suggestion.game_designer %}
|
||||
<span><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer }}</span>
|
||||
{% endif %}
|
||||
{% if suggestion.illustrator %}
|
||||
<span><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator }}</span>
|
||||
{% endif %}
|
||||
{% if suggestion.editor %}
|
||||
<span><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ suggestion.editor }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
58
suggestions/templates/suggestions/suggestion.html
Normal file
58
suggestions/templates/suggestions/suggestion.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>{{ suggestion.title }}</h1>
|
||||
|
||||
<div id="game_infos">
|
||||
{% if suggestion.image %}
|
||||
<img src="{{ suggestion.image.url }}" alt="{{ suggestion.title }}"/>
|
||||
{% endif %}
|
||||
|
||||
<div id="details">
|
||||
<p><i class="fa fa-fw fa-money" aria-hidden="true"></i> <a href="{{ suggestion.buy_link }}" rel="nofollow">{{ suggestion.price }} €</a></p>
|
||||
<hr/>
|
||||
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ suggestion.get_player_range }}</p>
|
||||
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ suggestion.duration|default:"(Durée de jeu inconnue)" }}
|
||||
<hr/>
|
||||
<p><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> {% if suggestion.category %}<a href="{{ suggestion.category.get_absolute_url }}">{{ suggestion.category }}</a>{% else %}(Pas de catégorie){% endif %}</p>
|
||||
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
|
||||
{% for tag in suggestion.tags.all %}
|
||||
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% empty %}
|
||||
(Aucun tag)
|
||||
{% endfor %}
|
||||
</p>
|
||||
<hr/>
|
||||
<p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer|default:"(Game designer inconnu·e)" }}</li>
|
||||
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator|default:"(Illustrateur·trice inconnu·e)" }}</li>
|
||||
<p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ suggestion.editor|default:"(Éditeur inconnu)" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Votes pour cette suggestion</h2>
|
||||
{% if suggestion.upvoting_users.count %}
|
||||
<p>Actuellement, {{ suggestion.upvoting_users.count }} {{ suggestion.upvoting_users.count|pluralize:"personne aimerait,personnes aimeraient" }} avoir ce jeu dans la ludothèque.</p>
|
||||
{% else %}
|
||||
<p>Pour l'instant, personne n'est intéressé par ce jeu.</p>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if request.user in suggestion.upvoting_users.all %}
|
||||
<p>Vous faites partie des personnes intéressées.</p>
|
||||
<a class="button" href="{% url "suggestions:downvote_suggestion" suggestion.slug %}"><i class="fa fa-thumbs-down" aria-hidden="true"></i> Annuler mon vote pour l'achat de ce jeu</a>
|
||||
{% else %}
|
||||
<a class="button" href="{% url "suggestions:upvote_suggestion" suggestion.slug %}"><i class="fa fa-thumbs-up" aria-hidden="true"></i> Voter pour acheter ce jeu</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p><a href="{% url "accounts:login" %}?next={{ request.get_full_path }}">Connectez-vous</a> pour voter pour une suggestion.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if suggestion.description %}
|
||||
<h2 id="description">Description</h2>
|
||||
{{ suggestion.description|linebreaks }}
|
||||
{% endif %}
|
||||
|
||||
<h2>Avis et commentaires</h2>
|
||||
{% url "suggestions:add_suggestion_comment" suggestion.slug as add_comment_url %}
|
||||
{% include "comments.html" with comments=suggestion.comments.all %}
|
||||
{% endblock %}
|
17
suggestions/templates/suggestions/suggestion_list.html
Normal file
17
suggestions/templates/suggestions/suggestion_list.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>Liste des suggestions</h1>
|
||||
<p>Ceci est la liste des achats suggérés par les membres du club jeux pour compléter la ludothèque.</p>
|
||||
{% for suggestion in suggestion_list %}
|
||||
{% include "./partials/suggestion_item.html" %}
|
||||
{% empty %}
|
||||
<p>(Aucune suggestion pour le moment)</p>
|
||||
{% endfor %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<a class="button" href="{% url "suggestions:add_suggestion" %}"><i class="fa fa-plus" aria-hidden="true"></i> Ajouter une suggestion</a>
|
||||
{% else %}
|
||||
<p><a href="{% url "accounts:login" %}?next={{ request.get_full_path }}">Connectez-vous</a> pour ajouter une suggestion.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
3
suggestions/tests.py
Normal file
3
suggestions/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
36
suggestions/urls.py
Normal file
36
suggestions/urls.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from django.urls import path
|
||||
from .views import (
|
||||
SuggestionListView,
|
||||
AddSuggestionView,
|
||||
SuggestionView,
|
||||
UpvoteSuggestionView,
|
||||
DownvoteSuggestionView,
|
||||
AddSuggestionCommentView,
|
||||
ModifySuggestionCommentView,
|
||||
)
|
||||
|
||||
app_name = "suggestions"
|
||||
|
||||
urlpatterns = [
|
||||
path("", SuggestionListView.as_view(), name="suggestions"),
|
||||
path("add/", AddSuggestionView.as_view(), name="add_suggestion"),
|
||||
path("item/<slug>/", SuggestionView.as_view(), name="suggestion"),
|
||||
path(
|
||||
"item/<slug>/upvote", UpvoteSuggestionView.as_view(), name="upvote_suggestion"
|
||||
),
|
||||
path(
|
||||
"item/<slug>/downvote",
|
||||
DownvoteSuggestionView.as_view(),
|
||||
name="downvote_suggestion",
|
||||
),
|
||||
path(
|
||||
"item/<slug>/add_comment",
|
||||
AddSuggestionCommentView.as_view(),
|
||||
name="add_suggestion_comment",
|
||||
),
|
||||
path(
|
||||
"item/<slug>/modify_comment/<int:comment_id>",
|
||||
ModifySuggestionCommentView.as_view(),
|
||||
name="modify_suggestion_comment",
|
||||
),
|
||||
]
|
87
suggestions/views.py
Normal file
87
suggestions/views.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
from django.views.generic import ListView, DetailView, FormView, RedirectView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.db.models import Count
|
||||
from comments.views import AddCommentView, ModifyCommentView
|
||||
from .models import Suggestion, SuggestionComment
|
||||
from .forms import AddSuggestionForm
|
||||
|
||||
|
||||
class SuggestionListView(ListView):
|
||||
model = Suggestion
|
||||
template_name = "suggestions/suggestion_list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return Suggestion.objects.annotate(
|
||||
num_upvotes=Count("upvoting_users")
|
||||
).order_by("-num_upvotes")
|
||||
|
||||
|
||||
class AddSuggestionView(LoginRequiredMixin, FormView):
|
||||
template_name = "suggestions/add_suggestion.html"
|
||||
form_class = AddSuggestionForm
|
||||
|
||||
def form_valid(self, form):
|
||||
suggestion = form.save()
|
||||
suggestion.upvoting_users.add(self.request.user)
|
||||
suggestion.comments.create(
|
||||
author=self.request.user, text=form.cleaned_data["comment"]
|
||||
)
|
||||
messages.success(self.request, "Votre suggestion est enregistrée")
|
||||
return redirect("suggestions:suggestion", suggestion.slug)
|
||||
|
||||
|
||||
class SuggestionView(DetailView):
|
||||
model = Suggestion
|
||||
template_name = "suggestions/suggestion.html"
|
||||
|
||||
|
||||
class UpvoteSuggestionView(LoginRequiredMixin, SingleObjectMixin, RedirectView):
|
||||
model = Suggestion
|
||||
pattern_name = "suggestions:suggestion"
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
suggestion = self.get_object()
|
||||
if self.request.user not in suggestion.upvoting_users.all():
|
||||
suggestion.upvoting_users.add(self.request.user)
|
||||
messages.success(
|
||||
self.request, "Votre vote pour cette suggestion est enregistré"
|
||||
)
|
||||
else:
|
||||
messages.error(self.request, "Vous avez déjà voté pour cette suggestion")
|
||||
|
||||
return super().get_redirect_url(*args, **kwargs)
|
||||
|
||||
|
||||
class DownvoteSuggestionView(LoginRequiredMixin, SingleObjectMixin, RedirectView):
|
||||
model = Suggestion
|
||||
pattern_name = "suggestions:suggestion"
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
suggestion = self.get_object()
|
||||
if self.request.user in suggestion.upvoting_users.all():
|
||||
suggestion.upvoting_users.remove(self.request.user)
|
||||
messages.success(
|
||||
self.request, "Votre vote pour cette suggestion a été retiré"
|
||||
)
|
||||
else:
|
||||
messages.error(
|
||||
self.request, "Vous ne votiez déjà pas pour cette suggestion"
|
||||
)
|
||||
|
||||
return super().get_redirect_url(*args, **kwargs)
|
||||
|
||||
|
||||
class AddSuggestionCommentView(AddCommentView):
|
||||
model = Suggestion
|
||||
comment_model = SuggestionComment
|
||||
pattern_name = "suggestions:suggestion"
|
||||
|
||||
|
||||
class ModifySuggestionCommentView(ModifyCommentView):
|
||||
model = Suggestion
|
||||
comment_model = SuggestionComment
|
||||
template_name = "suggestions/suggestion.html"
|
||||
success_pattern_name = "suggestions:suggestion"
|
|
@ -10,7 +10,7 @@
|
|||
<div>
|
||||
<a {% if url_name == "home" %}class="current"{% endif %} href="{% url "website:home" %}"><i class="fa fa-home" aria-hidden="true"></i></a>
|
||||
<a {% if url_name == "inventory" %}class="current"{% endif %} href="{% url "inventory:inventory" %}">Inventaire</a>
|
||||
<a {% if url_name == "suggestions" %}class="current"{% endif %} href="">Suggestions</a>
|
||||
<a {% if url_name == "suggestions" %}class="current"{% endif %} href="{% url "suggestions:suggestions" %}">Suggestions</a>
|
||||
{% if request.user.is_staff %}
|
||||
<a href="{% url "admin:index" %}">Admin</a>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue