Compare commits
9 Commits
225586e70e
...
7c7c638c19
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c7c638c19 | |||
| c415d26001 | |||
| eefa3bf268 | |||
| 08c72855ef | |||
| b1e5486d27 | |||
| c41188e17b | |||
| 2f02c8fa2b | |||
| 782d9cfbb1 | |||
| 0da6c9efa7 |
@@ -25,7 +25,7 @@ SECRET_KEY = "django-insecure-c1_r=$!h*n-@r1u-r#9x*xsgs7$a*2cnr7!c8=+irf!*4@g$$2
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = ["127.0.0.1", "bookify.tishenko.dev"]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|||||||
@@ -11,10 +11,16 @@
|
|||||||
<header>
|
<header>
|
||||||
<h1><a href="{% url 'books:book_list' %}">Bookify</a></h1>
|
<h1><a href="{% url 'books:book_list' %}">Bookify</a></h1>
|
||||||
<nav>
|
<nav>
|
||||||
|
<a href="{% url 'books:books_rating' %}">Рейтинг книг</a>
|
||||||
<a href="{% url 'books:book_list' %}">Список книг</a>
|
<a href="{% url 'books:book_list' %}">Список книг</a>
|
||||||
<a href="{% url 'books:genre_list' %}">Список жанров</a>
|
<a href="{% url 'books:genre_list' %}">Список жанров</a>
|
||||||
|
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'books:add_book' %}">Добавить книгу</a>
|
<a href="{% url 'books:add_book' %}">Добавить книгу</a>
|
||||||
|
<a href="{% url 'books:my_books' %}">Мои книги</a>
|
||||||
|
<a href="{% url 'books:my_reviews' %}">Мои отзывы</a>
|
||||||
|
|
|
||||||
|
<strong>{{ user.username }}</strong>
|
||||||
<a href="{% url 'logout' %}">Выйти</a>
|
<a href="{% url 'logout' %}">Выйти</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'login' %}">Войти</a>
|
<a href="{% url 'login' %}">Войти</a>
|
||||||
|
|||||||
@@ -14,9 +14,18 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Средний рейтинг:</strong> {{ book.average_rating }}</p>
|
<p><strong>Средний рейтинг:</strong> {{ book.average_rating }}</p>
|
||||||
|
<p><strong>Добавил:</strong>
|
||||||
|
{% if book.created_by %}
|
||||||
|
{{ book.created_by.username }}
|
||||||
|
{% else %}
|
||||||
|
Неизвестно
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
{% if user.is_authenticated and user == book.created_by %}
|
{% if user.is_authenticated and user == book.created_by %}
|
||||||
<p>
|
<p>
|
||||||
|
<a href="{% url 'books:edit_book' book.pk %}">Редактировать книгу</a> |
|
||||||
<a class="btn-delete" href="{% url 'books:delete_book' book.pk %}">Удалить книгу</a>
|
<a class="btn-delete" href="{% url 'books:delete_book' book.pk %}">Удалить книгу</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -26,7 +35,14 @@
|
|||||||
<h3>Отзывы</h3>
|
<h3>Отзывы</h3>
|
||||||
{% for review in reviews %}
|
{% for review in reviews %}
|
||||||
<div class="review">
|
<div class="review">
|
||||||
<p><strong>{{ review.user.username }}</strong> ({{ review.rating }}/5)</p>
|
<p>
|
||||||
|
<strong>{{ review.user.username }}</strong>
|
||||||
|
({{ review.rating }}/5)
|
||||||
|
{% if review.user == user %}
|
||||||
|
<!-- Добавим ссылку на редактирование -->
|
||||||
|
<a href="{% url 'books:edit_review' review.pk %}">Редактировать отзыв</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
<p>{{ review.text }}</p>
|
<p>{{ review.text }}</p>
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +51,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
{% if user_has_review %}
|
||||||
|
<p>Вы уже оставили отзыв.</p>
|
||||||
|
{% else %}
|
||||||
<div class="add-review">
|
<div class="add-review">
|
||||||
<h3>Добавить отзыв</h3>
|
<h3>Добавить отзыв</h3>
|
||||||
<form method="POST" action="{% url 'books:add_review' book.pk %}">
|
<form method="POST" action="{% url 'books:add_review' book.pk %}">
|
||||||
@@ -44,6 +64,7 @@
|
|||||||
<button type="submit">Отправить</button>
|
<button type="submit">Отправить</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Для добавления отзывов <a href="{% url 'login' %}">войдите</a> или <a href="{% url 'books:register' %}">зарегистрируйтесь</a>.</p>
|
<p>Для добавления отзывов <a href="{% url 'login' %}">войдите</a> или <a href="{% url 'books:register' %}">зарегистрируйтесь</a>.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
9
bookify/books/templates/books/books_rating.html
Normal file
9
bookify/books/templates/books/books_rating.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'books/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Общий рейтинг книг</h2>
|
||||||
|
<div class="book-list">
|
||||||
|
{% for book in books %}
|
||||||
|
{% include 'books/_book_item.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
9
bookify/books/templates/books/edit_book.html
Normal file
9
bookify/books/templates/books/edit_book.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'books/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Редактировать книгу "{{ book.title }}"</h2>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit">Сохранить</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
9
bookify/books/templates/books/edit_review.html
Normal file
9
bookify/books/templates/books/edit_review.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'books/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Редактировать отзыв для "{{ review.book.title }}"</h2>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit">Сохранить</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
11
bookify/books/templates/books/my_books.html
Normal file
11
bookify/books/templates/books/my_books.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'books/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Мои книги</h2>
|
||||||
|
<div class="book-list">
|
||||||
|
{% for book in books %}
|
||||||
|
{% include 'books/_book_item.html' %}
|
||||||
|
{% empty %}
|
||||||
|
<p>У вас нет добавленных книг.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
30
bookify/books/templates/books/my_reviews.html
Normal file
30
bookify/books/templates/books/my_reviews.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends 'books/base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Мои отзывы</h2>
|
||||||
|
{% if reviews %}
|
||||||
|
<ul>
|
||||||
|
{% for review in reviews %}
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Книга:</strong>
|
||||||
|
<a href="{% url 'books:book_detail' review.book.pk %}">
|
||||||
|
{{ review.book.title }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Рейтинг:</strong> {{ review.rating }}/5
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Отзыв:</strong> {{ review.text }}
|
||||||
|
</p>
|
||||||
|
<!-- Ссылка Edit (уже есть view edit_review) -->
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'books:edit_review' review.pk %}">Редактировать отзыв</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>Вы пока не оставляли отзывов.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -23,4 +23,9 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("register/", views.register, name="register"),
|
path("register/", views.register, name="register"),
|
||||||
path("succesful-logout/", views.logout, name="logout"),
|
path("succesful-logout/", views.logout, name="logout"),
|
||||||
|
path("book/<int:pk>/edit/", views.edit_book, name="edit_book"),
|
||||||
|
path("review/<int:pk>/edit/", views.edit_review, name="edit_review"),
|
||||||
|
path("rating/", views.books_rating, name="books_rating"),
|
||||||
|
path("my-books/", views.my_books, name="my_books"),
|
||||||
|
path("my-reviews/", views.my_reviews, name="my_reviews"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from django.contrib.auth import login
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db.models import Avg
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
from .forms import BookForm, CustomUserCreationForm, GenreForm, ReviewForm
|
from .forms import BookForm, CustomUserCreationForm, GenreForm, ReviewForm
|
||||||
@@ -25,6 +26,31 @@ def logout(requst):
|
|||||||
return render(requst, "accounts/logout.html")
|
return render(requst, "accounts/logout.html")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def my_books(request):
|
||||||
|
"""Список книг, добавленных текущим пользователем."""
|
||||||
|
books = Book.objects.filter(created_by=request.user)
|
||||||
|
return render(request, "books/my_books.html", {"books": books})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def my_reviews(request):
|
||||||
|
"""Все отзывы, оставленные текущим пользователем."""
|
||||||
|
reviews = Review.objects.filter(user=request.user)
|
||||||
|
return render(request, "books/my_reviews.html", {"reviews": reviews})
|
||||||
|
|
||||||
|
|
||||||
|
def books_rating(request):
|
||||||
|
"""
|
||||||
|
Страница со всеми книгами, отсортированными по убыванию среднего рейтинга.
|
||||||
|
"""
|
||||||
|
# Переименуем аннотацию в avg_rating
|
||||||
|
books = Book.objects.annotate(avg_rating=Avg("reviews__rating")).order_by(
|
||||||
|
"-avg_rating"
|
||||||
|
)
|
||||||
|
return render(request, "books/books_rating.html", {"books": books})
|
||||||
|
|
||||||
|
|
||||||
def book_list(request):
|
def book_list(request):
|
||||||
"""Главная страница со списком всех книг."""
|
"""Главная страница со списком всех книг."""
|
||||||
books = Book.objects.all()
|
books = Book.objects.all()
|
||||||
@@ -32,17 +58,45 @@ def book_list(request):
|
|||||||
|
|
||||||
|
|
||||||
def book_detail(request, pk):
|
def book_detail(request, pk):
|
||||||
"""Детальная страница книги."""
|
|
||||||
book = get_object_or_404(Book, pk=pk)
|
book = get_object_or_404(Book, pk=pk)
|
||||||
reviews = book.reviews.all()
|
reviews = book.reviews.all()
|
||||||
review_form = ReviewForm()
|
review_form = ReviewForm()
|
||||||
|
|
||||||
|
user_has_review = False
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
user_has_review = reviews.filter(user=request.user).exists()
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"books/book_detail.html",
|
"books/book_detail.html",
|
||||||
{"book": book, "reviews": reviews, "review_form": review_form},
|
{
|
||||||
|
"book": book,
|
||||||
|
"reviews": reviews,
|
||||||
|
"review_form": review_form,
|
||||||
|
"user_has_review": user_has_review,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_book(request, pk):
|
||||||
|
"""Редактирование книги, только для её создателя."""
|
||||||
|
book = get_object_or_404(Book, pk=pk)
|
||||||
|
# Проверяем, что текущий пользователь – владелец:
|
||||||
|
if book.created_by != request.user:
|
||||||
|
raise PermissionDenied("Вы не можете редактировать чужую книгу.")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = BookForm(request.POST, instance=book)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect("books:book_detail", pk=book.pk)
|
||||||
|
else:
|
||||||
|
form = BookForm(instance=book)
|
||||||
|
|
||||||
|
return render(request, "books/edit_book.html", {"form": form, "book": book})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_book(request):
|
def add_book(request):
|
||||||
"""Добавление новой книги (только авторизованный пользователь)."""
|
"""Добавление новой книги (только авторизованный пользователь)."""
|
||||||
@@ -73,8 +127,16 @@ def delete_book(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_review(request, pk):
|
def add_review(request, pk):
|
||||||
"""Добавление отзыва к книге (только авторизованный пользователь)."""
|
|
||||||
book = get_object_or_404(Book, pk=pk)
|
book = get_object_or_404(Book, pk=pk)
|
||||||
|
|
||||||
|
# Проверяем, не оставил ли уже этот пользователь отзыв
|
||||||
|
existing_review = Review.objects.filter(book=book, user=request.user).first()
|
||||||
|
if existing_review:
|
||||||
|
# Если уже есть отзыв, можно показать сообщение или
|
||||||
|
# перенаправить на страницу книги с сообщением
|
||||||
|
# Для простоты сделаем редирект с GET-параметром
|
||||||
|
return redirect("books:book_detail", pk=pk)
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = ReviewForm(request.POST)
|
form = ReviewForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@@ -85,6 +147,25 @@ def add_review(request, pk):
|
|||||||
return redirect("books:book_detail", pk=pk)
|
return redirect("books:book_detail", pk=pk)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_review(request, pk):
|
||||||
|
"""Редактирование отзыва, только если пользователь – автор."""
|
||||||
|
review = get_object_or_404(Review, pk=pk)
|
||||||
|
# Проверяем владельца
|
||||||
|
if review.user != request.user:
|
||||||
|
raise PermissionDenied("Нельзя редактировать чужой отзыв.")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ReviewForm(request.POST, instance=review)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect("books:book_detail", pk=review.book.pk)
|
||||||
|
else:
|
||||||
|
form = ReviewForm(instance=review)
|
||||||
|
|
||||||
|
return render(request, "books/edit_review.html", {"form": form, "review": review})
|
||||||
|
|
||||||
|
|
||||||
def genre_recommendations(request, genre_name):
|
def genre_recommendations(request, genre_name):
|
||||||
"""Рекомендации книг по заданному жанру."""
|
"""Рекомендации книг по заданному жанру."""
|
||||||
genre = get_object_or_404(Genre, name=genre_name)
|
genre = get_object_or_404(Genre, name=genre_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user