Make product cards fully clickable
All checks were successful
Build and Push Docker Image / build (push) Successful in 27s
All checks were successful
Build and Push Docker Image / build (push) Successful in 27s
This commit is contained in:
@@ -75,6 +75,16 @@ class FavoritePageTests(TestCase):
|
|||||||
self.assertContains(response, "/media/items/sink-primary.jpg")
|
self.assertContains(response, "/media/items/sink-primary.jpg")
|
||||||
self.assertContains(response, 'href="/catalog/plumbing/kitchen-sink/"', html=False)
|
self.assertContains(response, 'href="/catalog/plumbing/kitchen-sink/"', html=False)
|
||||||
self.assertContains(response, 'href="/catalog/plumbing/mono-tap/"', html=False)
|
self.assertContains(response, 'href="/catalog/plumbing/mono-tap/"', html=False)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<a class="item-card-overlay" href="/catalog/plumbing/kitchen-sink/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<a class="item-card-overlay" href="/catalog/plumbing/mono-tap/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
self.assertContains(response, "Удалить из избранного")
|
self.assertContains(response, "Удалить из избранного")
|
||||||
|
|
||||||
def test_toggle_endpoint_adds_and_removes_favorite(self):
|
def test_toggle_endpoint_adds_and_removes_favorite(self):
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ class HomePopularProductsTests(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, visible_item.name)
|
self.assertContains(response, visible_item.name)
|
||||||
self.assertContains(response, reverse("detail", args=[self.category.slug, visible_item.slug]))
|
self.assertContains(response, reverse("detail", args=[self.category.slug, visible_item.slug]))
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
f'<a class="product-card-link" href="{reverse("detail", args=[self.category.slug, visible_item.slug])}"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
self.assertContains(response, "1600,00 руб.")
|
self.assertContains(response, "1600,00 руб.")
|
||||||
self.assertNotContains(response, "Hidden sink")
|
self.assertNotContains(response, "Hidden sink")
|
||||||
|
|
||||||
|
|||||||
@@ -279,6 +279,42 @@ class CatalogViewTests(TestCase):
|
|||||||
response = self.client.get("/catalog/")
|
response = self.client.get("/catalog/")
|
||||||
|
|
||||||
self.assertContains(response, 'href="/catalog/plumbing/kitchen-sink/"')
|
self.assertContains(response, 'href="/catalog/plumbing/kitchen-sink/"')
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<a class="item-card-overlay" href="/catalog/plumbing/kitchen-sink/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sidebar_product_cards_include_full_card_links(self):
|
||||||
|
sale_item = Item.objects.create(
|
||||||
|
name="Акционная ванна",
|
||||||
|
slug="sale-bath",
|
||||||
|
category=self.plumbing,
|
||||||
|
price="900.00",
|
||||||
|
discount_price="750.00",
|
||||||
|
is_available=True,
|
||||||
|
)
|
||||||
|
new_item = Item.objects.create(
|
||||||
|
name="Новый душ",
|
||||||
|
slug="new-shower",
|
||||||
|
category=self.plumbing,
|
||||||
|
price="500.00",
|
||||||
|
is_available=True,
|
||||||
|
is_new=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get("/catalog/")
|
||||||
|
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
f'<a class="sidebar-mini-card__overlay" href="/catalog/plumbing/{sale_item.slug}/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
f'<a class="sidebar-mini-card__overlay" href="/catalog/plumbing/{new_item.slug}/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
|
||||||
def test_catalog_item_card_shows_favorite_button_for_authenticated_user(self):
|
def test_catalog_item_card_shows_favorite_button_for_authenticated_user(self):
|
||||||
user = get_user_model().objects.create_user(
|
user = get_user_model().objects.create_user(
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ class CartFlowTests(TestCase):
|
|||||||
self.assertContains(response, "3200,00 руб.")
|
self.assertContains(response, "3200,00 руб.")
|
||||||
self.assertContains(response, "3295,00 руб.")
|
self.assertContains(response, "3295,00 руб.")
|
||||||
self.assertContains(response, "/media/items/cart-item.jpg")
|
self.assertContains(response, "/media/items/cart-item.jpg")
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'<a class="cart-item__overlay" href="/catalog/plumbing/kitchen-sink/"',
|
||||||
|
html=False,
|
||||||
|
)
|
||||||
|
self.assertContains(response, 'id="cart-increment-', html=False)
|
||||||
|
self.assertContains(response, 'action="/orders/cart/remove/"', html=False)
|
||||||
self.assertNotContains(response, 'name="promo_code"', html=False)
|
self.assertNotContains(response, 'name="promo_code"', html=False)
|
||||||
|
|
||||||
def test_cart_page_redirects_guest_to_login(self):
|
def test_cart_page_redirects_guest_to_login(self):
|
||||||
|
|||||||
@@ -26,13 +26,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-item {
|
.cart-item {
|
||||||
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 230px minmax(0, 1fr);
|
grid-template-columns: 230px minmax(0, 1fr);
|
||||||
gap: 34px;
|
gap: 34px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item__media {
|
.cart-item__media {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 1.55 / 1;
|
aspect-ratio: 1.55 / 1;
|
||||||
@@ -40,6 +44,13 @@
|
|||||||
background: rgba(99, 118, 141, 0.12);
|
background: rgba(99, 118, 141, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart-item__overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.cart-item__media img {
|
.cart-item__media img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -61,6 +72,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-item__title {
|
.cart-item__title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
@@ -81,6 +94,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cart-item__controls {
|
.cart-item__controls {
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -200,9 +200,11 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-card {
|
.item-card {
|
||||||
|
position: relative;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border: 1px solid rgba(54, 33, 62, 0.14);
|
border: 1px solid rgba(54, 33, 62, 0.14);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +215,15 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-card-media-link {
|
.item-card-media-link {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-card-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +270,8 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-card-favorite-form {
|
.item-card-favorite-form {
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,10 +309,14 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-card-favorite-button--login {
|
.item-card-favorite-button--login {
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card-title-link {
|
.item-card-title-link {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,13 +705,24 @@ aside {
|
|||||||
|
|
||||||
/* Sidebar mini-cards */
|
/* Sidebar mini-cards */
|
||||||
.sidebar-mini-card {
|
.sidebar-mini-card {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-mini-card__overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-mini-card__img-link {
|
.sidebar-mini-card__img-link {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
flex: 0 0 52px;
|
flex: 0 0 52px;
|
||||||
width: 52px;
|
width: 52px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
@@ -719,6 +747,8 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-mini-card__name {
|
.sidebar-mini-card__name {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
color: #36213e;
|
color: #36213e;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
|||||||
@@ -507,6 +507,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid rgba(54, 33, 62, 0.12);
|
border: 1px solid rgba(54, 33, 62, 0.12);
|
||||||
|
cursor: pointer;
|
||||||
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,6 +518,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-media {
|
.product-media {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -524,6 +527,13 @@
|
|||||||
background: rgba(99, 118, 141, 0.1);
|
background: rgba(99, 118, 141, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-card-link {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.product-media img {
|
.product-media img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -545,7 +555,7 @@
|
|||||||
|
|
||||||
.product-actions {
|
.product-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
top: 18px;
|
top: 18px;
|
||||||
left: 18px;
|
left: 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -604,6 +614,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-name {
|
.product-name {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
{% url 'detail' item.category.slug item.slug as item_url %}
|
||||||
<article class="item-card{% if item.id in favorite_item_ids %} is-favorite{% endif %}" data-item-card="{{ item.id }}">
|
<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 }}">
|
<a class="item-card-overlay" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}" tabindex="-1" aria-hidden="true"></a>
|
||||||
|
<a class="item-card-media-link" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}">
|
||||||
{% with primary_image=item.primary_image %}
|
{% with primary_image=item.primary_image %}
|
||||||
<div class="item-card-image">
|
<div class="item-card-image">
|
||||||
{% if primary_image %}
|
{% if primary_image %}
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="item-card-title-link" href="{% url 'detail' item.category.slug item.slug %}" aria-label="Открыть товар {{ item.name }}">
|
<a class="item-card-title-link" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}">
|
||||||
<h2 class="item-card-title">{{ item.name }}</h2>
|
<h2 class="item-card-title">{{ item.name }}</h2>
|
||||||
</a>
|
</a>
|
||||||
<div class="item-card-footer">
|
<div class="item-card-footer">
|
||||||
|
|||||||
@@ -104,15 +104,17 @@
|
|||||||
</h2>
|
</h2>
|
||||||
{% for sale_item in sidebar_sale_items %}
|
{% for sale_item in sidebar_sale_items %}
|
||||||
<div class="sidebar-mini-card">
|
<div class="sidebar-mini-card">
|
||||||
|
{% url 'detail' sale_item.category.slug sale_item.slug as sale_item_url %}
|
||||||
|
<a class="sidebar-mini-card__overlay" href="{{ sale_item_url }}" aria-label="Открыть товар {{ sale_item.name }}" tabindex="-1" aria-hidden="true"></a>
|
||||||
{% with img=sale_item.primary_image %}
|
{% with img=sale_item.primary_image %}
|
||||||
{% if img %}
|
{% if img %}
|
||||||
<a href="{% url 'detail' sale_item.category.slug sale_item.slug %}" class="sidebar-mini-card__img-link">
|
<a href="{{ sale_item_url }}" class="sidebar-mini-card__img-link">
|
||||||
<img src="{{ img.image.url }}" alt="{{ img.alt|default:sale_item.name }}">
|
<img src="{{ img.image.url }}" alt="{{ img.alt|default:sale_item.name }}">
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="sidebar-mini-card__body">
|
<div class="sidebar-mini-card__body">
|
||||||
<a href="{% url 'detail' sale_item.category.slug sale_item.slug %}" class="sidebar-mini-card__name">{{ sale_item.name }}</a>
|
<a href="{{ sale_item_url }}" class="sidebar-mini-card__name">{{ sale_item.name }}</a>
|
||||||
<span class="sidebar-mini-card__price">{{ sale_item.display_price }} руб.</span>
|
<span class="sidebar-mini-card__price">{{ sale_item.display_price }} руб.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,15 +132,17 @@
|
|||||||
</h2>
|
</h2>
|
||||||
{% for new_item in sidebar_new_items %}
|
{% for new_item in sidebar_new_items %}
|
||||||
<div class="sidebar-mini-card">
|
<div class="sidebar-mini-card">
|
||||||
|
{% url 'detail' new_item.category.slug new_item.slug as new_item_url %}
|
||||||
|
<a class="sidebar-mini-card__overlay" href="{{ new_item_url }}" aria-label="Открыть товар {{ new_item.name }}" tabindex="-1" aria-hidden="true"></a>
|
||||||
{% with img=new_item.primary_image %}
|
{% with img=new_item.primary_image %}
|
||||||
{% if img %}
|
{% if img %}
|
||||||
<a href="{% url 'detail' new_item.category.slug new_item.slug %}" class="sidebar-mini-card__img-link">
|
<a href="{{ new_item_url }}" class="sidebar-mini-card__img-link">
|
||||||
<img src="{{ img.image.url }}" alt="{{ img.alt|default:new_item.name }}">
|
<img src="{{ img.image.url }}" alt="{{ img.alt|default:new_item.name }}">
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="sidebar-mini-card__body">
|
<div class="sidebar-mini-card__body">
|
||||||
<a href="{% url 'detail' new_item.category.slug new_item.slug %}" class="sidebar-mini-card__name">{{ new_item.name }}</a>
|
<a href="{{ new_item_url }}" class="sidebar-mini-card__name">{{ new_item.name }}</a>
|
||||||
<span class="sidebar-mini-card__price">{{ new_item.display_price }} руб.</span>
|
<span class="sidebar-mini-card__price">{{ new_item.display_price }} руб.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
{% for item in home_products %}
|
{% for item in home_products %}
|
||||||
{% url 'detail' item.category.slug item.slug as item_url %}
|
{% url 'detail' item.category.slug item.slug as item_url %}
|
||||||
<article class="product product-{{ forloop.counter }}">
|
<article class="product product-{{ forloop.counter }}">
|
||||||
|
<a class="product-card-link" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}" tabindex="-1" aria-hidden="true"></a>
|
||||||
<a class="product-media" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}">
|
<a class="product-media" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}">
|
||||||
{% with primary_image=item.primary_image %}
|
{% with primary_image=item.primary_image %}
|
||||||
{% if primary_image %}
|
{% if primary_image %}
|
||||||
|
|||||||
@@ -25,8 +25,10 @@
|
|||||||
<div class="cart-items">
|
<div class="cart-items">
|
||||||
{% for cart_item in cart_items %}
|
{% for cart_item in cart_items %}
|
||||||
{% with item=cart_item.item %}
|
{% with item=cart_item.item %}
|
||||||
|
{% url 'detail' item.category.slug item.slug as item_url %}
|
||||||
<article class="cart-item" data-auth-cart-item data-current-quantity="{{ cart_item.quantity }}">
|
<article class="cart-item" data-auth-cart-item data-current-quantity="{{ cart_item.quantity }}">
|
||||||
<a class="cart-item__media" href="{% url 'detail' item.category.slug item.slug %}">
|
<a class="cart-item__overlay" href="{{ item_url }}" aria-label="Открыть товар {{ item.name }}" tabindex="-1" aria-hidden="true"></a>
|
||||||
|
<a class="cart-item__media" href="{{ item_url }}">
|
||||||
{% with primary_image=item.primary_image %}
|
{% with primary_image=item.primary_image %}
|
||||||
{% if primary_image %}
|
{% if primary_image %}
|
||||||
<img src="{{ primary_image.image.url }}" alt="{{ primary_image.alt|default:item.name }}">
|
<img src="{{ primary_image.image.url }}" alt="{{ primary_image.alt|default:item.name }}">
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="cart-item__info">
|
<div class="cart-item__info">
|
||||||
<a class="cart-item__title" href="{% url 'detail' item.category.slug item.slug %}">{{ item.name }}</a>
|
<a class="cart-item__title" href="{{ item_url }}">{{ item.name }}</a>
|
||||||
{% if cart_item.selected_color %}
|
{% if cart_item.selected_color %}
|
||||||
<span class="cart-item__color">({{ cart_item.selected_color.name }})</span>
|
<span class="cart-item__color">({{ cart_item.selected_color.name }})</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user