В этом проекте мы реализуем мини-систему кассового аппарата с использованием Django и Django REST Framework. Она позволяет пользователю отправить список товаров по API и получить в ответ:
-
чек в формате PDF;
-
сгенерированный QR-код, ведущий к этому чеку;
-
ссылку на скачивание PDF.
Это может быть полезно для интернет-магазинов, офлайн-касс, киосков самообслуживания или даже курсов по Python 😄.
Создание проекта
Создаём новый проект в PyCharm и называем его cash_project
.
Открываем терминал и устанавливаем необходимые библиотеки:
pip install Django # сам фреймворк
pip install djangorestframework # для API-запросов
pip install pdfkit # для генерации PDF
pip install qrcode # для создания QR-кодов
Создание проекта и приложения
django-admin startproject config
cd config
python manage.py startapp aap
Настройка settings.py
Добавляем в конец INSTALLED_APPS
:
'rest_framework',
'aap',
Добавляем настройки для медиафайлов:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Настройка маршрутов (urls.py
)
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from aap.views import CashMachineView, serve_pdf
urlpatterns = [
path('admin/', admin.site.urls),
path('cash_machine/', CashMachineView.as_view(), name='cash_machine'), # маршрут для генерации PDF и QR-кода
path('media/<str:filename>', serve_pdf, name='serve_pdf'), # маршрут получения PDF чеков
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Модель Item
(models.py
)
from django.db import models
class Item(models.Model):
title = models.CharField(max_length=255, verbose_name="Наименование товара")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Стоимость")
def __str__(self):
return self.title
class Meta:
verbose_name = "Товар"
verbose_name_plural = "Товары"
Модель предназначена для хранения информации о товарах.
Сериализаторы (serializers.py
)
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ['id', 'title', 'price']
class ReceiptSerializer(serializers.Serializer):
items = serializers.ListField(
child=serializers.IntegerField(),
allow_empty=False
)
Основная логика (views.py
)
import os
import qrcode
import pdfkit
from django.conf import settings
from django.http import JsonResponse, FileResponse
from django.template.loader import render_to_string
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Item
from .serializers import ReceiptSerializer
from datetime import datetime
from pdfkit.configuration import Configuration
class CashMachineView(APIView):
def post(self, request):
serializer = ReceiptSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
item_ids = serializer.validated_data['items']
items = Item.objects.filter(id__in=item_ids)
# Проверяем что все товары найдены
found_ids = {item.id for item in items}
not_found = [i for i in item_ids if i not in found_ids]
if not_found:
return Response(
{"error": f"Items not found: {not_found}"},
status=status.HTTP_404_NOT_FOUND
)
# Подсчет количества и суммы
item_counts = {}
for item_id in item_ids:
item_counts[item_id] = item_counts.get(item_id, 0) + 1
receipt_items = []
total = 0
for item in items:
quantity = item_counts[item.id]
item_total = item.price * quantity
receipt_items.append({
'title': item.title,
'quantity': quantity,
'price': float(item.price),
'total': float(item_total)
})
total += item_total
# Подготовка данных для чека
context = {
'items': receipt_items,
'total': float(total),
'date': datetime.now().strftime("%d.%m.%Y %H:%M"),
'company_name': 'ООО "КОМПАНИЯ К"',
'cashier': 'АДМИ',
'receipt_number': datetime.now().strftime("%Y%m%d%H%M%S"),
}
# Генерация HTML
html_content = render_to_string('receipt_template.html', context)
# Создаем папку media если нет
os.makedirs(settings.MEDIA_ROOT, exist_ok=True)
pdf_filename = f"receipt_{context['receipt_number']}.pdf"
pdf_path = os.path.join(settings.MEDIA_ROOT, pdf_filename)
try:
# Конфигурация для Windows
config = Configuration(
wkhtmltopdf=settings.WKHTMLTOPDF_PATH
)
options = {
'encoding': 'UTF-8',
'quiet': '',
'enable-local-file-access': '',
'margin-top': '0mm',
'margin-right': '0mm',
'margin-bottom': '0mm',
'margin-left': '0mm',
'page-size': 'A7',
'disable-smart-shrinking': '',
}
pdfkit.from_string(
html_content,
pdf_path,
configuration=config,
options=options
)
except Exception as e:
return Response(
{
"error": "PDF generation failed",
"details": str(e),
"solution": "Check wkhtmltopdf installation at " + settings.WKHTMLTOPDF_PATH
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Генерация QR-кода
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(f"http://192.168.31.141:8080/{settings.MEDIA_URL}{pdf_filename}")
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
qr_filename = f"qr_{context['receipt_number']}.png"
qr_path = os.path.join(settings.MEDIA_ROOT, qr_filename)
qr_img.save(qr_path)
except Exception as e:
# Удаляем PDF если не удалось создать QR-код
if os.path.exists(pdf_path):
os.remove(pdf_path)
return Response(
{"error": f"QR code generation failed: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
return Response({
'qr_code_url': f"http://192.168.31.141:8080/{settings.MEDIA_URL}{qr_filename}",
'pdf_url': f"http://192.168.31.141:8080/{settings.MEDIA_URL}{pdf_filename}",
'receipt_number': context['receipt_number']
})
def serve_pdf(request, filename):
file_path = os.path.join(settings.MEDIA_ROOT, filename)
if os.path.exists(file_path):
return FileResponse(
open(file_path, 'rb'),
content_type='application/png',
as_attachment=False
)
return JsonResponse(
{'error': 'File not found'},
status=status.HTTP_404_NOT_FOUND
)
Регистрация модели в админке (admin.py
)
from django.contrib import admin
from .models import Item
admin.site.register(Item)
Шаблон receipt_template.html
Создаем шаблон receipt_template.html в папке templates
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Кассовый чек</title>
<style>
body {
font-family: Arial, sans-serif;
width: 80mm;
margin: 0 auto;
padding: 10px;
font-size: 14px;
}
.header {
text-align: center;
margin-bottom: 10px;
}
.company-name {
font-weight: bold;
margin-bottom: 5px;
}
.welcome {
margin-bottom: 10px;
}
.item {
margin-bottom: 5px;
border-bottom: 1px dashed #ccc;
padding-bottom: 5px;
}
.item-title {
font-weight: bold;
}
.item-quantity {
display: inline-block;
width: 20px;
}
.item-price {
float: right;
}
.total {
font-weight: bold;
text-align: right;
margin-top: 10px;
border-top: 1px solid #000;
padding-top: 5px;
}
.payment {
margin-top: 5px;
text-align: right;
}
.footer {
margin-top: 15px;
text-align: center;
font-size: 12px;
}
</style>
</head>
<body>
<div class="header">
<div class="company-name">{{ company_name }}</div>
<div class="welcome">Добро пожаловать</div>
<div>ККМ 00075411 #3969</div>
<div>ИНН 1087746942040</div>
<div>ЭКЛЗ 3851495566</div>
<div>{{ date }} СИС.</div>
</div>
<div class="cashier">{{ cashier }}</div>
<div class="items">
{% for item in items %}
<div class="item">
<div class="item-title">{{ item.title }}</div>
<div>
<span class="item-quantity">{{ item.quantity }}</span> x {{ item.price }} =
<span class="item-price">{{ item.total }}</span>
</div>
</div>
{% endfor %}
</div>
<div class="total">ИТОГ {{ total }}</div>
<div class="payment">НАЛИЧНЫМИ ={{ total }}</div>
<div class="footer">
***************<br>
{{ receipt_number }}# 05970
</div>
</body>
</html>
Выполняем миграции:
python manage.py makemigrations
python manage.py migrate
Создаём суперпользователя:
python manage.py createsuperuser
Запускаем сервер:
python manage.py runserver
Переходим по адресу: http://127.0.0.1:8000/admin/
— авторизуемся и добавляем товары.
Открываем http://127.0.0.1:8000/cash_machine/
в Postman или другом REST-клиенте, делаем POST-запрос с телом:
{
"items": [1, 2, 3]
}
Автор: Евгений Морковин
0 комментариев
Оставьте комментарий
Комментарии