all features OK, cleaned up (flake8) code except migrations files
@@ -20,7 +20,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-i^f6dau&ze)tzjzf*e#c%+r%sla+-q&e+on(-*xg&t$toyt24y'
|
||||
SECRET_KEY = \
|
||||
'django-insecure-i^f6dau&ze)tzjzf*e#c%+r%sla+-q&e+on(-*xg&t$toyt24y'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
@@ -37,7 +38,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'authentication',
|
||||
'authentication',
|
||||
'reviews',
|
||||
]
|
||||
|
||||
@@ -89,16 +90,21 @@ DATABASES = {
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
'NAME':
|
||||
'django.contrib.auth.\
|
||||
password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
'NAME': 'django.contrib.auth.\
|
||||
password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
'NAME': 'django.contrib.auth.\
|
||||
password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
'NAME': 'django.contrib.auth.\
|
||||
password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -125,7 +131,7 @@ STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
LOGIN_REDIRECT_URL = 'home'
|
||||
LOGIN_REDIRECT_URL = 'flux'
|
||||
|
||||
LOGIN_URL = 'login'
|
||||
|
||||
@@ -136,6 +142,3 @@ AUTH_USER_MODEL = 'authentication.User'
|
||||
MEDIA_URL = 'media/uploads/'
|
||||
|
||||
MEDIA_ROOT = BASE_DIR.joinpath('media/uploads/')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,21 +18,17 @@ from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView, PasswordChangeDoneView
|
||||
import authentication.views, reviews.views
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
import authentication.views
|
||||
import reviews.views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('home/', reviews.views.home, name='home'),
|
||||
path('', LoginView.as_view(
|
||||
# path('', authentication.views.login_page, name='login'),
|
||||
template_name='authentication/login.html',
|
||||
redirect_authenticated_user=True), name='login'),
|
||||
path('pwd-change/', PasswordChangeView.as_view(
|
||||
template_name='authentication/pwd_change.html'), name='pwd-change'),
|
||||
path('pwd-change-done/', PasswordChangeDoneView.as_view(
|
||||
template_name='authentication/pwd_change_done.html'), name='pwd-change-done'),
|
||||
path('logout/', LogoutView.as_view(next_page='login'), name='logout'),
|
||||
path('register/', authentication.views.register_page, name='register'),
|
||||
path('flux/', reviews.views.flux, name='flux'),
|
||||
@@ -40,14 +36,22 @@ urlpatterns = [
|
||||
path('subscribed/', reviews.views.subscribed, name='subscribed'),
|
||||
path('ticket/add/', reviews.views.create_ticket, name='ticket-add'),
|
||||
path('review/add/', reviews.views.create_review, name='review-add'),
|
||||
path('ticket/<int:ticket_id>/', reviews.views.ticket, name='ticket-detail'),
|
||||
path('ticket/<int:ticket_id>/update/', reviews.views.update_ticket, name='ticket-update'),
|
||||
path('ticket/<int:ticket_id>/delete/', reviews.views.delete_ticket, name='ticket-delete'),
|
||||
path('ticket/<int:ticket_id>/review/', reviews.views.ticket_review, name='review-ticket'),
|
||||
path('review/<int:review_id>/', reviews.views.review, name='review-detail'),
|
||||
path('review/<int:review_id>/update/', reviews.views.update_review, name='review-update'),
|
||||
path('review/<int:review_id>/delete/', reviews.views.delete_review, name='review-delete'),
|
||||
path('confirm/', reviews.views.delete_confirm, name='delete-confirm'),
|
||||
path('ticket/<int:ticket_id>/',
|
||||
reviews.views.ticket, name='ticket-detail'),
|
||||
path('ticket/<int:ticket_id>/update/',
|
||||
reviews.views.update_ticket, name='ticket-update'),
|
||||
path('ticket/<int:ticket_id>/delete/',
|
||||
reviews.views.delete_ticket, name='ticket-delete'),
|
||||
path('ticket/<int:ticket_id>/review/',
|
||||
reviews.views.ticket_review, name='review-ticket'),
|
||||
path('review/<int:review_id>/',
|
||||
reviews.views.review, name='review-detail'),
|
||||
path('review/<int:review_id>/update/',
|
||||
reviews.views.update_review, name='review-update'),
|
||||
path('review/<int:review_id>/delete/',
|
||||
reviews.views.delete_review, name='review-delete'),
|
||||
path('unsubscribe/<int:followed_user_id>/',
|
||||
reviews.views.unsubscribe, name='unsubscribe'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from django.contrib import admin
|
||||
# from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@@ -5,10 +5,12 @@ from django.contrib.auth.forms import UserCreationForm
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField(max_length=63, label='Nom d’utilisateur')
|
||||
password = forms.CharField(max_length=63, widget=forms.PasswordInput, label='Mot de passe')
|
||||
password = forms.CharField(max_length=63,
|
||||
widget=forms.PasswordInput,
|
||||
label='Mot de passe')
|
||||
|
||||
|
||||
class RegisterForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = get_user_model()
|
||||
fields = ('username',)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@
|
||||
{% csrf_token %}
|
||||
<div class="form-group p-3">
|
||||
{{ form.username }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group p-3">
|
||||
{{ form.password }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary self-align-right">Se connecter</button>
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">Se connecter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="row justify-content-center mt-5 mb-3">
|
||||
<div class="col text-center">
|
||||
<h2>Inscrivez-vous</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-6 justify-content-around d-flex align-items-end">
|
||||
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-8">
|
||||
<form method='post'>
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="row justify-content-end">
|
||||
<div class="col-3 order-2">
|
||||
<button type="submit" class="btn btn-primary">S'inscrire</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-3 order-1">
|
||||
<a href="{% url 'login' %}" class="btn btn-primary">Retourner</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-8 d-flex justify-content-end">
|
||||
<div class="d-flex align-self-end m-2">
|
||||
<a href="{% url 'login' %}" class="btn btn-primary">Retourner</a>
|
||||
</div>
|
||||
<div class="d-flex align-self-end m-2">
|
||||
<button type="submit" class="btn btn-primary">S'inscrire</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from django.test import TestCase
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -1,32 +1,7 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import login, logout, authenticate
|
||||
from django.contrib.auth import login
|
||||
from django.conf import settings
|
||||
from django import forms
|
||||
from authentication.forms import LoginForm, RegisterForm
|
||||
|
||||
|
||||
def login_page(request):
|
||||
form = LoginForm()
|
||||
message = ""
|
||||
|
||||
if request.method == 'POST':
|
||||
form = LoginForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = authenticate(
|
||||
username=form.cleaned_data['username'],
|
||||
password=form.cleaned_data['password'],
|
||||
)
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
return redirect('home')
|
||||
else:
|
||||
message = "Identifiants invalides"
|
||||
|
||||
print(request.POST)
|
||||
|
||||
return render(request,
|
||||
'authentication/login.html',
|
||||
{'form': form})
|
||||
from authentication.forms import RegisterForm
|
||||
|
||||
|
||||
def register_page(request):
|
||||
|
||||
BIN
LITReview/media/uploads/VieilHommeMer.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 19 KiB |
BIN
LITReview/media/uploads/horde_contrevent.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
BIN
LITReview/media/uploads/punch_kX6sdM6.jpeg
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
LITReview/media/uploads/punch_sfEVezr.jpeg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
19
LITReview/reviews/migrations/0007_alter_review_ticket.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
17
LITReview/reviews/migrations/0008_remove_ticket_topic.py
Normal 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',
|
||||
),
|
||||
]
|
||||
@@ -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}'
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
14
LITReview/reviews/templates/reviews/unsubscribe.html
Normal 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 %}
|
||||
0
LITReview/reviews/templatetags/__init.py__
Normal file
21
LITReview/reviews/templatetags/reviews_extras.py
Normal 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"
|
||||
@@ -1,3 +1,3 @@
|
||||
from django.test import TestCase
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title> LITReview </title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="s ha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container-fluid border border-3 border-secondary-subtle">
|
||||
<div class="col d-flex justify-content-center">
|
||||
<h1> LITReview </h1>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title> LITReview </title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container-fluid border border-3 border-secondary-subtle">
|
||||
<div class="col-6 d-flex justify-content-end ml-3">
|
||||
<h1> LITReview </h1>
|
||||
</div>
|
||||
<div class="col-3 d-flex self-align-end">
|
||||
{% if user.is_authenticated %}
|
||||
{% include 'nav.html' %}
|
||||
{% include 'nav.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ul class="navbar-nav z-index=2">
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'home' %}" class="nav-link {% if navbar == 'home' %}active{% endif %}">Flux</a>
|
||||
<a href="{% url 'flux' %}" class="nav-link {% if navbar == 'flux' %}active{% endif %}">Flux</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'posts' %}" class="nav-link {% if navbar == 'posts' %}active{% endif %}">Posts</a>
|
||||
|
||||