all features OK, cleaned up (flake8) code except migrations files

This commit is contained in:
2025-05-06 10:38:27 +02:00
parent 29192378c2
commit 5227bf42b2
62 changed files with 3024 additions and 284 deletions

View File

@@ -1,7 +1,9 @@
from django.contrib import admin
from authentication.models import User
from reviews.models import Ticket, Review, UserFollows
admin.site.register(Ticket)
admin.site.register(Review)
admin.site.register(UserFollows)
admin.site.register(User)

View File

@@ -11,8 +11,11 @@ class TicketForm(forms.ModelForm):
class ReviewForm(forms.ModelForm):
CHOICES = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5}
rating = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
class Meta:
model = models.Review
fields = ['headline', 'rating', 'comment']
class ToFollowForm(forms.Form):
user = forms.CharField(label="Nom d'utilisateur", max_length=50)

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2 on 2025-05-02 12:48
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('reviews', '0006_ticket_review_alter_review_ticket'),
]
operations = [
migrations.AlterField(
model_name='review',
name='ticket',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='of_ticket', to='reviews.ticket'),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2 on 2025-05-05 14:10
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('reviews', '0007_alter_review_ticket'),
]
operations = [
migrations.RemoveField(
model_name='ticket',
name='topic',
),
]

View File

@@ -7,13 +7,15 @@ from PIL import Image
class Ticket(models.Model):
# Your Ticket model definition goes here
title = models.CharField("Titre", max_length=100)
topic = models.CharField(max_length=100)
desc = models.CharField("Description", max_length=8192)
user = models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField()
time_created = models.DateTimeField(auto_now_add=True)
review = models.ForeignKey(to='reviews.Review', on_delete=models.SET_NULL, related_name='has_review', null=True)
review = models.ForeignKey(to='reviews.Review',
on_delete=models.SET_NULL,
related_name='has_review',
null=True)
IMAGE_SIZE = (400, 400)
@@ -21,28 +23,39 @@ class Ticket(models.Model):
image = Image.open(self.image)
image.thumbnail(self.IMAGE_SIZE)
image.save(self.image.path)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.resize_image()
class Review(models.Model):
ticket = models.ForeignKey(to=Ticket, on_delete=models.CASCADE, related_name='of_ticket')
ticket = models.ForeignKey(to=Ticket,
on_delete=models.CASCADE,
related_name='of_ticket',
null=True)
rating = models.PositiveSmallIntegerField(
# validates that rating must be between 0 and 5
validators=[MinValueValidator(0), MaxValueValidator(5)])
headline = models.CharField("titre", max_length=128)
comment = models.CharField("commentaire", max_length=8192, blank=True)
user = models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
comment = models.CharField("commentaire",
max_length=8192,
blank=True)
user = models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
time_created = models.DateTimeField(auto_now_add=True)
class UserFollows(models.Model):
# Your UserFollows model definition goes here
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="following", null=True)
followed_user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="followed", null=True)
user = models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="following",
null=True)
followed_user = models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="followed",
null=True)
starting_date = models.DateTimeField(auto_now_add=True)
class Meta:
@@ -50,3 +63,5 @@ class UserFollows(models.Model):
# for unique user-user_followed pairs
unique_together = ('user', 'followed_user', )
def __str__(self):
return f'{self.user} -> {self.followed_user}'

View File

@@ -1,9 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="d-flex align-items-center justify-content-center">
<p> Suppression effectuée </p>
</div>
{% endblock %}

View File

@@ -1,23 +1,50 @@
{% extends 'base.html' %}
{% load reviews_extras %}
{% block content %}
<div class="container">
<div class="row">
<div class="d-flex justify-content-center mt-5">
<h2> FLux </h2>
<h2> Bienvenue {{ request.user }}</h2>
</div>
</div>
<div class="row">
{% for ticket in tickets %}
{% include 'reviews/ticket_detail.html' %}
<div class="col-3 align-self-end">
<a href="{% url 'review-ticket' ticket.id %}" type="button" class="btn btn-primary">Créer une critique</a>
<div class="row d-flex justify-content-center mt-4 mb-3">
<div class="col-3 d-flex justify-content-center">
<a href="{% url 'ticket-add' %}" type="button" class="btn btn-primary">Demander une critique</a>
</div>
<div class="col-3 d-flex justify-content-center">
<a href="{% url 'review-add' %}" type="button" class="btn btn-primary">Créer une critique</a>
</div>
</div>
<div class="row">
{% for post in posts %}
{% if post.content_type == 'TICKET' %}
<div class="row border border-2 border-secondary-subtle my-3">
{% include 'reviews/ticket_detail.html' with ticket=post %}
<div class="col-6 d-flex justify-content-end mb-2">
{% if not post.review %}
<div class="col-4 align-self-end">
<a href="{% url 'review-ticket' post.id %}" type="button" class="btn btn-primary">Créer une critique</a>
</div>
{% else %}
<div class="col-6 align-self-end">
<i>{% display_owner post.review.user %} créé une critique, le {{ post.time_created|date:"d M Y"}}</i>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% elif post.content_type == 'REVIEW' %}
<div class="row border border-2 border-secondary-subtle my-3">
{% include 'reviews/review_detail.html' with review=post %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -19,7 +19,7 @@
{% for ticket in tickets %}
{% include 'reviews/ticket_detail.html' %}
<div class="col-6 d-flex justify-content-end mb-2">
{% if ticket.review is not True %}
{% if not ticket.review %}
<div class="col-4 align-self-end">
<a href="{% url 'review-ticket' ticket.id %}" type="button" class="btn btn-primary">Créer une critique</a>
</div>
@@ -30,5 +30,4 @@
</div>
</div>
{% endblock %}

View File

@@ -2,45 +2,41 @@
{% block content %}
<div class="container">
<div class="row">
<div class="d-flex justify-content-center mt-5">
<h2> Posts </h2>
</div>
</div>
<div class="row d-flex justify-content-center mt-4 mb-3">
{% for ticket in tickets %}
{% include 'reviews/ticket_detail.html' %}
<div class="col-4 d-flex justify-content-end mb-2">
{% if perms.reviews.change_ticket %}
<div class="col-3 align-self-end">
<a href="{% url 'ticket-update' ticket.id %}" type="button" class="btn btn-primary">Modifier</a>
</div>
<div class="col-3 align-self-end">
<a href="{% url 'ticket-delete' ticket.id %}" type="button" class="btn btn-danger">Supprimer</a>
</div>
{% for ticket in tickets %}
<div class="row border border-2 border-secondary-subtle my-3">
{% include 'reviews/ticket_detail.html' %}
<div class="col d-flex justify-content-end mb-2">
<div class="d-flex align-self-end m-2">
<a href="{% url 'ticket-update' ticket.id %}" type="button" class="btn btn-primary">Modifier</a>
</div>
{% endif %}
</div>
{% endfor %}
{% for review in reviews %}
{% include 'reviews/review_detail.html' %}
<div class="col d-flex justify-content-end mb-2">
{% if perms.reviews.change_ticket %}
<div class="d-flex align-self-end m-2">
<a href="{% url 'review-update' review.id %}" type="button" class="btn btn-primary">Modifier</a>
</div>
<div class="d-flex align-self-end m-2">
<a href="{% url 'review-delete' review.id %}" type="button" class="btn btn-danger">Supprimer</a>
</div>
<div class="d-flex align-self-end m-2">
<a href="{% url 'ticket-delete' ticket.id %}" type="button" class="btn btn-danger">Supprimer</a>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
{% for review in reviews %}
<div class="row border border-2 border-secondary-subtle my-3">
{% include 'reviews/review_detail.html' %}
<div class="col d-flex justify-content-end mb-2">
<div class="d-flex align-self-end m-2">
<a href="{% url 'review-update' review.id %}" type="button" class="btn btn-primary">Modifier</a>
</div>
<div class="d-flex align-self-end m-2">
<a href="{% url 'review-delete' review.id %}" type="button" class="btn btn-danger">Supprimer</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -1,18 +1,19 @@
<div class="row border border-2 border-secondary-subtle my-3">
{% load reviews_extras %}
<div class="row">
<div class="col-8 mb-4">
{% if request.user == review.user %}
vous avez publié une critique
{% else %}
{{ review.user }} a publié une critique
{% endif %}
{% display_owner review.user %} publié une critique
</div>
<div class="col-4 d-flex justify-content-end">
{{ review.time_created }}
</div>
<span style="font-size: 22px">{{ review.headline }} - {{ review.rating }} ★ </span>
<p> {{ review.comment }} </p>
<div class="d-flex justify-content-center border border-3 mb-3">
{% include 'reviews/ticket_detail.html' with ticket=review.ticket %}
</div>
</div>
<div class="row d-flex justify-content-center">
<span style="font-size: 22px" class="mb-3">{{ review.headline }} - {{ review.rating|rating_stars }}</span>
<p> {{ review.comment }} </p>
<div class="col-10 border border-2 border-secondary-subtle mb-3">
{% include 'reviews/ticket_detail.html' with ticket=review.ticket %}
</div>
</div>

View File

@@ -1,10 +0,0 @@
<form method='post' enctype='multipart/form-data'>
{% csrf_token %}
{{ review_form.headline }}
{% for radio in review_form.rating %}
<div class="radio">
{{ radio }}
</div>
{% endfor %}
</form>

View File

@@ -11,7 +11,9 @@
<div class="row border border-3 border-secondary-subtle">
<p>Vous êtes en train de répondre à </p>
<div class="col d-flex justify-content-center mb-2">
{% include 'reviews/ticket_detail.html' %}</div>
<div class="row border border-2 border-secondary-subtle my-3">
{% include 'reviews/ticket_detail.html' %}
</div>
</div>
</div>

View File

@@ -3,21 +3,22 @@
{% block content %}
<div class="container">
<div class="row mt-4">
<div class="row my-4">
<div class="d-flex justify-content-center">
<h2>Modifier votre critique</h2>
</div>
</div>
<div class="row border border-3">
<div class="col-8">
<p>Vous êtes en train de poster en réponse à</p>
</div>
<div class="d-flex justify-content-center">
{% include 'reviews/ticket_detail.html' with ticket=review.ticket %}
</div>
<div class="row d-flex justify-content-center">
<div class="col-10 border border-2 border-secondary-subtle mb-3">
{% include 'reviews/ticket_detail.html' with ticket=review.ticket %}
</div>
</div>
</div>
<div class="row border border-3 p-2">
<div class="col-3">
<p>Critique</p>
@@ -35,6 +36,7 @@
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,12 +1,55 @@
{% extends 'base.html' %}
{% block nav %}
<nav>
<a href="{% url 'flux' %}">Flux</a>
<a href="{% url 'posts' %}">Posts</a>
<a href="{% url 'subscribed' %}">Abonnements</a>
<a href="{% url 'logout' %}">Se déconnecter</a>
</nav>
{% endblock %}
{% block content %}
<h2> Subscribed </h2>
<div class="container text-center">
<div class="row d-flex justify-content-center">
<div class="d-flex justify-content-center my-5">
<h2> Suivre d'autres utilisateurs </h2>
</div>
<div class="col-6">
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ user_form }}
</div>
</div>
<div class="col-2">
<button type="submit" class="btn btn-primary">Envoyer</button>
</div>
</form>
</div>
</div>
<div class="row d-flex justify-content-end">
<div class="d-flex justify-content-center mt-5">
<h2> Abonnements </h2>
</div>
{% for user in followed %}
<div class="col-6 border border-2 m-1">
{{ user.followed_user }}
</div>
<div class="col-3 m-1">
<a href="{% url 'unsubscribe' user.followed_user.id %}" type="button" class="btn btn-primary">Se désabonner</a>
</div>
{% endfor %}
</div>
<div class="row d-flex justify-content-center">
<div class="d-flex justify-content-center mt-5">
<h2> Abonnés </h2>
</div>
{% for user in following %}
<div class="col-6 border border-2 m-1">
{{ user.user }}
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,12 +1,16 @@
{% extends 'base.html' %}
{% block content %}
<div class="d-flex justify-content-center align-self-center">
<form method="post">
{% csrf_token %}
<p> Êtes vous sûr de vouloir supprimer {{ ticket.title }} ? </p>
<button type="submit" class="btn btn-warning">Supprimer</button>
</form>
<div class="container text-center" style="height: 100vh">
<div class="row h-100 align-items-center">
<form method="post">
{% csrf_token %}
<p> Êtes vous sûr de vouloir supprimer la demande "{{ ticket.title }}" ? </p>
<button type="submit" class="btn btn-warning">Supprimer</button>
</form>
</div>
</div>
{% endblock %}

View File

@@ -1,17 +1,17 @@
<div class="row border border-2 border-secondary-subtle my-3">
{% load reviews_extras %}
<div class="row">
<div class="col-8 mb-4">
{% if request.user == ticket.user %}
<strong>vous</strong> avez demandé une critique
{% else %}
<strong>{{ ticket.user }}</strong> a demandé une critique
{% endif %}
{% display_owner ticket.user %} publié une demande
</div>
<div class="col-4 d-flex justify-content-end">
{{ ticket.time_created }}
</div>
<p><strong> {{ ticket.title }} </strong></p>
<p> {{ ticket.desc }} </p>
<div class="col-3 mb-2">
<img src="{{ ticket.image.url }}" class="img" style="width: 200px; height: auto" alt="Couverture de {{ ticket.title }}">
</div>
</div>
<p><strong> {{ ticket.title }} </strong></p>
<p> {{ ticket.desc }} </p>
<div class="col-3 mb-2">
<img src="{{ ticket.image.url }}" class="img" alt="Couverture de {{ ticket.title }}">
</div>

View File

@@ -1,28 +0,0 @@
<div class="row border border-2 border-secondary-subtle my-3">
<div class="col-8">
{% if request.user == ticket.user %}
vous avez demandé une critique
{% else %}
{{ ticket.user }} a demandé une critique
{% endif %}
</div>
<div class="col-4 d-flex justify-content-end">
{{ ticket.time_created }}
</div>
<p> {{ ticket.title }} </p>
<p> {{ ticket.body }} </p>
<div class="col-3 mb-2">
<img src="{{ ticket.image.url }}" class="img" style="width: 200px; height: auto" alt="Couverture de {{ ticket.title }}">
</div>
<div class="col-4 d-flex justify-content-end mb-2">
{% if request.user == ticket.user %}
<div class="col-3 align-self-end">
<a href="{% url 'ticket-update' ticket.id %}" type="button" class="btn btn-primary">Modifier</a>
</div>
<div class="col-3 align-self-end">
<a href="{% url 'ticket-delete' ticket.id %}" type="button" class="btn btn-danger">Supprimer</a>
</div>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% block content %}
<div class="container text-center" style="height: 100vh">
<div class="row h-100 align-items-center">
<form method="post">
{% csrf_token %}
<p> Êtes vous sûr de vouloir vous désabonner de {{ followed.followed_user }} ? </p>
<button type="submit" class="btn btn-warning">Confirmer</button>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,21 @@
from django import template
register = template.Library()
@register.filter
def rating_stars(rating):
stars = ''
for i in range(rating):
stars += ""
for i in range(5 - rating):
stars += ""
return stars
@register.simple_tag(takes_context=True)
def display_owner(context, user):
if user == context['user']:
return "vous avez"
return f"{user.username} a"

View File

@@ -1,3 +1,3 @@
from django.test import TestCase
# from django.test import TestCase
# Create your tests here.

View File

@@ -1,42 +1,55 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required, permission_required
from reviews.models import Ticket, Review
from reviews.forms import TicketForm, ReviewForm
from django.db.models import CharField, Value
from authentication.models import User
from reviews.models import Ticket, Review, UserFollows
from reviews.forms import TicketForm, ReviewForm, ToFollowForm
from itertools import chain
@login_required
def home(request):
tickets = Ticket.objects.all()
return render(request, 'reviews/home.html', {'tickets': tickets})
return render(request,
'reviews/home.html',
{'tickets': tickets})
@login_required
def flux(request):
tickets = Ticket.objects.all()
reviews = Review.objects.all()
context = {
'tickets': tickets,
'reviews': reviews,
}
return render(request, 'reviews/flux.html', context)
followed = UserFollows.objects.filter(user=request.user)
users_followed = []
for userf in followed:
users_followed.append(userf.followed_user)
tickets = Ticket.objects.filter(user__in=users_followed)
tickets = tickets.annotate(content_type=Value('TICKET', CharField()))
reviews = Review.objects.filter(user__in=users_followed)
reviews = reviews.annotate(content_type=Value('REVIEW', CharField()))
posts = sorted(
chain(reviews, tickets),
key=lambda post: post.time_created,
reverse=True)
return render(request,
'reviews/flux.html',
{'posts': posts})
@login_required
def posts(request):
tickets = Ticket.objects.filter(user=request.user)
reviews = Review.objects.filter(user=request.user)
return render(request,
'reviews/posts.html',
{'tickets': tickets, 'reviews': reviews})
'reviews/posts.html',
{'tickets': tickets, 'reviews': reviews})
@login_required
def subscribed(request):
return render(request, 'reviews/subscribed.html')
@login_required
def ticket(request, ticket_id):
ticket = Ticket.objects.get(id=ticket_id)
return render(request,
'reviews/ticket.html',
{'ticket': ticket})
'reviews/ticket.html',
{'ticket': ticket})
@login_required
def create_ticket(request):
@@ -48,10 +61,11 @@ def create_ticket(request):
ticket = ticket_form.save(commit=False)
ticket.user = request.user
ticket.save()
return redirect('home')
return render(request,
'reviews/ticket_create.html',
context = {'ticket_form': ticket_form, 'tickets': tickets})
return redirect('flux')
return render(request,
'reviews/ticket_create.html',
context={'ticket_form': ticket_form, 'tickets': tickets})
@login_required
@permission_required('review.change_ticket', raise_exception=True)
@@ -61,34 +75,36 @@ def update_ticket(request, ticket_id):
ticket_form = TicketForm(request.POST, instance=ticket)
if ticket_form.is_valid():
ticket = ticket_form.save()
return redirect('home')
return redirect('flux')
else:
ticket_form = TicketForm(instance=ticket)
return render(request,
'reviews/ticket_update.html',
{'ticket_form': ticket_form})
return render(request,
'reviews/ticket_update.html',
{'ticket_form': ticket_form})
@login_required
@permission_required('review.onwer', raise_exception=True)
@permission_required('review.delete_ticket', raise_exception=True)
def delete_ticket(request, ticket_id):
ticket = Ticket.objects.get(id=ticket_id)
if request.method == 'POST':
ticket.delete()
return redirect('home')
return redirect('flux')
return render(request,
'reviews/ticket_delete.html',
{'ticket': ticket})
return render(request,
'reviews/ticket_delete.html',
{'ticket': ticket})
@login_required
def review(request, review_id):
review = Review.objects.get(id=review_id)
ticket = review.ticket
return render(request,
'reviews/review.html',
{'review': review})
@login_required
def create_review(request):
ticket_form = TicketForm()
@@ -99,23 +115,24 @@ def create_review(request):
print(request.POST)
if all([ticket_form.is_valid(), review_form.is_valid()]):
ticket = ticket_form.save(commit=False)
print(ticket)
ticket.user = request.user
ticket.save()
review = review_form.save(commit=False)
print(review)
review.user = request.user
review.ticket = ticket
review.save()
return redirect('posts')
ticket = ticket_form.save(commit=False)
review = review_form.save(commit=False)
review.user = ticket.user = request.user
review.save()
ticket.save()
review.ticket = ticket
ticket.review = review
ticket.save()
review.save()
return redirect('posts')
context = {
'ticket_form': ticket_form,
'review_form': review_form,
}
return render(request,
'reviews/review_create.html', context)
return render(request,
'reviews/review_create.html', context)
@login_required
def ticket_review(request, ticket_id):
@@ -128,14 +145,17 @@ def ticket_review(request, ticket_id):
review = review_form.save(commit=False)
review.user = request.user
review.ticket = ticket
ticket.review = review
review.save()
ticket.review = review
ticket.save()
return redirect('posts')
context = {
'ticket': ticket,
'review_form': review_form,
}
return render(request,
'reviews/review_ticket.html', context)
'reviews/review_ticket.html', context)
@login_required
@@ -147,16 +167,15 @@ def update_review(request, review_id):
print(review_form.is_valid())
if review_form.is_valid():
review = review_form.save()
return redirect('home')
return redirect('posts')
else:
review_form = ReviewForm(instance=review)
return render(request,
'reviews/review_update.html',
{'review_form': review_form, 'review': review})
@login_required
@permission_required('review.owner', raise_exception=True)
def delete_review(request, review_id):
review = Review.objects.get(id=review_id)
if request.method == 'POST':
@@ -166,12 +185,39 @@ def delete_review(request, review_id):
'reviews/review_delete.html',
{'review': review})
def follow_user(request):
pass
def unfollow_user(request):
pass
@login_required
def subscribed(request):
follows = UserFollows()
user_form = ToFollowForm()
following = UserFollows.objects.filter(followed_user=request.user)
if request.method == 'POST':
user_form = ToFollowForm(request.POST)
if user_form.is_valid():
user = user_form.cleaned_data["user"]
user_followed = User.objects.filter(username=user)
follows.followed_user = user_followed[0]
follows.user = request.user
follows.save()
return redirect('subscribed')
followed = UserFollows.objects.filter(user=request.user)
context = {
'user_form': user_form,
'followed': followed,
'following': following
}
return render(request,
'reviews/subscribed.html', context)
def delete_confirm(request, truc_id):
render (request,
'reviews/delete_confirm.html')
@login_required
def unsubscribe(request, followed_user_id):
followed = UserFollows.objects.get(
user=request.user,
followed_user=followed_user_id)
if request.method == 'POST':
followed.delete()
return redirect('subscribed')
return render(request,
'reviews/unsubscribe.html',
{'followed': followed})