feat: add favorites feature for users
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
- Implemented `Favorite` model with unique user-item constraint and timestamped records - Added `favorites:index` and `favorites:toggle` views with AJAX support for toggling favorites - Built templates and front-end assets for the favorites page and integration with item grids - Extended header with favorites link and dynamic counter - Added tests covering models, views, and templates for the favorites functionality - Updated admin to enable management of favorites via Django admin interface
This commit is contained in:
57
templates/catalog/_fragments/item-card.html
Normal file
57
templates/catalog/_fragments/item-card.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<article class="item-card{% if item.id in favorite_item_ids %} is-favorite{% endif %}" data-item-card="{{ item.id }}">
|
||||
<a class="item-card-media-link" href="{% url 'detail' item.category.slug item.slug %}" aria-label="Открыть товар {{ item.name }}">
|
||||
{% with primary_image=item.primary_image %}
|
||||
<div class="item-card-image">
|
||||
{% if primary_image %}
|
||||
<img src="{{ primary_image.image.url }}" alt="{{ primary_image.alt|default:item.name }}">
|
||||
{% else %}
|
||||
<div class="item-card-placeholder">Нет фото</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
</a>
|
||||
|
||||
<div class="item-card-body">
|
||||
<div class="item-card-header">
|
||||
<div class="item-card-category">
|
||||
{{ item.category.name }}{% if item.subcategory %} / {{ item.subcategory.name }}{% endif %}
|
||||
</div>
|
||||
{% if request.user.is_authenticated %}
|
||||
<form class="item-card-favorite-form" method="post" action="{% url 'favorites:toggle' %}" data-favorite-form>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button
|
||||
class="item-card-favorite-button{% if item.id in favorite_item_ids %} is-active{% endif %}"
|
||||
type="submit"
|
||||
title="{% if item.id in favorite_item_ids %}Удалить из избранного{% else %}Добавить в избранное{% endif %}"
|
||||
aria-label="{% if item.id in favorite_item_ids %}Удалить из избранного{% else %}Добавить в избранное{% endif %}"
|
||||
data-favorite-button
|
||||
data-label-add="Добавить в избранное"
|
||||
data-label-remove="Удалить из избранного"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false">
|
||||
<path d="m8 2.748-.717-.737C5.6.281 2.514.878 1.4 3.053c-.523 1.023-.641 2.5.314 4.385.92 1.815 2.834 3.989 6.286 6.357 3.452-2.368 5.365-4.542 6.286-6.357.955-1.886.838-3.362.314-4.385C13.486.878 10.4.28 8.717 2.01z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="item-card-favorite-button item-card-favorite-button--login" href="{% url 'users:login' %}?next={{ request.get_full_path|urlencode }}" title="Войдите, чтобы добавить в избранное" aria-label="Войдите, чтобы добавить в избранное" data-favorite-button data-label-add="Добавить в избранное" data-label-remove="Удалить из избранного">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false">
|
||||
<path d="m8 2.748-.717-.737C5.6.281 2.514.878 1.4 3.053c-.523 1.023-.641 2.5.314 4.385.92 1.815 2.834 3.989 6.286 6.357 3.452-2.368 5.365-4.542 6.286-6.357.955-1.886.838-3.362.314-4.385C13.486.878 10.4.28 8.717 2.01z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a class="item-card-title-link" href="{% url 'detail' item.category.slug item.slug %}" aria-label="Открыть товар {{ item.name }}">
|
||||
<h2 class="item-card-title">{{ item.name }}</h2>
|
||||
</a>
|
||||
<div class="item-card-footer">
|
||||
<span class="item-card-price">{{ item.price }} руб.</span>
|
||||
<span class="item-card-availability {% if item.is_available %}available{% else %}unavailable{% endif %}">
|
||||
{% if item.is_available %}В наличии{% else %}Нет в наличии{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -1,34 +1,3 @@
|
||||
<main class="item-list">
|
||||
<div class="items-grid">
|
||||
{% for item in items %}
|
||||
<article class="item-card">
|
||||
<a class="item-card-link" href="{% url 'detail' item.category.slug item.slug %}" aria-label="Открыть товар {{ item.name }}">
|
||||
{% with primary_image=item.primary_image %}
|
||||
<div class="item-card-image">
|
||||
{% if primary_image %}
|
||||
<img src="{{ primary_image.image.url }}" alt="{{ primary_image.alt|default:item.name }}">
|
||||
{% else %}
|
||||
<div class="item-card-placeholder">Нет фото</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<div class="item-card-body">
|
||||
<div class="item-card-category">
|
||||
{{ item.category.name }}{% if item.subcategory %} / {{ item.subcategory.name }}{% endif %}
|
||||
</div>
|
||||
<h2 class="item-card-title">{{ item.name }}</h2>
|
||||
<div class="item-card-footer">
|
||||
<span class="item-card-price">{{ item.price }} руб.</span>
|
||||
<span class="item-card-availability {% if item.is_available %}available{% else %}unavailable{% endif %}">
|
||||
{% if item.is_available %}В наличии{% else %}Нет в наличии{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
{% empty %}
|
||||
<p class="item-list-empty">В этой категории пока нет товаров.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% include "_fragments/item-grid.html" with items=items empty_message="В этой категории пока нет товаров." %}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user