feat: создать базовый шаблон и структуру для страницы каталога
All checks were successful
Build and Push Docker Image / build (push) Successful in 26s

- Создан шаблон `catalog/index.html` с поддержкой хлебных крошек и активного раздела
- Добавлены модели `Category` и `Item` с настройкой админ-панели
- Реализован обработчик URL для отображения категорий и страницы каталога
- Добавлены стили `catalog.css` и `error.css` для новых элементов
- Переведён проект в production режим (DEBUG=False)
This commit is contained in:
2026-04-07 22:30:30 +02:00
parent bbf43d29a7
commit a2702b13c7
10 changed files with 147 additions and 6 deletions

View File

@@ -1,3 +1,16 @@
from django.contrib import admin
# Register your models here.
from .models import Category, Item
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
prepopulated_fields = {'slug': ('name',)}
@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'price', 'is_available', 'created_at')
list_filter = ('is_available', 'category', 'created_at')
prepopulated_fields = {'slug': ('name',)}

View File

@@ -1,3 +1,31 @@
from django.db import models
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=255, verbose_name='Название')
slug = models.SlugField(unique=True, verbose_name='URL')
class Meta:
verbose_name = 'Категория'
verbose_name_plural = 'Категории'
def __str__(self):
return self.name
class Item(models.Model):
name = models.CharField(max_length=255, verbose_name='Название')
slug = models.SlugField(unique=True, verbose_name='URL')
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='items', verbose_name='Категория')
description = models.TextField(blank=True, verbose_name='Описание')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Цена')
is_available = models.BooleanField(default=True, verbose_name='В наличии')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата обновления')
class Meta:
verbose_name = 'Товар'
verbose_name_plural = 'Товары'
def __str__(self):
return self.name

View File

@@ -1,3 +1,8 @@
from django.urls import path
from . import views
urlpatterns = []
urlpatterns = [
path('', views.index),
path('<str:category>/', views.index)
]

View File

@@ -1,3 +1,29 @@
from django.shortcuts import render
from dataclasses import dataclass
from django.http import HttpRequest, HttpResponse, Http404
from django.shortcuts import render, redirect
from items.models import Category
# Create your views here.
@dataclass
class SelectedCategory:
name: str | None
id: str | None
def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
selected_category = SelectedCategory(name=None, id=None)
if category is not None:
try:
cat = Category.objects.get(slug=category.lower())
selected_category = SelectedCategory(name=cat.name, id=cat.slug)
except Category.DoesNotExist:
raise Http404()
return render(request, 'catalog/index.html', {
'categories': Category.objects.all(),
'selected_category': selected_category
})

View File

@@ -22,7 +22,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-*%u*i415=%@ny05mbd-j3802bcl!(0nwa3#^yxnor)7nd(w7%q'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False
ALLOWED_HOSTS = [
"127.0.0.1",

0
static/css/catalog.css Normal file
View File

50
static/css/error.css Normal file
View File

@@ -0,0 +1,50 @@
.error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 100px;
& > .error-code {
font-size: 20rem;
color: #63768d;
margin: 0;
}
& > .error-subtitle {
font-size: 2rem;
color: #63768d;
}
& > .error-description {
margin-top: 20px;
font-size: 1.1rem;
text-align: center;
color: #554971;
}
& > .error-actions {
margin-top: 20px;
& > a {
display: inline-flex;
padding: 20px 30px;
width: 300px;
margin: 0 10px;
align-items: center;
justify-content: center;
}
& > .error-btn-1 {
background: #63768d;
color: white;
border: 0;
}
& > .error-btn-2 {
background: #ffffff;
color: #63768d;
border: 1px solid #63768d;
}
}
}

View File

@@ -557,6 +557,7 @@
.faq-section {
margin-top: 120px;
padding-bottom: 90px;
}
.faq-section h2 {

View File

@@ -316,7 +316,6 @@ footer {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 40px;
margin-top: 60px;
}
.footer-logo {

View File

@@ -0,0 +1,19 @@
{% extends "_layouts/base.html" %}
{% load static %}
{% block title %}Каталог{% endblock %}
{% block add_css %}
<link rel="stylesheet" href="{% static "css/catalog.css" %}">
{% endblock %}
{% block content %}
<!-- HEADER -->
{% include "_fragments/header.html" with active="catalog" %}
{% if selected_category.id == None %}
{% include '_fragments/breadcrumbs.html' with url_1='/' text_1='Главная' url_2='/catalog/' text_2='Каталог' %}
{% else %}
{% include '_fragments/breadcrumbs.html' with url_1='/' text_1='Главная' url_2='/catalog/' text_2='Каталог' text_3=selected_category.name %}
{% endif %}
{% include "_fragments/footer.html" %}
{% endblock %}