diehard1207 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diehard1207-1.0.0/MANIFEST.in +7 -0
- diehard1207-1.0.0/PKG-INFO +8 -0
- diehard1207-1.0.0/README.md +0 -0
- diehard1207-1.0.0/core/__init__.py +0 -0
- diehard1207-1.0.0/core/admin.py +56 -0
- diehard1207-1.0.0/core/apps.py +5 -0
- diehard1207-1.0.0/core/backends.py +16 -0
- diehard1207-1.0.0/core/csv_import/SQL.txt +50 -0
- diehard1207-1.0.0/core/csv_import/backup.txt +297 -0
- diehard1207-1.0.0/core/csv_import/order_items.csv +21 -0
- diehard1207-1.0.0/core/csv_import/orders.csv +11 -0
- diehard1207-1.0.0/core/csv_import/pickup_points.csv +37 -0
- diehard1207-1.0.0/core/csv_import/positions.csv +4 -0
- diehard1207-1.0.0/core/csv_import/products.csv +11 -0
- diehard1207-1.0.0/core/csv_import/roles.csv +4 -0
- diehard1207-1.0.0/core/csv_import/users.csv +11 -0
- diehard1207-1.0.0/core/migrations/0001_initial.py +112 -0
- diehard1207-1.0.0/core/migrations/__init__.py +0 -0
- diehard1207-1.0.0/core/models.py +114 -0
- diehard1207-1.0.0/core/static/core/css/custom_admin.css +30 -0
- diehard1207-1.0.0/core/static/core/js/admin_discount.js +11 -0
- diehard1207-1.0.0/core/templates/admin/base_site.html +10 -0
- diehard1207-1.0.0/core/templates/admin/login.html +8 -0
- diehard1207-1.0.0/core/tests.py +3 -0
- diehard1207-1.0.0/core/views.py +3 -0
- diehard1207-1.0.0/diehard1207.egg-info/PKG-INFO +8 -0
- diehard1207-1.0.0/diehard1207.egg-info/SOURCES.txt +37 -0
- diehard1207-1.0.0/diehard1207.egg-info/dependency_links.txt +1 -0
- diehard1207-1.0.0/diehard1207.egg-info/requires.txt +2 -0
- diehard1207-1.0.0/diehard1207.egg-info/top_level.txt +4 -0
- diehard1207-1.0.0/main.py +16 -0
- diehard1207-1.0.0/manage.py +22 -0
- diehard1207-1.0.0/pyproject.toml +21 -0
- diehard1207-1.0.0/setup.cfg +4 -0
- diehard1207-1.0.0/toy_shop_project/__init__.py +0 -0
- diehard1207-1.0.0/toy_shop_project/asgi.py +16 -0
- diehard1207-1.0.0/toy_shop_project/settings.py +132 -0
- diehard1207-1.0.0/toy_shop_project/urls.py +39 -0
- diehard1207-1.0.0/toy_shop_project/wsgi.py +16 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
# Register your models here.
|
|
4
|
+
import os
|
|
5
|
+
from django.contrib import admin
|
|
6
|
+
from django.utils.html import format_html
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from .models import Roles, Users, PickupPoints, Products, Orders, OrderItems
|
|
9
|
+
|
|
10
|
+
admin.site.site_header = "ООО «МирИгрушек» — Панель управления"
|
|
11
|
+
|
|
12
|
+
class OrderItemsInline(admin.TabularInline):
|
|
13
|
+
model = OrderItems
|
|
14
|
+
extra = 1
|
|
15
|
+
|
|
16
|
+
@admin.register(Orders)
|
|
17
|
+
class OrdersAdmin(admin.ModelAdmin):
|
|
18
|
+
list_display = ('order_id', 'order_date', 'delivery_date', 'pickup_point', 'client', 'order_status')
|
|
19
|
+
list_display_links = ('order_id',)
|
|
20
|
+
list_filter = ('order_status', 'order_date')
|
|
21
|
+
search_fields = ('order_id', 'client__fio')
|
|
22
|
+
inlines = [OrderItemsInline]
|
|
23
|
+
|
|
24
|
+
def has_change_permission(self, request, obj=None): return request.user.role_id == 1
|
|
25
|
+
def has_add_permission(self, request): return request.user.role_id == 1
|
|
26
|
+
def has_delete_permission(self, request, obj=None): return request.user.role_id == 1
|
|
27
|
+
|
|
28
|
+
class Media:
|
|
29
|
+
css = {'all': ('core/css/custom_admin.css',)}
|
|
30
|
+
|
|
31
|
+
@admin.register(Products)
|
|
32
|
+
class ProductsAdmin(admin.ModelAdmin):
|
|
33
|
+
list_display = ('display_photo', 'product_name', 'manufacturer', 'price', 'discount', 'stock_quantity')
|
|
34
|
+
list_filter = ('manufacturer',)
|
|
35
|
+
search_fields = ('product_name',)
|
|
36
|
+
ordering = ('price',)
|
|
37
|
+
|
|
38
|
+
def has_add_permission(self, request): return request.user.role_id == 1
|
|
39
|
+
def has_delete_permission(self, request, obj=None): return request.user.role_id == 1
|
|
40
|
+
|
|
41
|
+
def display_photo(self, obj):
|
|
42
|
+
photo_name = obj.photo.strip() if obj.photo else "11.jpg"
|
|
43
|
+
full_path = os.path.join(settings.MEDIA_ROOT, photo_name)
|
|
44
|
+
if os.path.exists(full_path):
|
|
45
|
+
url = f"{settings.MEDIA_URL}{photo_name}"
|
|
46
|
+
return format_html('<img src="{}" width="65" height="65" style="object-fit: contain; border: 1px solid #DEB887; background: #FAFAFA;"/>', url)
|
|
47
|
+
return format_html('<span style="color: #777777; font-size: 11px;">Нет фото</span>')
|
|
48
|
+
display_photo.short_description = "Изображение"
|
|
49
|
+
|
|
50
|
+
class Media:
|
|
51
|
+
css = {'all': ('core/css/custom_admin.css',)}
|
|
52
|
+
js = ('core/js/admin_discount.js',)
|
|
53
|
+
|
|
54
|
+
admin.site.register(Roles)
|
|
55
|
+
admin.site.register(Users)
|
|
56
|
+
admin.site.register(PickupPoints)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.contrib.auth.backends import BaseBackend
|
|
2
|
+
from core.models import Users
|
|
3
|
+
|
|
4
|
+
class ToyShopAuthBackend(BaseBackend):
|
|
5
|
+
def authenticate(self, request, username=None, password=None, **kwargs):
|
|
6
|
+
try:
|
|
7
|
+
user = Users.objects.get(login=username)
|
|
8
|
+
if user.password == password:
|
|
9
|
+
user.update_last_login = False
|
|
10
|
+
return user
|
|
11
|
+
except Users.DoesNotExist:
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
def get_user(self, user_id):
|
|
15
|
+
try: return Users.objects.get(pk=user_id)
|
|
16
|
+
except Users.DoesNotExist: return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
CREATE TABLE roles (
|
|
2
|
+
role_id SERIAL PRIMARY KEY,
|
|
3
|
+
role_name VARCHAR(50) UNIQUE NOT NULL
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
CREATE TABLE users (
|
|
7
|
+
user_id SERIAL PRIMARY KEY,
|
|
8
|
+
fio VARCHAR(150) NOT NULL,
|
|
9
|
+
login VARCHAR(100) UNIQUE NOT NULL,
|
|
10
|
+
password VARCHAR(100) NOT NULL,
|
|
11
|
+
role_id INT REFERENCES roles(role_id) ON DELETE RESTRICT,
|
|
12
|
+
last_login TIMESTAMP WITH TIME ZONE
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE TABLE pickup_points (
|
|
16
|
+
point_id INT PRIMARY KEY,
|
|
17
|
+
address TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE products (
|
|
21
|
+
sku VARCHAR(50) PRIMARY KEY,
|
|
22
|
+
product_name TEXT NOT NULL,
|
|
23
|
+
unit_of_measure VARCHAR(20) NOT NULL,
|
|
24
|
+
price NUMERIC(10, 2) NOT NULL,
|
|
25
|
+
supplier VARCHAR(100) NOT NULL,
|
|
26
|
+
manufacturer VARCHAR(100) NOT NULL,
|
|
27
|
+
category VARCHAR(100) NOT NULL,
|
|
28
|
+
discount INT DEFAULT 0,
|
|
29
|
+
stock_quantity INT NOT NULL,
|
|
30
|
+
description TEXT,
|
|
31
|
+
photo VARCHAR(255)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE orders (
|
|
35
|
+
order_id INT PRIMARY KEY,
|
|
36
|
+
order_date DATE NOT NULL,
|
|
37
|
+
delivery_date DATE NOT NULL,
|
|
38
|
+
pickup_point_id INT REFERENCES pickup_points(point_id) ON DELETE RESTRICT,
|
|
39
|
+
client_id INT REFERENCES users(user_id) ON DELETE RESTRICT,
|
|
40
|
+
pickup_code INT NOT NULL,
|
|
41
|
+
order_status VARCHAR(50) NOT NULL
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE order_items (
|
|
45
|
+
id SERIAL PRIMARY KEY,
|
|
46
|
+
order_id INT REFERENCES orders(order_id) ON DELETE CASCADE,
|
|
47
|
+
sku VARCHAR(50) REFERENCES products(sku) ON DELETE RESTRICT,
|
|
48
|
+
quantity INT NOT NULL,
|
|
49
|
+
CONSTRAINT order_items_order_id_sku_key UNIQUE (order_id, sku)
|
|
50
|
+
);
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import os, sys, psycopg2
|
|
2
|
+
from psycopg2.extras import DictCursor
|
|
3
|
+
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
4
|
+
QPushButton, QStackedWidget, QListWidget, QListWidgetItem, QComboBox, QMessageBox, QDialog,
|
|
5
|
+
QFormLayout, QDateEdit, QSpinBox, QDoubleSpinBox)
|
|
6
|
+
from PyQt6.QtCore import QDate, Qt
|
|
7
|
+
from PyQt6.QtGui import QPixmap, QColor
|
|
8
|
+
|
|
9
|
+
DB_CFG = {"host": "localhost", "port": 5432, "user": "postgres", "password": "12072005", "dbname": "381KashutinDE"}
|
|
10
|
+
IMG_DIR = r"D:\Загрузки\17 июня\17 июня\Прил_В4_КОД 09.02.07-2-2026-ПУ\Модуль 1\import"
|
|
11
|
+
CSS = "* { font-family: 'Arial'; font-size: 14px; background-color: #FFFFFF; } QPushButton { background-color: #F5DEB3; font-weight: bold; }"
|
|
12
|
+
|
|
13
|
+
class DB:
|
|
14
|
+
@staticmethod
|
|
15
|
+
def run(q, p=(), f=None):
|
|
16
|
+
try:
|
|
17
|
+
c = psycopg2.connect(**DB_CFG, cursor_factory=DictCursor)
|
|
18
|
+
cur = c.cursor()
|
|
19
|
+
cur.execute(q, p)
|
|
20
|
+
res = cur.fetchall() if f == 'all' else cur.fetchone() if f == 'one' else c.commit() or True
|
|
21
|
+
c.close()
|
|
22
|
+
return res
|
|
23
|
+
except Exception as e:
|
|
24
|
+
QMessageBox.critical(None, "Ошибка БД", str(e))
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
class ProdWidget(QWidget):
|
|
28
|
+
def __init__(self, i, p=None):
|
|
29
|
+
super().__init__(p)
|
|
30
|
+
lay = QHBoxLayout(self)
|
|
31
|
+
img = QLabel()
|
|
32
|
+
pix = QPixmap(os.path.join(IMG_DIR, str(i.get('photo') or '11.jpg').strip() or '11.jpg')).scaled(100, 100)
|
|
33
|
+
img.setPixmap(pix)
|
|
34
|
+
txt = QLabel(
|
|
35
|
+
f"<b>{i['product_name']}</b> (Артикул: {i['sku']})<br>{i['description'] or '-'}<br>{i['manufacturer']} | <b>{i['price']} руб.</b><br>Скидка: {i['discount']}% | В наличии: {i['stock_quantity']}")
|
|
36
|
+
txt.setWordWrap(True)
|
|
37
|
+
lay.addWidget(img)
|
|
38
|
+
lay.addWidget(txt)
|
|
39
|
+
|
|
40
|
+
class ProdDlg(QDialog):
|
|
41
|
+
def __init__(self, sku=None, p=None):
|
|
42
|
+
super().__init__(p)
|
|
43
|
+
self.sku = sku
|
|
44
|
+
self.setWindowTitle("Редактирование товара")
|
|
45
|
+
self.ui = {
|
|
46
|
+
'sku': QLineEdit(),
|
|
47
|
+
'name': QLineEdit(),
|
|
48
|
+
'desc': QLineEdit(),
|
|
49
|
+
'mfg': QLineEdit(),
|
|
50
|
+
'price': QDoubleSpinBox(),
|
|
51
|
+
'disc': QSpinBox(),
|
|
52
|
+
'stock': QSpinBox()
|
|
53
|
+
}
|
|
54
|
+
self.ui['sku'].setEnabled(not sku)
|
|
55
|
+
self.ui['price'].setRange(0, 999999999)
|
|
56
|
+
self.ui['disc'].setRange(0, 100)
|
|
57
|
+
self.ui['stock'].setRange(0, 99999999)
|
|
58
|
+
|
|
59
|
+
lay = QFormLayout(self)
|
|
60
|
+
labels = ["Артикул:", "Название:", "Описание:", "ПОставшик:", "Цена:", "Скидка:", "Кол-во:"]
|
|
61
|
+
for l, k in zip(labels, self.ui.keys()):
|
|
62
|
+
lay.addRow(l, self.ui[k])
|
|
63
|
+
|
|
64
|
+
b1, b2 = QPushButton("Сохранить"), QPushButton("Отмена")
|
|
65
|
+
b1.clicked.connect(self.save)
|
|
66
|
+
b2.clicked.connect(self.reject)
|
|
67
|
+
lay.addRow(b1, b2)
|
|
68
|
+
|
|
69
|
+
if sku and (r := DB.run("SELECT * FROM products WHERE sku = %s;", (sku,), 'one')):
|
|
70
|
+
self.ui['sku'].setText(r['sku'])
|
|
71
|
+
self.ui['name'].setText(r['product_name'])
|
|
72
|
+
self.ui['desc'].setText(r['description'] or '')
|
|
73
|
+
self.ui['mfg'].setText(r['manufacturer'] or '')
|
|
74
|
+
self.ui['price'].setValue(float(r['price']))
|
|
75
|
+
self.ui['disc'].setValue(r['discount'] or 0)
|
|
76
|
+
self.ui['stock'].setValue(r['stock_quantity'] or 0)
|
|
77
|
+
|
|
78
|
+
def save(self):
|
|
79
|
+
v = {k: w.text() if isinstance(w, QLineEdit) else w.value() for k, w in self.ui.items()}
|
|
80
|
+
if not v['sku'] or not v['name']:
|
|
81
|
+
return QMessageBox.warning(self, "!", "Заполните Артикул и Название")
|
|
82
|
+
|
|
83
|
+
if self.sku:
|
|
84
|
+
DB.run(
|
|
85
|
+
"UPDATE products SET product_name=%s, description=%s, manufacturer=%s, price=%s, discount=%s, stock_quantity=%s WHERE sku=%s;",
|
|
86
|
+
(v['name'], v['desc'], v['mfg'], v['price'], v['disc'], v['stock'], self.sku))
|
|
87
|
+
else:
|
|
88
|
+
if DB.run("SELECT 1 FROM products WHERE sku=%s;", (v['sku'],), 'one'):
|
|
89
|
+
return QMessageBox.warning(self, "!", "Товар с таким артикулом уже есть")
|
|
90
|
+
DB.run(
|
|
91
|
+
"INSERT INTO products (sku, product_name, description, manufacturer, price, discount, stock_quantity) VALUES (%s, %s, %s, %s, %s, %s, %s);",
|
|
92
|
+
(v['sku'], v['name'], v['desc'], v['mfg'], v['price'], v['disc'], v['stock']))
|
|
93
|
+
self.accept()
|
|
94
|
+
|
|
95
|
+
class OrderDlg(QDialog):
|
|
96
|
+
def __init__(self, oid=None, p=None):
|
|
97
|
+
super().__init__(p)
|
|
98
|
+
self.oid = oid
|
|
99
|
+
self.ui = {'id': QSpinBox(), 'd1': QDateEdit(QDate.currentDate()),
|
|
100
|
+
'd2': QDateEdit(QDate.currentDate().addDays(3)), 'pt': QComboBox(), 'cl': QComboBox(),
|
|
101
|
+
'cd': QSpinBox(), 'st': QComboBox()}
|
|
102
|
+
self.ui['id'].setRange(1, 999999)
|
|
103
|
+
self.ui['id'].setValue(oid or 1)
|
|
104
|
+
self.ui['id'].setEnabled(not oid)
|
|
105
|
+
self.ui['cd'].setRange(100, 999)
|
|
106
|
+
self.ui['st'].addItems(["Новый", "Завершен"])
|
|
107
|
+
|
|
108
|
+
lay = QFormLayout(self)
|
|
109
|
+
for l, k in zip(["№:", "Дата:", "Доставка:", "Пункт:", "Клиент:", "Код получения:", "Статус:"],
|
|
110
|
+
self.ui.keys()): lay.addRow(l, self.ui[k])
|
|
111
|
+
|
|
112
|
+
b1, b2 = QPushButton("Сохранить"), QPushButton("Отмена")
|
|
113
|
+
b1.clicked.connect(self.save)
|
|
114
|
+
b2.clicked.connect(self.reject)
|
|
115
|
+
lay.addRow(b1, b2)
|
|
116
|
+
|
|
117
|
+
for r in DB.run("SELECT point_id, address FROM pickup_points;", f='all') or []: self.ui['pt'].addItem(
|
|
118
|
+
r['address'], r['point_id'])
|
|
119
|
+
for r in DB.run("SELECT user_id, fio FROM users WHERE role_id = 3;", f='all') or []: self.ui['cl'].addItem(
|
|
120
|
+
r['fio'], r['user_id'])
|
|
121
|
+
|
|
122
|
+
if oid and (r := DB.run("SELECT * FROM orders WHERE order_id = %s;", (oid,), 'one')):
|
|
123
|
+
self.ui['d1'].setDate(r['order_date'])
|
|
124
|
+
self.ui['d2'].setDate(r['delivery_date'])
|
|
125
|
+
self.ui['pt'].setCurrentIndex(self.ui['pt'].findData(r['pickup_point_id']))
|
|
126
|
+
self.ui['cl'].setCurrentIndex(self.ui['cl'].findData(r['client_id']))
|
|
127
|
+
self.ui['cd'].setValue(r['pickup_code'])
|
|
128
|
+
self.ui['st'].setCurrentText(r['order_status'])
|
|
129
|
+
|
|
130
|
+
def save(self):
|
|
131
|
+
v = {k: w.currentData() if k in ('pt', 'cl') else
|
|
132
|
+
w.currentText() if k == 'st' else
|
|
133
|
+
w.date().toPyDate() if k in ('d1', 'd2') else
|
|
134
|
+
w.value() for k, w in self.ui.items()}
|
|
135
|
+
|
|
136
|
+
if not v['pt'] or not v['cl']:
|
|
137
|
+
return QMessageBox.warning(self, "!", "Заполните списки")
|
|
138
|
+
if self.oid:
|
|
139
|
+
DB.run(
|
|
140
|
+
"UPDATE orders SET order_date=%s, delivery_date=%s, pickup_point_id=%s, client_id=%s, pickup_code=%s, order_status=%s WHERE order_id=%s;",
|
|
141
|
+
(v['d1'], v['d2'], v['pt'], v['cl'], v['cd'], v['st'], self.oid))
|
|
142
|
+
elif DB.run("SELECT 1 FROM orders WHERE order_id=%s;", (v['id'],), 'one'):
|
|
143
|
+
return QMessageBox.warning(self, "!", "Такой заказ уже есть")
|
|
144
|
+
else:
|
|
145
|
+
DB.run(
|
|
146
|
+
"INSERT INTO orders (order_id, order_date, delivery_date, pickup_point_id, client_id, pickup_code, order_status) VALUES (%s, %s, %s, %s, %s, %s, %s);",
|
|
147
|
+
(v['id'], v['d1'], v['d2'], v['pt'], v['cl'], v['cd'], v['st']))
|
|
148
|
+
self.accept()
|
|
149
|
+
|
|
150
|
+
class MainWin(QMainWindow):
|
|
151
|
+
def __init__(self):
|
|
152
|
+
super().__init__()
|
|
153
|
+
self.stk = QStackedWidget()
|
|
154
|
+
self.setCentralWidget(self.stk)
|
|
155
|
+
|
|
156
|
+
lw, mw = QWidget(), QWidget()
|
|
157
|
+
ll, ml = QVBoxLayout(lw), QVBoxLayout(mw)
|
|
158
|
+
|
|
159
|
+
self.l_in, self.p_in = QLineEdit(placeholderText="Логин"), QLineEdit(placeholderText="Пароль",
|
|
160
|
+
echoMode=QLineEdit.EchoMode.Password)
|
|
161
|
+
b_in, b_g = QPushButton("Войти"), QPushButton("Войти как гость")
|
|
162
|
+
b_in.clicked.connect(self.log)
|
|
163
|
+
b_g.clicked.connect(lambda: self.set_u({"role_id": 0, "fio": "Гость"}))
|
|
164
|
+
for w in (QLabel("ВХОД"), self.l_in, self.p_in, b_in, b_g): ll.addWidget(w)
|
|
165
|
+
|
|
166
|
+
self.info, self.b_vw, b_out = QLabel(), QPushButton("Заказы"), QPushButton("Выйти")
|
|
167
|
+
self.b_vw.clicked.connect(self.tg)
|
|
168
|
+
b_out.clicked.connect(self.logout)
|
|
169
|
+
hl = QHBoxLayout()
|
|
170
|
+
hl.addWidget(self.info)
|
|
171
|
+
hl.addWidget(self.b_vw)
|
|
172
|
+
hl.addWidget(b_out)
|
|
173
|
+
ml.addLayout(hl)
|
|
174
|
+
|
|
175
|
+
self.sub = QStackedWidget()
|
|
176
|
+
pw, ow = QWidget(), QWidget()
|
|
177
|
+
pl, ol = QVBoxLayout(pw), QVBoxLayout(ow)
|
|
178
|
+
|
|
179
|
+
self.srch, self.srt, self.mfg = QLineEdit(placeholderText="Поиск"), QComboBox(), QComboBox()
|
|
180
|
+
self.srt.addItems(["Без сортировки", "Цена (возр.)", "Цена (убыв.)"])
|
|
181
|
+
for w in (self.srch, self.srt, self.mfg):
|
|
182
|
+
pl.addWidget(w)
|
|
183
|
+
if w != self.mfg: w.textChanged.connect(self.l_pr) if w == self.srch else w.currentIndexChanged.connect(
|
|
184
|
+
self.l_pr)
|
|
185
|
+
self.mfg.currentIndexChanged.connect(self.l_pr)
|
|
186
|
+
|
|
187
|
+
self.p_lst = QListWidget()
|
|
188
|
+
pl.addWidget(self.p_lst)
|
|
189
|
+
|
|
190
|
+
self.b_edit_prod = QPushButton("Редактировать товар")
|
|
191
|
+
self.b_edit_prod.clicked.connect(self.ed_prod)
|
|
192
|
+
pl.addWidget(self.b_edit_prod)
|
|
193
|
+
|
|
194
|
+
self.btns = [QPushButton(t) for t in ("Добавить", "Изменить", "Удалить")]
|
|
195
|
+
self.btns[0].clicked.connect(lambda: self.ed(0))
|
|
196
|
+
self.btns[1].clicked.connect(lambda: self.ed(1))
|
|
197
|
+
self.btns[2].clicked.connect(self.dl)
|
|
198
|
+
for b in self.btns: ol.addWidget(b)
|
|
199
|
+
self.o_lst = QListWidget()
|
|
200
|
+
ol.addWidget(self.o_lst)
|
|
201
|
+
|
|
202
|
+
self.sub.addWidget(pw)
|
|
203
|
+
self.sub.addWidget(ow)
|
|
204
|
+
ml.addWidget(self.sub)
|
|
205
|
+
self.stk.addWidget(lw)
|
|
206
|
+
self.stk.addWidget(mw)
|
|
207
|
+
|
|
208
|
+
def log(self):
|
|
209
|
+
if r := DB.run("SELECT * FROM users WHERE login=%s AND password=%s;", (self.l_in.text(), self.p_in.text()),
|
|
210
|
+
'one'):
|
|
211
|
+
self.set_u(dict(r))
|
|
212
|
+
else:
|
|
213
|
+
QMessageBox.warning(self, "!", "Ошибка входа")
|
|
214
|
+
|
|
215
|
+
def set_u(self, d):
|
|
216
|
+
self.u = d
|
|
217
|
+
self.info.setText(d['fio'])
|
|
218
|
+
for w in (self.srch, self.srt, self.mfg, self.b_vw): w.setVisible(d.get('role_id') in [1, 2])
|
|
219
|
+
for b in self.btns: b.setVisible(d.get('role_id') == 1)
|
|
220
|
+
self.b_edit_prod.setVisible(d.get('role_id') == 1)
|
|
221
|
+
|
|
222
|
+
self.mfg.blockSignals(True)
|
|
223
|
+
self.mfg.clear()
|
|
224
|
+
self.mfg.addItem("Все поставщики")
|
|
225
|
+
for m in DB.run("SELECT DISTINCT manufacturer FROM products WHERE manufacturer IS NOT NULL;",
|
|
226
|
+
f='all') or []: self.mfg.addItem(m[0])
|
|
227
|
+
self.mfg.blockSignals(False)
|
|
228
|
+
|
|
229
|
+
self.sub.setCurrentIndex(0)
|
|
230
|
+
self.b_vw.setText("Заказы")
|
|
231
|
+
self.l_pr()
|
|
232
|
+
self.stk.setCurrentIndex(1)
|
|
233
|
+
self.showMaximized()
|
|
234
|
+
|
|
235
|
+
def logout(self):
|
|
236
|
+
self.stk.setCurrentIndex(0)
|
|
237
|
+
self.showNormal()
|
|
238
|
+
self.resize(400, 300)
|
|
239
|
+
|
|
240
|
+
def tg(self):
|
|
241
|
+
p = self.sub.currentIndex() == 0
|
|
242
|
+
self.sub.setCurrentIndex(int(p))
|
|
243
|
+
self.b_vw.setText("Товары" if p else "Заказы")
|
|
244
|
+
self.l_ord() if p else self.l_pr()
|
|
245
|
+
|
|
246
|
+
def l_pr(self):
|
|
247
|
+
self.p_lst.clear()
|
|
248
|
+
q, p = "SELECT * FROM products WHERE 1=1", []
|
|
249
|
+
if self.u.get('role_id') in [1, 2]:
|
|
250
|
+
if t := self.srch.text(): q += " AND product_name ILIKE %s"; p.append(f"%{t}%")
|
|
251
|
+
if (m := self.mfg.currentText()) != "Все поставщики": q += " AND manufacturer=%s"; p.append(m)
|
|
252
|
+
q += ["", " ORDER BY price ASC", " ORDER BY price DESC"][self.srt.currentIndex()]
|
|
253
|
+
|
|
254
|
+
for i in DB.run(q, p, 'all') or []:
|
|
255
|
+
li = QListWidgetItem(self.p_lst)
|
|
256
|
+
li.setData(Qt.ItemDataRole.UserRole, i['sku'])
|
|
257
|
+
w = ProdWidget(i)
|
|
258
|
+
li.setSizeHint(w.sizeHint())
|
|
259
|
+
if i['discount'] > 17: li.setBackground(QColor("#FFDEAD"))
|
|
260
|
+
self.p_lst.setItemWidget(li, w)
|
|
261
|
+
|
|
262
|
+
def ed_prod(self):
|
|
263
|
+
if not self.p_lst.currentItem():
|
|
264
|
+
return QMessageBox.warning(self, "!", "Выберите товар для редактирования!")
|
|
265
|
+
sku = self.p_lst.currentItem().data(Qt.ItemDataRole.UserRole)
|
|
266
|
+
dlg = ProdDlg(sku, self)
|
|
267
|
+
if dlg.exec():
|
|
268
|
+
self.l_pr()
|
|
269
|
+
|
|
270
|
+
def l_ord(self):
|
|
271
|
+
self.o_lst.clear()
|
|
272
|
+
q = "SELECT o.*, p.address, u.fio, COALESCE(string_agg(oi.sku || ' (' || oi.quantity || ' шт)', ', '), '-') as items FROM orders o LEFT JOIN pickup_points p ON o.pickup_point_id=p.point_id LEFT JOIN users u ON o.client_id=u.user_id LEFT JOIN order_items oi ON o.order_id=oi.order_id GROUP BY o.order_id, p.address, u.fio ORDER BY o.order_id DESC;"
|
|
273
|
+
for o in DB.run(q, f='all') or []:
|
|
274
|
+
i = QListWidgetItem(
|
|
275
|
+
f"№ {o['order_id']} | Статус: {o['order_status']} | Доставка: {o['delivery_date']}\nПункт: {o['address']}\nКлиент: {o['fio']}\nТовары: {o['items']}")
|
|
276
|
+
i.setData(Qt.ItemDataRole.UserRole, o['order_id'])
|
|
277
|
+
self.o_lst.addItem(i)
|
|
278
|
+
|
|
279
|
+
def ed(self, e=0):
|
|
280
|
+
if e and not self.o_lst.currentItem(): return QMessageBox.warning(self, "!", "Выберите заказ")
|
|
281
|
+
oid = self.o_lst.currentItem().data(Qt.ItemDataRole.UserRole) if e else None
|
|
282
|
+
|
|
283
|
+
dlg = OrderDlg(oid, self)
|
|
284
|
+
if dlg.exec(): self.l_ord()
|
|
285
|
+
|
|
286
|
+
def dl(self):
|
|
287
|
+
if (i := self.o_lst.currentItem()) and QMessageBox.question(self, "?",
|
|
288
|
+
"Удалить?") == QMessageBox.StandardButton.Yes:
|
|
289
|
+
if DB.run("DELETE FROM orders WHERE order_id=%s;", (i.data(Qt.ItemDataRole.UserRole),)): self.l_ord()
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
app = QApplication(sys.argv)
|
|
293
|
+
app.setStyleSheet(CSS)
|
|
294
|
+
w = MainWin()
|
|
295
|
+
w.resize(400, 300)
|
|
296
|
+
w.show()
|
|
297
|
+
sys.exit(app.exec())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
order_id;sku;quantity
|
|
2
|
+
1;PMEZMH;2
|
|
3
|
+
1;BPV4MM;2
|
|
4
|
+
2;JVL42J;1
|
|
5
|
+
2;F895RB;1
|
|
6
|
+
3;3XBOTN;10
|
|
7
|
+
3;3L7RCZ;10
|
|
8
|
+
4;S72AM3;5
|
|
9
|
+
4;2G3280;4
|
|
10
|
+
5;MIO8YV;2
|
|
11
|
+
5;UER2QD;2
|
|
12
|
+
6;PMEZMH;2
|
|
13
|
+
6;BPV4MM;2
|
|
14
|
+
7;JVL42J;1
|
|
15
|
+
7;F895RB;1
|
|
16
|
+
8;3XBOTN;10
|
|
17
|
+
8;3L7RCZ;10
|
|
18
|
+
9;S72AM3;5
|
|
19
|
+
9;2G3280;4
|
|
20
|
+
10;MIO8YV;2
|
|
21
|
+
10;UER2QD;2
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
order_id;order_date;delivery_date;pickup_point_id;client_id;pickup_code;order_status
|
|
2
|
+
1;2025-02-27;2025-04-20;1;7;901;Завершен
|
|
3
|
+
2;2024-09-28;2025-04-21;11;8;902;Завершен
|
|
4
|
+
3;2025-03-21;2025-04-22;2;9;903;Завершен
|
|
5
|
+
4;2025-02-20;2025-04-23;11;10;904;Завершен
|
|
6
|
+
5;2025-03-17;2025-04-24;2;7;905;Завершен
|
|
7
|
+
6;2025-03-01;2025-04-25;15;8;906;Завершен
|
|
8
|
+
7;2025-03-01;2025-04-26;3;9;907;Завершен
|
|
9
|
+
8;2025-03-31;2025-04-27;19;10;908;Новый
|
|
10
|
+
9;2025-04-02;2025-04-28;5;9;909;Новый
|
|
11
|
+
10;2025-04-03;2025-04-29;19;10;910;Новый
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
point_id;address
|
|
2
|
+
1;420151, г. Лесной, ул. Вишневая, 32
|
|
3
|
+
2;125061, г. Лесной, ул. Подгорная, 8
|
|
4
|
+
3;630370, г. Лесной, ул. Шоссейная, 24
|
|
5
|
+
4;400562, г. Лесной, ул. Зеленая, 32
|
|
6
|
+
5;614510, г. Лесной, ул. Маяковского, 47
|
|
7
|
+
6;410542, г. Лесной, ул. Светлая, 46
|
|
8
|
+
7;620839, г. Лесной, ул. Цветочная, 8
|
|
9
|
+
8;443890, г. Лесной, ул. Коммунистическая, 1
|
|
10
|
+
9;603379, г. Лесной, ул. Спортивная, 46
|
|
11
|
+
10;603721, г. Лесной, ул. Гоголя, 41
|
|
12
|
+
11;410172, г. Лесной, ул. Северная, 13
|
|
13
|
+
12;614611, г. Лесной, ул. Молодежная, 50
|
|
14
|
+
13;454311, г.Лесной, ул. Новая, 19
|
|
15
|
+
14;660007, г.Лесной, ул. Октябрьская, 19
|
|
16
|
+
15;603036, г. Лесной, ул. Садовая, 4
|
|
17
|
+
16;394060, г.Лесной, ул. Фрунзе, 43
|
|
18
|
+
17;410661, г. Лесной, ул. Школьная, 50
|
|
19
|
+
18;625590, г. Лесной, ул. Коммунистическая, 20
|
|
20
|
+
19;625683, г. Лесной, ул. 8 Марта
|
|
21
|
+
20;450983, г.Лесной, ул. Комсомольская, 26
|
|
22
|
+
21;394782, г. Лесной, ул. Чехова, 3
|
|
23
|
+
22;603002, г. Лесной, ул. Дзержинского, 28
|
|
24
|
+
23;450558, г. Лесной, ул. Набережная, 30
|
|
25
|
+
24;344288, г. Лесной, ул. Чехова, 1
|
|
26
|
+
25;614164, г.Лесной, ул. Степная, 30
|
|
27
|
+
26;394242, г. Лесной, ул. Коммунистическая, 43
|
|
28
|
+
27;660540, г. Лесной, ул. Солнечная, 25
|
|
29
|
+
28;125837, г. Лесной, ул. Шоссейная, 40
|
|
30
|
+
29;125703, г. Лесной, ул. Партизанская, 49
|
|
31
|
+
30;625283, г. Лесной, ул. Победы, 46
|
|
32
|
+
31;614753, г. Лесной, ул. Полевая, 35
|
|
33
|
+
32;426030, г. Лесной, ул. Маяковского, 44
|
|
34
|
+
33;450375, г. Лесной ул. Клубная, 44
|
|
35
|
+
34;625560, г. Лесной, ул. Некрасова, 12
|
|
36
|
+
35;630201, г. Лесной, ул. Комсомольская, 17
|
|
37
|
+
36;190949, г. Лесной, ул. Мичурина, 26
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
sku;product_name;unit_of_measure;price;supplier;manufacturer;category;discount;stock_quantity;description;photo
|
|
2
|
+
PMEZMH;Детский игровой набор машинок Щенячий патруль / Dogs mini . 9 героев + 9 инерфионных машинок;шт.;1414;Pikeshop;ABSпластик;Игровой набор;22;50;Детский набор машинок с героями мультсериала «Щенячий патруль» подойдет как для мальчиков, так и для девочек. В детский набор входит 9 фигурок щенков спасателей. ;1.jpg
|
|
3
|
+
BPV4MM;Конструктор Гарри Поттер Сова Букля 630 деталей совместим с lego harry potter, лего совместимый);шт.;771;Playbig;ABSпластик;Конструктор;15;26;Коллекционная модель Букля состоит из множества потрясающих элементов, а также специального механизма внутри. С его помощью можно плавно поднимать-опускать крылья птицы.;2.jpg
|
|
4
|
+
JVL42J;Музыкальные инструменты для детей, ксилофон, барабаны, развивающие игрушки, игрушки для детей;шт.;2750;Playbig;BambiniFelici;Детский музыкальный инструмент;15;0;Откройте мир музыки для вашего ребенка с этой уникальной игрушкой! Это многофункциональное музыкальное чудо объединяет в себе всё, что нужно для творческого развития.;3.jpg
|
|
5
|
+
F895RB;Машинка игрушка диско шар светящаяся музыкальная;шт.;368;Knauf;ABSпластик;Машинка;6;7;Светящаяся музыкальная машина с диско шаром переливается разными цветами, играет ритмичные мелодии, объезжает препятствия и крутится, поэтому с ней точно не будет скучно.;4.jpg
|
|
6
|
+
3XBOTN;Игровой набор Hot Wheels Action Loop Cyclone Challenge Track, с машинкой и удобным хранением, HTK16;шт.;3426;Knauf;BambiniFelici;Игровой набор;10;21;Игровой набор Hot Wheels Action Loop Cyclone Challenge Track - это уникальная игра, которая позволит вам испытать себя и своих друзей в скорости и ловкости. Этот набор состоит из металлической дорожки с циклоном, которая создает потрясающий эффект и добавляет дополнительную сложность в игру.;5.jpg
|
|
7
|
+
3L7RCZ;Игровой набор с деревянными машинками Стройплощадка Кран-Паркс, Junion;шт.;7400;Knauf;Junion;Игровой набор;15;0;Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.;6.jpg
|
|
8
|
+
S72AM3;Синтезатор детский с микрофоном 61 клавиша;шт.;1749;CHILITOY;Junion;Детский музыкальный инструмент;10;35;Откройте для ребенка дверь в мир музыки с детским синтезатором! Этот компактный инструмент с микрофоном станет верным другом для юных музыкантов, помогая им развивать творческий потенциал и получать удовольствие от игры.;7.jpg
|
|
9
|
+
2G3280;"Деревянный игровой набор JUNION Стройплощадка ""Кран-Паркс"" с подъёмным, строительным краном и машинками, 18 предметов, подвижные элементы";шт.;1624;Vinylon;Junion;Игровой набор;9;20;Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.;8.jpg
|
|
10
|
+
MIO8YV;Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей;шт.;305;Vinylon;BambiniFelici;Детский музыкальный инструмент;9;31;Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей;9.jpg
|
|
11
|
+
UER2QD;Большой набор опытов и экспериментов для детей 14 в 1;шт.;2506;Vinylon;BambiniFelici;Игровой набор;8;27;Большой набор опытов и экспериментов для детей 14 в 1;10.jpg
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
user_id;fio;login;password;role_id
|
|
2
|
+
1;Ворсин Петр Евгеньевич;94d5ous@gmail.com;uzWC67;1
|
|
3
|
+
2;Старикова Елена Павловна;uth4iz@mail.com;2L6KZG;1
|
|
4
|
+
3;Одинцов Серафим Артёмович;yzls62@outlook.com;JlFRCZ;1
|
|
5
|
+
4;Михайлюк Анна Вячеславовна;1diph5e@tutanota.com;8ntwUp;2
|
|
6
|
+
5;Ситдикова Елена Анатольевна;tjde7c@yahoo.com;YOyhfR;2
|
|
7
|
+
6;Никифорова Весения Николаевна;wpmrc3do@tutanota.com;RSbvHv;2
|
|
8
|
+
7;Степанов Михаил Артёмович;5d4zbu@tutanota.com;rwVDh9;3
|
|
9
|
+
8;Ворсин Петр Евгеньевич;ptec8ym@yahoo.com;LdNyos;3
|
|
10
|
+
9;Старикова Елена Павловна;1qz4kw@mail.com;gynQMT;3
|
|
11
|
+
10;Сазонов Руслан Германович;4np6se@mail.com;AtnDjr;3
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Generated by Django 6.0.6 on 2026-06-16 15:27
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
initial = True
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.CreateModel(
|
|
17
|
+
name='Users',
|
|
18
|
+
fields=[
|
|
19
|
+
('user_id', models.AutoField(primary_key=True, serialize=False)),
|
|
20
|
+
('fio', models.CharField(max_length=150, verbose_name='ФИО')),
|
|
21
|
+
('login', models.CharField(max_length=100, unique=True, verbose_name='Логин')),
|
|
22
|
+
('password', models.CharField(max_length=100, verbose_name='Пароль')),
|
|
23
|
+
],
|
|
24
|
+
options={
|
|
25
|
+
'verbose_name': 'Пользователь',
|
|
26
|
+
'verbose_name_plural': 'Пользователи',
|
|
27
|
+
'db_table': 'users',
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
migrations.CreateModel(
|
|
31
|
+
name='PickupPoints',
|
|
32
|
+
fields=[
|
|
33
|
+
('point_id', models.IntegerField(primary_key=True, serialize=False)),
|
|
34
|
+
('address', models.TextField(verbose_name='Адрес')),
|
|
35
|
+
],
|
|
36
|
+
options={
|
|
37
|
+
'verbose_name': 'Пункт выдачи',
|
|
38
|
+
'verbose_name_plural': 'Пункты выдачи',
|
|
39
|
+
'db_table': 'pickup_points',
|
|
40
|
+
},
|
|
41
|
+
),
|
|
42
|
+
migrations.CreateModel(
|
|
43
|
+
name='Products',
|
|
44
|
+
fields=[
|
|
45
|
+
('sku', models.CharField(max_length=50, primary_key=True, serialize=False, verbose_name='Артикул')),
|
|
46
|
+
('product_name', models.TextField(verbose_name='Наименование')),
|
|
47
|
+
('unit_of_measure', models.CharField(max_length=20, verbose_name='Ед. изм.')),
|
|
48
|
+
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Цена')),
|
|
49
|
+
('supplier', models.CharField(max_length=100, verbose_name='Поставщик')),
|
|
50
|
+
('manufacturer', models.CharField(max_length=100, verbose_name='Производитель')),
|
|
51
|
+
('category', models.CharField(max_length=100, verbose_name='Категория')),
|
|
52
|
+
('discount', models.IntegerField(default=0, verbose_name='Скидка')),
|
|
53
|
+
('stock_quantity', models.IntegerField(verbose_name='Остаток')),
|
|
54
|
+
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
|
55
|
+
('photo', models.CharField(blank=True, max_length=255, null=True, verbose_name='Фото')),
|
|
56
|
+
],
|
|
57
|
+
options={
|
|
58
|
+
'verbose_name': 'Товар',
|
|
59
|
+
'verbose_name_plural': 'Товары',
|
|
60
|
+
'db_table': 'products',
|
|
61
|
+
},
|
|
62
|
+
),
|
|
63
|
+
migrations.CreateModel(
|
|
64
|
+
name='Roles',
|
|
65
|
+
fields=[
|
|
66
|
+
('role_id', models.AutoField(primary_key=True, serialize=False)),
|
|
67
|
+
('role_name', models.CharField(max_length=50, unique=True, verbose_name='Название роли')),
|
|
68
|
+
],
|
|
69
|
+
options={
|
|
70
|
+
'verbose_name': 'Роль',
|
|
71
|
+
'verbose_name_plural': 'Роли',
|
|
72
|
+
'db_table': 'roles',
|
|
73
|
+
},
|
|
74
|
+
),
|
|
75
|
+
migrations.CreateModel(
|
|
76
|
+
name='Orders',
|
|
77
|
+
fields=[
|
|
78
|
+
('order_id', models.IntegerField(primary_key=True, serialize=False, verbose_name='№ заказа')),
|
|
79
|
+
('order_date', models.DateField(verbose_name='Дата заказа')),
|
|
80
|
+
('delivery_date', models.DateField(verbose_name='Дата доставки')),
|
|
81
|
+
('pickup_code', models.IntegerField(verbose_name='Код получения')),
|
|
82
|
+
('order_status', models.CharField(max_length=50, verbose_name='Статус')),
|
|
83
|
+
('client', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to=settings.AUTH_USER_MODEL, verbose_name='Клиент')),
|
|
84
|
+
('pickup_point', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='core.pickuppoints', verbose_name='Пункт выдачи')),
|
|
85
|
+
],
|
|
86
|
+
options={
|
|
87
|
+
'verbose_name': 'Заказ',
|
|
88
|
+
'verbose_name_plural': 'Заказы',
|
|
89
|
+
'db_table': 'orders',
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
migrations.AddField(
|
|
93
|
+
model_name='users',
|
|
94
|
+
name='role',
|
|
95
|
+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='core.roles', verbose_name='Роль'),
|
|
96
|
+
),
|
|
97
|
+
migrations.CreateModel(
|
|
98
|
+
name='OrderItems',
|
|
99
|
+
fields=[
|
|
100
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
101
|
+
('quantity', models.IntegerField(verbose_name='Количество')),
|
|
102
|
+
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.orders', verbose_name='Заказ')),
|
|
103
|
+
('sku', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='core.products', verbose_name='Товар')),
|
|
104
|
+
],
|
|
105
|
+
options={
|
|
106
|
+
'verbose_name': 'Составной элемент',
|
|
107
|
+
'verbose_name_plural': 'Состав заказа',
|
|
108
|
+
'db_table': 'order_items',
|
|
109
|
+
'unique_together': {('order', 'sku')},
|
|
110
|
+
},
|
|
111
|
+
),
|
|
112
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
# Create your models here.
|
|
4
|
+
from django.db import models
|
|
5
|
+
from django.contrib.auth.models import AbstractBaseUser
|
|
6
|
+
|
|
7
|
+
class Roles(models.Model):
|
|
8
|
+
role_id = models.AutoField(primary_key=True)
|
|
9
|
+
role_name = models.CharField(unique=True, max_length=50, verbose_name="Название роли")
|
|
10
|
+
|
|
11
|
+
def __str__(self): return self.role_name
|
|
12
|
+
class Meta:
|
|
13
|
+
db_table = 'roles'
|
|
14
|
+
verbose_name = "Роль"
|
|
15
|
+
verbose_name_plural = "Роли"
|
|
16
|
+
|
|
17
|
+
class Users(AbstractBaseUser):
|
|
18
|
+
user_id = models.AutoField(primary_key=True)
|
|
19
|
+
fio = models.CharField(max_length=150, verbose_name="ФИО")
|
|
20
|
+
login = models.CharField(unique=True, max_length=100, verbose_name="Логин")
|
|
21
|
+
password = models.CharField(max_length=100, verbose_name="Пароль")
|
|
22
|
+
role = models.ForeignKey(Roles, models.RESTRICT, blank=True, null=True, verbose_name="Роль")
|
|
23
|
+
|
|
24
|
+
USERNAME_FIELD = 'login'
|
|
25
|
+
REQUIRED_FIELDS = ['fio']
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def is_staff(self):
|
|
29
|
+
return self.role_id in [1, 2]
|
|
30
|
+
@property
|
|
31
|
+
def is_superuser(self):
|
|
32
|
+
return self.role_id == 1
|
|
33
|
+
def has_perm(self, perm, obj=None): return True
|
|
34
|
+
def has_module_perms(self, app_label): return True
|
|
35
|
+
@property
|
|
36
|
+
def last_login(self): return None
|
|
37
|
+
@last_login.setter
|
|
38
|
+
def last_login(self, value): pass
|
|
39
|
+
|
|
40
|
+
class Meta:
|
|
41
|
+
db_table = 'users'
|
|
42
|
+
verbose_name = "Пользователь"
|
|
43
|
+
verbose_name_plural = "Пользователи"
|
|
44
|
+
|
|
45
|
+
class PickupPoints(models.Model):
|
|
46
|
+
point_id = models.IntegerField(primary_key=True)
|
|
47
|
+
address = models.TextField(verbose_name="Адрес")
|
|
48
|
+
|
|
49
|
+
def __str__(self): return self.address
|
|
50
|
+
class Meta:
|
|
51
|
+
db_table = 'pickup_points'
|
|
52
|
+
verbose_name = "Пункт выдачи"
|
|
53
|
+
verbose_name_plural = "Пункты выдачи"
|
|
54
|
+
|
|
55
|
+
class Products(models.Model):
|
|
56
|
+
sku = models.CharField(primary_key=True, max_length=50, verbose_name="Артикул")
|
|
57
|
+
product_name = models.TextField(verbose_name="Наименование")
|
|
58
|
+
unit_of_measure = models.CharField(max_length=20, verbose_name="Ед. изм.")
|
|
59
|
+
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Цена")
|
|
60
|
+
supplier = models.CharField(max_length=100, verbose_name="Поставщик")
|
|
61
|
+
manufacturer = models.CharField(max_length=100, verbose_name="Производитель")
|
|
62
|
+
category = models.CharField(max_length=100, verbose_name="Категория")
|
|
63
|
+
discount = models.IntegerField(default=0, verbose_name="Скидка")
|
|
64
|
+
stock_quantity = models.IntegerField(verbose_name="Остаток")
|
|
65
|
+
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
|
66
|
+
photo = models.CharField(max_length=255, blank=True, null=True, verbose_name="Фото")
|
|
67
|
+
|
|
68
|
+
def __str__(self): return self.product_name
|
|
69
|
+
class Meta:
|
|
70
|
+
db_table = 'products'
|
|
71
|
+
verbose_name = "Товар"
|
|
72
|
+
verbose_name_plural = "Товары"
|
|
73
|
+
|
|
74
|
+
class Orders(models.Model):
|
|
75
|
+
order_id = models.IntegerField(primary_key=True, verbose_name="№ заказа")
|
|
76
|
+
order_date = models.DateField(verbose_name="Дата заказа")
|
|
77
|
+
delivery_date = models.DateField(verbose_name="Дата доставки")
|
|
78
|
+
pickup_point = models.ForeignKey(PickupPoints, models.RESTRICT, verbose_name="Пункт выдачи")
|
|
79
|
+
client = models.ForeignKey(Users, models.RESTRICT, verbose_name="Клиент")
|
|
80
|
+
pickup_code = models.IntegerField(verbose_name="Код получения")
|
|
81
|
+
order_status = models.CharField(max_length=50, verbose_name="Статус")
|
|
82
|
+
|
|
83
|
+
class Meta:
|
|
84
|
+
db_table = 'orders'
|
|
85
|
+
verbose_name = "Заказ"
|
|
86
|
+
verbose_name_plural = "Заказы"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class OrderItems(models.Model):
|
|
90
|
+
# Django автоматически свяжет это поле с новой колонкой id в PostgreSQL
|
|
91
|
+
id = models.AutoField(primary_key=True)
|
|
92
|
+
|
|
93
|
+
order = models.ForeignKey(
|
|
94
|
+
Orders,
|
|
95
|
+
models.CASCADE,
|
|
96
|
+
db_column='order_id',
|
|
97
|
+
verbose_name="Заказ"
|
|
98
|
+
)
|
|
99
|
+
sku = models.ForeignKey(
|
|
100
|
+
Products,
|
|
101
|
+
models.RESTRICT,
|
|
102
|
+
db_column='sku',
|
|
103
|
+
verbose_name="Товар"
|
|
104
|
+
)
|
|
105
|
+
quantity = models.IntegerField(verbose_name="Количество")
|
|
106
|
+
|
|
107
|
+
def __str__(self):
|
|
108
|
+
return f"{self.sku.product_name} ({self.quantity} шт.)"
|
|
109
|
+
|
|
110
|
+
class Meta:
|
|
111
|
+
db_table = 'order_items'
|
|
112
|
+
unique_together = (('order', 'sku'),)
|
|
113
|
+
verbose_name = "Товар в заказе"
|
|
114
|
+
verbose_name_plural = "Состав заказа"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
body, input, select, textarea, button { font-family: 'Arial', sans-serif !important; }
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--primary: #DEB887; /* Цвет акцентов */
|
|
5
|
+
--secondary: #F5DEB3; /* Дополнительный фон */
|
|
6
|
+
--accent: #DEB887;
|
|
7
|
+
--body-bg: #FFFFFF; /* Основной фон — белый */
|
|
8
|
+
--header-bg: #F5DEB3 !important;
|
|
9
|
+
--header-color: #000000 !important;
|
|
10
|
+
--header-link-color: #000000 !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Кнопки целевого действия (Акцент) */
|
|
14
|
+
.button, input[type=submit], .submit-row input {
|
|
15
|
+
background: #DEB887 !important;
|
|
16
|
+
border-color: #DEB887 !important;
|
|
17
|
+
color: #000000 !important;
|
|
18
|
+
font-weight: bold;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Окно авторизации */
|
|
22
|
+
body.login { background: #F5DEB3 !important; }
|
|
23
|
+
body.login #container {
|
|
24
|
+
background: #FFFFFF !important;
|
|
25
|
+
border: 1px solid #DEB887 !important;
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Покраска строк при скидке > 17% */
|
|
30
|
+
tr.high-discount { background-color: #FFDEAD !important; }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
2
|
+
document.querySelectorAll("#result_list tbody tr").forEach(row => {
|
|
3
|
+
const discountCell = row.querySelector(".field-discount");
|
|
4
|
+
if (discountCell) {
|
|
5
|
+
const discountValue = parseInt(discountCell.textContent.replace('%', '').trim());
|
|
6
|
+
if (!isNaN(discountValue) && discountValue > 17) {
|
|
7
|
+
row.classList.add("high-discount");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{% extends "admin/base.html" %}
|
|
2
|
+
{% load static %}
|
|
3
|
+
|
|
4
|
+
{% block extrastyle %}
|
|
5
|
+
<link rel="stylesheet" href="{% static 'core/css/custom_admin.css' %}">
|
|
6
|
+
{% endblock %}
|
|
7
|
+
|
|
8
|
+
{% block branding %}
|
|
9
|
+
<h1 id="site-name"><a href="{% url 'admin:index' %}">ООО «МирИгрушек»</a></h1>
|
|
10
|
+
{% endblock %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% extends "admin/login.html" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
{{ block.super }}
|
|
5
|
+
<div style="margin-top: 15px;">
|
|
6
|
+
<a href="/catalog/" style="background: #F5DEB3; display: block; text-align: center; padding: 10px; color: #000; font-weight: bold; text-decoration: none; border-radius: 4px; border: 1px solid #DEB887;">ВОЙТИ КАК ГОСТЬ</a>
|
|
7
|
+
</div>
|
|
8
|
+
{% endblock %}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
main.py
|
|
4
|
+
manage.py
|
|
5
|
+
pyproject.toml
|
|
6
|
+
core/__init__.py
|
|
7
|
+
core/admin.py
|
|
8
|
+
core/apps.py
|
|
9
|
+
core/backends.py
|
|
10
|
+
core/models.py
|
|
11
|
+
core/tests.py
|
|
12
|
+
core/views.py
|
|
13
|
+
core/csv_import/SQL.txt
|
|
14
|
+
core/csv_import/backup.txt
|
|
15
|
+
core/csv_import/order_items.csv
|
|
16
|
+
core/csv_import/orders.csv
|
|
17
|
+
core/csv_import/pickup_points.csv
|
|
18
|
+
core/csv_import/positions.csv
|
|
19
|
+
core/csv_import/products.csv
|
|
20
|
+
core/csv_import/roles.csv
|
|
21
|
+
core/csv_import/users.csv
|
|
22
|
+
core/migrations/0001_initial.py
|
|
23
|
+
core/migrations/__init__.py
|
|
24
|
+
core/static/core/css/custom_admin.css
|
|
25
|
+
core/static/core/js/admin_discount.js
|
|
26
|
+
core/templates/admin/base_site.html
|
|
27
|
+
core/templates/admin/login.html
|
|
28
|
+
diehard1207.egg-info/PKG-INFO
|
|
29
|
+
diehard1207.egg-info/SOURCES.txt
|
|
30
|
+
diehard1207.egg-info/dependency_links.txt
|
|
31
|
+
diehard1207.egg-info/requires.txt
|
|
32
|
+
diehard1207.egg-info/top_level.txt
|
|
33
|
+
toy_shop_project/__init__.py
|
|
34
|
+
toy_shop_project/asgi.py
|
|
35
|
+
toy_shop_project/settings.py
|
|
36
|
+
toy_shop_project/urls.py
|
|
37
|
+
toy_shop_project/wsgi.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This is a sample Python script.
|
|
2
|
+
|
|
3
|
+
# Press Shift+F10 to execute it or replace it with your code.
|
|
4
|
+
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def print_hi(name):
|
|
8
|
+
# Use a breakpoint in the code line below to debug your script.
|
|
9
|
+
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Press the green button in the gutter to run the script.
|
|
13
|
+
if __name__ == '__main__':
|
|
14
|
+
print_hi('PyCharm')
|
|
15
|
+
|
|
16
|
+
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Django's command-line utility for administrative tasks."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
"""Run administrative tasks."""
|
|
9
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'toy_shop_project.settings')
|
|
10
|
+
try:
|
|
11
|
+
from django.core.management import execute_from_command_line
|
|
12
|
+
except ImportError as exc:
|
|
13
|
+
raise ImportError(
|
|
14
|
+
"Couldn't import Django. Are you sure it's installed and "
|
|
15
|
+
"available on your PYTHONPATH environment variable? Did you "
|
|
16
|
+
"forget to activate a virtual environment?"
|
|
17
|
+
) from exc
|
|
18
|
+
execute_from_command_line(sys.argv)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == '__main__':
|
|
22
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "diehard1207" # Имя пакета на PyPI
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Полный слепок проекта МирИгрушек для развертывания"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"Django>=5.0",
|
|
13
|
+
"psycopg2-binary>=2.9",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
# Указываем, какие папки положить в корень архива
|
|
18
|
+
packages = ["core", "toy_shop_project"]
|
|
19
|
+
# Указываем, какие отдельные файлы положить в корень архива
|
|
20
|
+
py-modules = ["manage", "main"]
|
|
21
|
+
include-package-data = true
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ASGI config for toy_shop_project project.
|
|
3
|
+
|
|
4
|
+
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
5
|
+
|
|
6
|
+
For more information on this file, see
|
|
7
|
+
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
from django.core.asgi import get_asgi_application
|
|
13
|
+
|
|
14
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'toy_shop_project.settings')
|
|
15
|
+
|
|
16
|
+
application = get_asgi_application()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django settings for toy_shop_project project.
|
|
3
|
+
|
|
4
|
+
Generated by 'django-admin startproject' using Django 6.0.6.
|
|
5
|
+
|
|
6
|
+
For more information on this file, see
|
|
7
|
+
https://docs.djangoproject.com/en/6.0/topics/settings/
|
|
8
|
+
|
|
9
|
+
For the full list of settings and their values, see
|
|
10
|
+
https://docs.djangoproject.com/en/6.0/ref/settings/
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
16
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Quick-start development settings - unsuitable for production
|
|
20
|
+
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
|
21
|
+
|
|
22
|
+
# SECURITY WARNING: keep the secret key used in production secret!
|
|
23
|
+
SECRET_KEY = 'django-insecure-gju!wxir==3vn1h%k%fs*uel-ty)0&j*f8%wtu^&)r+g6kxla0'
|
|
24
|
+
|
|
25
|
+
# SECURITY WARNING: don't run with debug turned on in production!
|
|
26
|
+
DEBUG = True
|
|
27
|
+
|
|
28
|
+
ALLOWED_HOSTS = []
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Application definition
|
|
32
|
+
|
|
33
|
+
INSTALLED_APPS = [
|
|
34
|
+
'django.contrib.admin',
|
|
35
|
+
'django.contrib.auth',
|
|
36
|
+
'django.contrib.contenttypes',
|
|
37
|
+
'django.contrib.sessions',
|
|
38
|
+
'django.contrib.messages',
|
|
39
|
+
'django.contrib.staticfiles',
|
|
40
|
+
'core',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
MIDDLEWARE = [
|
|
44
|
+
'django.middleware.security.SecurityMiddleware',
|
|
45
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
46
|
+
'django.middleware.common.CommonMiddleware',
|
|
47
|
+
'django.middleware.csrf.CsrfViewMiddleware',
|
|
48
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
49
|
+
'django.contrib.messages.middleware.MessageMiddleware',
|
|
50
|
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
ROOT_URLCONF = 'toy_shop_project.urls'
|
|
54
|
+
import os
|
|
55
|
+
TEMPLATES = [
|
|
56
|
+
{
|
|
57
|
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
58
|
+
|
|
59
|
+
'DIRS': [os.path.join(BASE_DIR, 'core', 'templates')],
|
|
60
|
+
'APP_DIRS': True,
|
|
61
|
+
'OPTIONS': {
|
|
62
|
+
'context_processors': [
|
|
63
|
+
'django.template.context_processors.request',
|
|
64
|
+
'django.contrib.auth.context_processors.auth',
|
|
65
|
+
'django.contrib.messages.context_processors.messages',
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
WSGI_APPLICATION = 'toy_shop_project.wsgi.application'
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Database
|
|
75
|
+
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
|
76
|
+
|
|
77
|
+
DATABASES = {
|
|
78
|
+
'default': {
|
|
79
|
+
'ENGINE': 'django.db.backends.postgresql',
|
|
80
|
+
'NAME': '381KashutinDE',
|
|
81
|
+
'USER': 'postgres',
|
|
82
|
+
'PASSWORD': '12072005',
|
|
83
|
+
'HOST': 'localhost',
|
|
84
|
+
'PORT': '5432',
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Password validation
|
|
90
|
+
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
|
91
|
+
|
|
92
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
93
|
+
{
|
|
94
|
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Internationalization
|
|
109
|
+
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
|
110
|
+
|
|
111
|
+
LANGUAGE_CODE = 'ru-ru'
|
|
112
|
+
|
|
113
|
+
TIME_ZONE = 'UTC'
|
|
114
|
+
|
|
115
|
+
USE_I18N = True
|
|
116
|
+
|
|
117
|
+
USE_TZ = True
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Static files (CSS, JavaScript, Images)
|
|
121
|
+
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
|
122
|
+
|
|
123
|
+
STATIC_URL = 'static/'
|
|
124
|
+
MEDIA_URL = '/media/'
|
|
125
|
+
MEDIA_ROOT = r"D:\Загрузки\17 июня\17 июня\Прил_В4_КОД 09.02.07-2-2026-ПУ\Модуль 1\import"
|
|
126
|
+
|
|
127
|
+
AUTH_USER_MODEL = 'core.Users'
|
|
128
|
+
AUTHENTICATION_BACKENDS = [
|
|
129
|
+
'core.backends.ToyShopAuthBackend',
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URL configuration for toy_shop_project project.
|
|
3
|
+
|
|
4
|
+
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
5
|
+
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
|
6
|
+
Examples:
|
|
7
|
+
Function views
|
|
8
|
+
1. Add an import: from my_app import views
|
|
9
|
+
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
10
|
+
Class-based views
|
|
11
|
+
1. Add an import: from other_app.views import Home
|
|
12
|
+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
13
|
+
Including another URLconf
|
|
14
|
+
1. Import the include() function: from django.urls import include, path
|
|
15
|
+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
16
|
+
"""
|
|
17
|
+
from django.contrib import admin
|
|
18
|
+
from django.urls import path
|
|
19
|
+
from django.conf import settings
|
|
20
|
+
from django.conf.urls.static import static
|
|
21
|
+
from django.http import HttpResponse
|
|
22
|
+
from core.models import Products
|
|
23
|
+
|
|
24
|
+
# Простейшее гостевое представление прямо в URL-файле (для демонстрации)
|
|
25
|
+
def guest_catalog(request):
|
|
26
|
+
items = Products.objects.all()
|
|
27
|
+
html = f"<h1 style='font-family: Arial; background: #F5DEB3; padding: 20px; margin: 0;'>Каталог товаров (Режим Гостя)</h1><div style='padding: 20px; font-family: Arial;'>"
|
|
28
|
+
for item in items:
|
|
29
|
+
html += f"<p style='border-bottom: 1px solid #DEB887; padding: 10px;'><b>{item.product_name}</b> — Производитель: {item.manufacturer} | Цена: {item.price} руб.</p>"
|
|
30
|
+
html += "</div><p style='padding: 20px;'><a href='/admin/login/' style='color: #DEB887; font-weight: bold;'>Вернуться к авторизации</a></p>"
|
|
31
|
+
return HttpResponse(html)
|
|
32
|
+
|
|
33
|
+
urlpatterns = [
|
|
34
|
+
path('admin/', admin.site.urls),
|
|
35
|
+
path('catalog/', guest_catalog),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
if settings.MEDIA_ROOT:
|
|
39
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WSGI config for toy_shop_project project.
|
|
3
|
+
|
|
4
|
+
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
5
|
+
|
|
6
|
+
For more information on this file, see
|
|
7
|
+
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
from django.core.wsgi import get_wsgi_application
|
|
13
|
+
|
|
14
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'toy_shop_project.settings')
|
|
15
|
+
|
|
16
|
+
application = get_wsgi_application()
|