flask-update 0.1.1__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.
- flask_update-0.1.1/MANIFEST.in +3 -0
- flask_update-0.1.1/PKG-INFO +41 -0
- flask_update-0.1.1/README.md +31 -0
- flask_update-0.1.1/flask_update/__init__.py +1 -0
- flask_update-0.1.1/flask_update/__main__.py +4 -0
- flask_update-0.1.1/flask_update/app.py +230 -0
- flask_update-0.1.1/flask_update/cli.py +57 -0
- flask_update-0.1.1/flask_update/database/full_database.sql +200 -0
- flask_update-0.1.1/flask_update/database.py +36 -0
- flask_update-0.1.1/flask_update/static/css/style.css +224 -0
- flask_update-0.1.1/flask_update/static/images/icon.ico +0 -0
- flask_update-0.1.1/flask_update/static/images/icon.png +0 -0
- flask_update-0.1.1/flask_update/static/images/logo.png +0 -0
- flask_update-0.1.1/flask_update/static/images/picture.png +0 -0
- flask_update-0.1.1/flask_update/static/images/products/1.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/10.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/2.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/3.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/4.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/5.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/6.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/7.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/8.jpg +0 -0
- flask_update-0.1.1/flask_update/static/images/products/9.jpg +0 -0
- flask_update-0.1.1/flask_update/static/js/app.js +10 -0
- flask_update-0.1.1/flask_update/templates/base.html +46 -0
- flask_update-0.1.1/flask_update/templates/error.html +9 -0
- flask_update-0.1.1/flask_update/templates/login.html +19 -0
- flask_update-0.1.1/flask_update/templates/order_form.html +64 -0
- flask_update-0.1.1/flask_update/templates/orders.html +55 -0
- flask_update-0.1.1/flask_update/templates/product_form.html +79 -0
- flask_update-0.1.1/flask_update/templates/products.html +90 -0
- flask_update-0.1.1/flask_update.egg-info/PKG-INFO +41 -0
- flask_update-0.1.1/flask_update.egg-info/SOURCES.txt +38 -0
- flask_update-0.1.1/flask_update.egg-info/dependency_links.txt +1 -0
- flask_update-0.1.1/flask_update.egg-info/entry_points.txt +3 -0
- flask_update-0.1.1/flask_update.egg-info/requires.txt +3 -0
- flask_update-0.1.1/flask_update.egg-info/top_level.txt +1 -0
- flask_update-0.1.1/pyproject.toml +36 -0
- flask_update-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flask_update
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Student Flask PostgreSQL project for toy store exam
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: Flask>=2.3
|
|
8
|
+
Requires-Dist: psycopg2-binary>=2.9
|
|
9
|
+
Requires-Dist: Pillow>=10.0
|
|
10
|
+
|
|
11
|
+
# flask_update
|
|
12
|
+
|
|
13
|
+
Учебная библиотека, которая по команде создает готовое решение Flask + PostgreSQL.
|
|
14
|
+
|
|
15
|
+
## Установка
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install flask_update
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Создание проекта
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
flask_update
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
или с именем папки:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
flask_update my_exam_project
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
После команды появится папка с готовым проектом.
|
|
34
|
+
|
|
35
|
+
## Запуск созданного проекта
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd my_exam_project
|
|
39
|
+
pip install -r requirements.txt
|
|
40
|
+
python app.py
|
|
41
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# flask_update
|
|
2
|
+
|
|
3
|
+
Учебная библиотека, которая по команде создает готовое решение Flask + PostgreSQL.
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install flask_update
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Создание проекта
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
flask_update
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
или с именем папки:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
flask_update my_exam_project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
После команды появится папка с готовым проектом.
|
|
24
|
+
|
|
25
|
+
## Запуск созданного проекта
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my_exam_project
|
|
29
|
+
pip install -r requirements.txt
|
|
30
|
+
python app.py
|
|
31
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.1.1'
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from flask import Flask, flash, redirect, render_template, request, session, url_for
|
|
6
|
+
from PIL import Image
|
|
7
|
+
from psycopg2 import IntegrityError
|
|
8
|
+
from .database import execute, fetch_all, fetch_one
|
|
9
|
+
|
|
10
|
+
app = Flask(__name__)
|
|
11
|
+
app.secret_key = os.getenv('SECRET_KEY', 'exam-key')
|
|
12
|
+
IMG_DIR = Path(app.root_path) / 'static' / 'images' / 'products'
|
|
13
|
+
IMG_DIR.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
ADMIN, MANAGER, CLIENT, GUEST = 'Администратор', 'Менеджер', 'Авторизированный клиент', 'Гость'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def role(): return session.get('role_name')
|
|
18
|
+
def is_admin(): return role() == ADMIN
|
|
19
|
+
def is_staff(): return role() in (ADMIN, MANAGER)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def need(*roles):
|
|
23
|
+
def dec(func):
|
|
24
|
+
@wraps(func)
|
|
25
|
+
def wrap(*a, **kw):
|
|
26
|
+
if not role():
|
|
27
|
+
flash('Сначала войдите в систему.', 'warning')
|
|
28
|
+
return redirect(url_for('login'))
|
|
29
|
+
if roles and role() not in roles:
|
|
30
|
+
flash('Недостаточно прав.', 'danger')
|
|
31
|
+
return redirect(url_for('products'))
|
|
32
|
+
return func(*a, **kw)
|
|
33
|
+
return wrap
|
|
34
|
+
return dec
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.context_processor
|
|
38
|
+
def menu_data():
|
|
39
|
+
return {'current_user_name': session.get('full_name', ''), 'current_user_role': role() or '',
|
|
40
|
+
'is_admin': is_admin(), 'is_manager_or_admin': is_staff()}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.route('/', methods=['GET', 'POST'])
|
|
44
|
+
def login():
|
|
45
|
+
if request.method == 'POST':
|
|
46
|
+
user = fetch_one('''SELECT u.user_id, u.full_name, u.password_hash, r.role_name
|
|
47
|
+
FROM users u JOIN roles r ON r.role_id=u.role_id WHERE u.login=%s''',
|
|
48
|
+
[request.form.get('login', '').strip()])
|
|
49
|
+
if user and user['password_hash'] == request.form.get('password', '').strip():
|
|
50
|
+
session.clear()
|
|
51
|
+
session.update(user_id=user['user_id'], full_name=user['full_name'], role_name=user['role_name'])
|
|
52
|
+
return redirect(url_for('products'))
|
|
53
|
+
flash('Неверный логин или пароль.', 'danger')
|
|
54
|
+
return render_template('login.html')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.route('/guest')
|
|
58
|
+
def guest_login():
|
|
59
|
+
session.clear(); session.update(full_name='Гость', role_name=GUEST)
|
|
60
|
+
return redirect(url_for('products'))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.route('/logout')
|
|
64
|
+
def logout():
|
|
65
|
+
session.clear()
|
|
66
|
+
return redirect(url_for('login'))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def product_refs():
|
|
70
|
+
return {'categories': fetch_all('SELECT category_id, category_name FROM categories ORDER BY category_name'),
|
|
71
|
+
'manufacturers': fetch_all('SELECT manufacturer_id, manufacturer_name FROM manufacturers ORDER BY manufacturer_name'),
|
|
72
|
+
'suppliers': fetch_all('SELECT supplier_id, supplier_name FROM suppliers ORDER BY supplier_name'),
|
|
73
|
+
'units': fetch_all('SELECT unit_id, unit_name FROM units ORDER BY unit_name')}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.route('/products')
|
|
77
|
+
@need()
|
|
78
|
+
def products():
|
|
79
|
+
q = request.args.get('q', '').strip() if is_staff() else ''
|
|
80
|
+
supplier = request.args.get('supplier_id', '').strip() if is_staff() else ''
|
|
81
|
+
sort = request.args.get('sort', '').strip() if is_staff() else ''
|
|
82
|
+
where, params = [], []
|
|
83
|
+
if q:
|
|
84
|
+
where.append('LOWER(p.article||p.product_name||p.description||c.category_name||m.manufacturer_name||s.supplier_name||u.unit_name) LIKE %s')
|
|
85
|
+
params.append('%' + q.lower() + '%')
|
|
86
|
+
if supplier.isdigit():
|
|
87
|
+
where.append('p.supplier_id=%s'); params.append(int(supplier))
|
|
88
|
+
order = {'stock_asc': 'p.stock_quantity', 'stock_desc': 'p.stock_quantity DESC',
|
|
89
|
+
'price_asc': 'p.price', 'price_desc': 'p.price DESC'}.get(sort, 'p.product_id')
|
|
90
|
+
rows = fetch_all(f'''SELECT p.*, c.category_name, m.manufacturer_name, s.supplier_name, u.unit_name,
|
|
91
|
+
ROUND((p.price*(100-p.discount)/100)::numeric, 2) AS final_price FROM products p
|
|
92
|
+
JOIN categories c ON c.category_id=p.category_id JOIN manufacturers m ON m.manufacturer_id=p.manufacturer_id
|
|
93
|
+
JOIN suppliers s ON s.supplier_id=p.supplier_id JOIN units u ON u.unit_id=p.unit_id
|
|
94
|
+
{'WHERE ' + ' AND '.join(where) if where else ''} ORDER BY {order}, p.product_id''', params)
|
|
95
|
+
return render_template('products.html', products=rows, suppliers=product_refs()['suppliers'],
|
|
96
|
+
q=q, supplier_id=supplier, sort=sort, allow_tools=is_staff())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def save_image(file, old=None):
|
|
100
|
+
if not file or not file.filename: return old
|
|
101
|
+
if Path(file.filename).suffix.lower() not in ('.jpg', '.jpeg', '.png'): raise ValueError('Нужно выбрать JPG или PNG.')
|
|
102
|
+
name = 'product_' + uuid4().hex + '.jpg'
|
|
103
|
+
img = Image.open(file.stream).convert('RGB'); img.thumbnail((300, 200)); img.save(IMG_DIR / name, 'JPEG', quality=90)
|
|
104
|
+
return 'images/products/' + name
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def product_values(form, image):
|
|
108
|
+
return [form['article'].strip(), form['product_name'].strip(), int(form['unit_id']), float(form['price'].replace(',', '.')),
|
|
109
|
+
int(form['supplier_id']), int(form['manufacturer_id']), int(form['category_id']), int(form['discount']),
|
|
110
|
+
int(form['stock_quantity']), form['description'].strip(), image]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def product_error(form):
|
|
114
|
+
fields = ['article', 'product_name', 'category_id', 'manufacturer_id', 'supplier_id', 'unit_id', 'price', 'stock_quantity', 'discount', 'description']
|
|
115
|
+
if any(not form.get(x, '').strip() for x in fields): return 'Заполните все обязательные поля.'
|
|
116
|
+
if float(form['price'].replace(',', '.')) < 0 or int(form['stock_quantity']) < 0: return 'Цена и количество не могут быть отрицательными.'
|
|
117
|
+
if not 0 <= int(form['discount']) <= 100: return 'Скидка должна быть от 0 до 100.'
|
|
118
|
+
return ''
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.route('/products/add', methods=['GET', 'POST'], defaults={'product_id': None}, endpoint='add_product')
|
|
122
|
+
@app.route('/products/<int:product_id>/edit', methods=['GET', 'POST'], endpoint='edit_product')
|
|
123
|
+
@need(ADMIN)
|
|
124
|
+
def product_form(product_id):
|
|
125
|
+
refs = product_refs()
|
|
126
|
+
product = fetch_one('SELECT * FROM products WHERE product_id=%s', [product_id]) if product_id else {}
|
|
127
|
+
if product_id and not product:
|
|
128
|
+
flash('Товар не найден.', 'danger')
|
|
129
|
+
return redirect(url_for('products'))
|
|
130
|
+
if request.method == 'POST':
|
|
131
|
+
try:
|
|
132
|
+
error = product_error(request.form)
|
|
133
|
+
if error: raise ValueError(error)
|
|
134
|
+
image = save_image(request.files.get('image'), product.get('image_path') if product else None)
|
|
135
|
+
vals = product_values(request.form, image)
|
|
136
|
+
if product_id:
|
|
137
|
+
execute('''UPDATE products SET article=%s, product_name=%s, unit_id=%s, price=%s, supplier_id=%s,
|
|
138
|
+
manufacturer_id=%s, category_id=%s, discount=%s, stock_quantity=%s, description=%s, image_path=%s
|
|
139
|
+
WHERE product_id=%s''', vals + [product_id])
|
|
140
|
+
flash('Товар изменен.', 'success')
|
|
141
|
+
else:
|
|
142
|
+
execute('''INSERT INTO products(article, product_name, unit_id, price, supplier_id, manufacturer_id,
|
|
143
|
+
category_id, discount, stock_quantity, description, image_path) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)''', vals)
|
|
144
|
+
flash('Товар добавлен.', 'success')
|
|
145
|
+
return redirect(url_for('products'))
|
|
146
|
+
except IntegrityError:
|
|
147
|
+
flash('Такой артикул уже есть.', 'danger')
|
|
148
|
+
except Exception as err:
|
|
149
|
+
flash(str(err), 'danger')
|
|
150
|
+
product = dict(product or {}); product.update(request.form)
|
|
151
|
+
return render_template('product_form.html', product=product, references=refs, mode='edit' if product_id else 'add')
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.route('/products/<int:product_id>/delete', methods=['POST'])
|
|
155
|
+
@need(ADMIN)
|
|
156
|
+
def delete_product(product_id):
|
|
157
|
+
busy = fetch_one('SELECT COUNT(*) AS c FROM order_items WHERE product_id=%s', [product_id])
|
|
158
|
+
if busy and busy['c']: flash('Товар есть в заказе, удалить нельзя.', 'danger')
|
|
159
|
+
else:
|
|
160
|
+
execute('DELETE FROM products WHERE product_id=%s', [product_id]); flash('Товар удален.', 'success')
|
|
161
|
+
return redirect(url_for('products'))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def order_refs():
|
|
165
|
+
return {'statuses': fetch_all('SELECT status_id, status_name FROM order_statuses ORDER BY status_name'),
|
|
166
|
+
'pickup_points': fetch_all('SELECT pickup_point_id, address FROM pickup_points ORDER BY pickup_point_id'),
|
|
167
|
+
'products': fetch_all('SELECT product_id, article, product_name FROM products ORDER BY article'),
|
|
168
|
+
'clients': fetch_all('''SELECT u.user_id, u.full_name FROM users u JOIN roles r ON r.role_id=u.role_id
|
|
169
|
+
WHERE r.role_name=%s ORDER BY u.full_name''', [CLIENT])}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.route('/orders')
|
|
173
|
+
@need(ADMIN, MANAGER)
|
|
174
|
+
def orders():
|
|
175
|
+
rows = fetch_all('''SELECT o.order_id, o.order_date, o.delivery_date, o.receive_code, os.status_name, pp.address,
|
|
176
|
+
COALESCE(u.full_name, 'Не указан') AS customer_name,
|
|
177
|
+
COALESCE(STRING_AGG(p.article || ' x' || oi.quantity, ', ' ORDER BY p.article), 'Нет') AS order_articles
|
|
178
|
+
FROM orders o JOIN order_statuses os ON os.status_id=o.status_id JOIN pickup_points pp ON pp.pickup_point_id=o.pickup_point_id
|
|
179
|
+
LEFT JOIN users u ON u.user_id=o.customer_id LEFT JOIN order_items oi ON oi.order_id=o.order_id LEFT JOIN products p ON p.product_id=oi.product_id
|
|
180
|
+
GROUP BY o.order_id, os.status_name, pp.address, u.full_name ORDER BY o.order_id''')
|
|
181
|
+
return render_template('orders.html', orders=rows)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def order_values(form):
|
|
185
|
+
return [form['status_id'], form['pickup_point_id'], form.get('customer_id', ''), form['order_date'], form.get('delivery_date', ''), form['receive_code'].strip()]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.route('/orders/add', methods=['GET', 'POST'], defaults={'order_id': None}, endpoint='add_order')
|
|
189
|
+
@app.route('/orders/<int:order_id>/edit', methods=['GET', 'POST'], endpoint='edit_order')
|
|
190
|
+
@need(ADMIN)
|
|
191
|
+
def order_form(order_id):
|
|
192
|
+
refs = order_refs()
|
|
193
|
+
order = fetch_one('''SELECT o.*, oi.product_id, oi.quantity FROM orders o LEFT JOIN order_items oi ON oi.order_id=o.order_id
|
|
194
|
+
WHERE o.order_id=%s LIMIT 1''', [order_id]) if order_id else {}
|
|
195
|
+
if order_id and not order:
|
|
196
|
+
flash('Заказ не найден.', 'danger')
|
|
197
|
+
return redirect(url_for('orders'))
|
|
198
|
+
if request.method == 'POST':
|
|
199
|
+
if not request.form.get('product_id') or int(request.form.get('quantity', 0)) <= 0: flash('Выберите товар и количество.', 'danger')
|
|
200
|
+
elif order_id:
|
|
201
|
+
execute('''UPDATE orders SET status_id=%s, pickup_point_id=%s, customer_id=NULLIF(%s,'')::integer,
|
|
202
|
+
order_date=%s, delivery_date=NULLIF(%s,'')::date, receive_code=%s WHERE order_id=%s''', order_values(request.form) + [order_id])
|
|
203
|
+
execute('DELETE FROM order_items WHERE order_id=%s', [order_id])
|
|
204
|
+
execute('INSERT INTO order_items(order_id, product_id, quantity) VALUES (%s,%s,%s)', [order_id, request.form['product_id'], request.form['quantity']])
|
|
205
|
+
flash('Заказ изменен.', 'success')
|
|
206
|
+
return redirect(url_for('orders'))
|
|
207
|
+
else:
|
|
208
|
+
new = execute('''INSERT INTO orders(status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code)
|
|
209
|
+
VALUES (%s,%s,NULLIF(%s,'')::integer,%s,NULLIF(%s,'')::date,%s) RETURNING order_id''', order_values(request.form), True)
|
|
210
|
+
execute('INSERT INTO order_items(order_id, product_id, quantity) VALUES (%s,%s,%s)', [new['order_id'], request.form['product_id'], request.form['quantity']])
|
|
211
|
+
flash('Заказ добавлен.', 'success')
|
|
212
|
+
return redirect(url_for('orders'))
|
|
213
|
+
order = dict(order or {}); order.update(request.form)
|
|
214
|
+
return render_template('order_form.html', order=order, refs=refs, mode='edit' if order_id else 'add')
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@app.route('/orders/<int:order_id>/delete', methods=['POST'])
|
|
218
|
+
@need(ADMIN)
|
|
219
|
+
def delete_order(order_id):
|
|
220
|
+
execute('DELETE FROM orders WHERE order_id=%s', [order_id])
|
|
221
|
+
flash('Заказ удален.', 'success')
|
|
222
|
+
return redirect(url_for('orders'))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def main():
|
|
226
|
+
app.run(debug=True)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == '__main__':
|
|
230
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
import importlib.resources as res
|
|
5
|
+
|
|
6
|
+
SKIP_DIRS = {'__pycache__', 'dist', '*.egg-info'}
|
|
7
|
+
SKIP_FILES = {'cli.py'}
|
|
8
|
+
|
|
9
|
+
def _skip(path: Path) -> bool:
|
|
10
|
+
parts = set(path.parts)
|
|
11
|
+
if '__pycache__' in parts or 'dist' in parts or any(p.endswith('.egg-info') for p in parts):
|
|
12
|
+
return True
|
|
13
|
+
return path.name in SKIP_FILES or path.suffix in {'.pyc', '.pyo'}
|
|
14
|
+
|
|
15
|
+
def create_project(target='MirIgrushek_Flask_PostgreSQL'):
|
|
16
|
+
target_path = Path(target).resolve()
|
|
17
|
+
if target_path.exists() and any(target_path.iterdir()):
|
|
18
|
+
print(f'Папка уже существует и не пустая: {target_path}')
|
|
19
|
+
print('Укажи другое имя: flask_update my_project')
|
|
20
|
+
return 1
|
|
21
|
+
|
|
22
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
src_root = Path(str(res.files('flask_update')))
|
|
24
|
+
|
|
25
|
+
for src in src_root.rglob('*'):
|
|
26
|
+
rel = src.relative_to(src_root)
|
|
27
|
+
if _skip(rel):
|
|
28
|
+
continue
|
|
29
|
+
dst = target_path / rel
|
|
30
|
+
if src.is_dir():
|
|
31
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
else:
|
|
33
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
shutil.copy2(src, dst)
|
|
35
|
+
|
|
36
|
+
readme = target_path / 'README.md'
|
|
37
|
+
if not readme.exists():
|
|
38
|
+
readme.write_text('''# Готовое решение Flask + PostgreSQL\n\nЗапуск:\n\n```bash\npy -m venv .venv\n.venv\\Scripts\\activate\npip install -r requirements.txt\npython init_db.py\npython app.py\n```\n''', encoding='utf-8')
|
|
39
|
+
|
|
40
|
+
req = target_path / 'requirements.txt'
|
|
41
|
+
if not req.exists():
|
|
42
|
+
req.write_text('Flask\npsycopg2-binary\nPillow\n', encoding='utf-8')
|
|
43
|
+
|
|
44
|
+
print('Готовое решение создано:')
|
|
45
|
+
print(target_path)
|
|
46
|
+
print('\nДальше:')
|
|
47
|
+
print(f'cd "{target_path}"')
|
|
48
|
+
print('pip install -r requirements.txt')
|
|
49
|
+
print('python app.py')
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
name = sys.argv[1] if len(sys.argv) > 1 else 'MirIgrushek_Flask_PostgreSQL'
|
|
54
|
+
raise SystemExit(create_project(name))
|
|
55
|
+
|
|
56
|
+
if __name__ == '__main__':
|
|
57
|
+
main()
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
CREATE TABLE roles (
|
|
2
|
+
role_id SERIAL PRIMARY KEY,
|
|
3
|
+
role_name VARCHAR(80) NOT NULL UNIQUE
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
CREATE TABLE users (
|
|
7
|
+
user_id SERIAL PRIMARY KEY,
|
|
8
|
+
role_id INTEGER NOT NULL REFERENCES roles(role_id) ON UPDATE CASCADE,
|
|
9
|
+
full_name VARCHAR(180) NOT NULL,
|
|
10
|
+
login VARCHAR(160) NOT NULL UNIQUE,
|
|
11
|
+
password_hash VARCHAR(255) NOT NULL
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE categories (
|
|
15
|
+
category_id SERIAL PRIMARY KEY,
|
|
16
|
+
category_name VARCHAR(160) NOT NULL UNIQUE
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE manufacturers (
|
|
20
|
+
manufacturer_id SERIAL PRIMARY KEY,
|
|
21
|
+
manufacturer_name VARCHAR(160) NOT NULL UNIQUE
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE suppliers (
|
|
25
|
+
supplier_id SERIAL PRIMARY KEY,
|
|
26
|
+
supplier_name VARCHAR(160) NOT NULL UNIQUE
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE units (
|
|
30
|
+
unit_id SERIAL PRIMARY KEY,
|
|
31
|
+
unit_name VARCHAR(40) NOT NULL UNIQUE
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE products (
|
|
35
|
+
product_id SERIAL PRIMARY KEY,
|
|
36
|
+
article VARCHAR(30) NOT NULL UNIQUE,
|
|
37
|
+
product_name TEXT NOT NULL,
|
|
38
|
+
unit_id INTEGER NOT NULL REFERENCES units(unit_id) ON UPDATE CASCADE,
|
|
39
|
+
price NUMERIC(10,2) NOT NULL CHECK (price >= 0),
|
|
40
|
+
supplier_id INTEGER NOT NULL REFERENCES suppliers(supplier_id) ON UPDATE CASCADE,
|
|
41
|
+
manufacturer_id INTEGER NOT NULL REFERENCES manufacturers(manufacturer_id) ON UPDATE CASCADE,
|
|
42
|
+
category_id INTEGER NOT NULL REFERENCES categories(category_id) ON UPDATE CASCADE,
|
|
43
|
+
discount INTEGER NOT NULL DEFAULT 0 CHECK (discount >= 0 AND discount <= 100),
|
|
44
|
+
stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
|
|
45
|
+
description TEXT NOT NULL,
|
|
46
|
+
image_path VARCHAR(255),
|
|
47
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE pickup_points (
|
|
51
|
+
pickup_point_id SERIAL PRIMARY KEY,
|
|
52
|
+
address TEXT NOT NULL UNIQUE
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE order_statuses (
|
|
56
|
+
status_id SERIAL PRIMARY KEY,
|
|
57
|
+
status_name VARCHAR(80) NOT NULL UNIQUE
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE orders (
|
|
61
|
+
order_id SERIAL PRIMARY KEY,
|
|
62
|
+
status_id INTEGER NOT NULL REFERENCES order_statuses(status_id) ON UPDATE CASCADE,
|
|
63
|
+
pickup_point_id INTEGER NOT NULL REFERENCES pickup_points(pickup_point_id) ON UPDATE CASCADE,
|
|
64
|
+
customer_id INTEGER REFERENCES users(user_id) ON UPDATE CASCADE,
|
|
65
|
+
order_date DATE NOT NULL,
|
|
66
|
+
delivery_date DATE,
|
|
67
|
+
receive_code VARCHAR(20) NOT NULL
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE order_items (
|
|
71
|
+
order_item_id SERIAL PRIMARY KEY,
|
|
72
|
+
order_id INTEGER NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
73
|
+
product_id INTEGER NOT NULL REFERENCES products(product_id) ON UPDATE CASCADE,
|
|
74
|
+
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
|
75
|
+
UNIQUE (order_id, product_id)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE INDEX idx_products_search ON products USING gin (to_tsvector('russian', product_name || ' ' || description || ' ' || article));
|
|
79
|
+
CREATE INDEX idx_products_supplier ON products(supplier_id);
|
|
80
|
+
CREATE INDEX idx_orders_status ON orders(status_id);
|
|
81
|
+
|
|
82
|
+
BEGIN;
|
|
83
|
+
INSERT INTO roles(role_id, role_name) VALUES (1, 'Администратор');
|
|
84
|
+
INSERT INTO roles(role_id, role_name) VALUES (2, 'Менеджер');
|
|
85
|
+
INSERT INTO roles(role_id, role_name) VALUES (3, 'Авторизированный клиент');
|
|
86
|
+
INSERT INTO categories(category_id, category_name) VALUES (1, 'Детский музыкальный инструмент');
|
|
87
|
+
INSERT INTO categories(category_id, category_name) VALUES (2, 'Игровой набор');
|
|
88
|
+
INSERT INTO categories(category_id, category_name) VALUES (3, 'Конструктор');
|
|
89
|
+
INSERT INTO categories(category_id, category_name) VALUES (4, 'Машинка');
|
|
90
|
+
INSERT INTO manufacturers(manufacturer_id, manufacturer_name) VALUES (1, 'ABSпластик');
|
|
91
|
+
INSERT INTO manufacturers(manufacturer_id, manufacturer_name) VALUES (2, 'BambiniFelici');
|
|
92
|
+
INSERT INTO manufacturers(manufacturer_id, manufacturer_name) VALUES (3, 'Junion');
|
|
93
|
+
INSERT INTO suppliers(supplier_id, supplier_name) VALUES (1, 'CHILITOY');
|
|
94
|
+
INSERT INTO suppliers(supplier_id, supplier_name) VALUES (2, 'Knauf');
|
|
95
|
+
INSERT INTO suppliers(supplier_id, supplier_name) VALUES (3, 'Pikeshop');
|
|
96
|
+
INSERT INTO suppliers(supplier_id, supplier_name) VALUES (4, 'Playbig');
|
|
97
|
+
INSERT INTO suppliers(supplier_id, supplier_name) VALUES (5, 'Vinylon');
|
|
98
|
+
INSERT INTO units(unit_id, unit_name) VALUES (1, 'шт.');
|
|
99
|
+
INSERT INTO order_statuses(status_id, status_name) VALUES (1, 'Завершен');
|
|
100
|
+
INSERT INTO order_statuses(status_id, status_name) VALUES (2, 'Новый');
|
|
101
|
+
INSERT INTO order_statuses(status_id, status_name) VALUES (3, 'Отменен');
|
|
102
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (1, '420151, г. Лесной, ул. Вишневая, 32');
|
|
103
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (2, '125061, г. Лесной, ул. Подгорная, 8');
|
|
104
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (3, '630370, г. Лесной, ул. Шоссейная, 24');
|
|
105
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (4, '400562, г. Лесной, ул. Зеленая, 32');
|
|
106
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (5, '614510, г. Лесной, ул. Маяковского, 47');
|
|
107
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (6, '410542, г. Лесной, ул. Светлая, 46');
|
|
108
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (7, '620839, г. Лесной, ул. Цветочная, 8');
|
|
109
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (8, '443890, г. Лесной, ул. Коммунистическая, 1');
|
|
110
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (9, '603379, г. Лесной, ул. Спортивная, 46');
|
|
111
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (10, '603721, г. Лесной, ул. Гоголя, 41');
|
|
112
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (11, '410172, г. Лесной, ул. Северная, 13');
|
|
113
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (12, '614611, г. Лесной, ул. Молодежная, 50');
|
|
114
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (13, '454311, г.Лесной, ул. Новая, 19');
|
|
115
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (14, '660007, г.Лесной, ул. Октябрьская, 19');
|
|
116
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (15, '603036, г. Лесной, ул. Садовая, 4');
|
|
117
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (16, '394060, г.Лесной, ул. Фрунзе, 43');
|
|
118
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (17, '410661, г. Лесной, ул. Школьная, 50');
|
|
119
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (18, '625590, г. Лесной, ул. Коммунистическая, 20');
|
|
120
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (19, '625683, г. Лесной, ул. 8 Марта');
|
|
121
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (20, '450983, г.Лесной, ул. Комсомольская, 26');
|
|
122
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (21, '394782, г. Лесной, ул. Чехова, 3');
|
|
123
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (22, '603002, г. Лесной, ул. Дзержинского, 28');
|
|
124
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (23, '450558, г. Лесной, ул. Набережная, 30');
|
|
125
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (24, '344288, г. Лесной, ул. Чехова, 1');
|
|
126
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (25, '614164, г.Лесной, ул. Степная, 30');
|
|
127
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (26, '394242, г. Лесной, ул. Коммунистическая, 43');
|
|
128
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (27, '660540, г. Лесной, ул. Солнечная, 25');
|
|
129
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (28, '125837, г. Лесной, ул. Шоссейная, 40');
|
|
130
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (29, '125703, г. Лесной, ул. Партизанская, 49');
|
|
131
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (30, '625283, г. Лесной, ул. Победы, 46');
|
|
132
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (31, '614753, г. Лесной, ул. Полевая, 35');
|
|
133
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (32, '426030, г. Лесной, ул. Маяковского, 44');
|
|
134
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (33, '450375, г. Лесной ул. Клубная, 44');
|
|
135
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (34, '625560, г. Лесной, ул. Некрасова, 12');
|
|
136
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (35, '630201, г. Лесной, ул. Комсомольская, 17');
|
|
137
|
+
INSERT INTO pickup_points(pickup_point_id, address) VALUES (36, '190949, г. Лесной, ул. Мичурина, 26');
|
|
138
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (1, 1, 'Ворсин Петр Евгеньевич', '94d5ous@gmail.com', 'uzWC67');
|
|
139
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (2, 1, 'Старикова Елена Павловна', 'uth4iz@mail.com', '2L6KZG');
|
|
140
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (3, 1, 'Одинцов Серафим Артёмович', 'yzls62@outlook.com', 'JlFRCZ');
|
|
141
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (4, 2, 'Михайлюк Анна Вячеславовна', '1diph5e@tutanota.com', '8ntwUp');
|
|
142
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (5, 2, 'Ситдикова Елена Анатольевна', 'tjde7c@yahoo.com', 'YOyhfR');
|
|
143
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (6, 2, 'Никифорова Весения Николаевна', 'wpmrc3do@tutanota.com', 'RSbvHv');
|
|
144
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (7, 3, 'Степанов Михаил Артёмович', '5d4zbu@tutanota.com', 'rwVDh9');
|
|
145
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (8, 3, 'Ворсин Петр Евгеньевич', 'ptec8ym@yahoo.com', 'LdNyos');
|
|
146
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (9, 3, 'Старикова Елена Павловна', '1qz4kw@mail.com', 'gynQMT');
|
|
147
|
+
INSERT INTO users(user_id, role_id, full_name, login, password_hash) VALUES (10, 3, 'Сазонов Руслан Германович', '4np6se@mail.com', 'AtnDjr');
|
|
148
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (1, 'PMEZMH', 'Детский игровой набор машинок Щенячий патруль / Dogs mini . 9 героев + 9 инерфионных машинок', 1, 1414.00, 3, 1, 2, 22, 50, 'Детский набор машинок с героями мультсериала «Щенячий патруль» подойдет как для мальчиков, так и для девочек. В детский набор входит 9 фигурок щенков спасателей.', 'images/products/1.jpg');
|
|
149
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (2, 'BPV4MM', 'Конструктор Гарри Поттер Сова Букля 630 деталей совместим с lego harry potter, лего совместимый)', 1, 771.00, 4, 1, 3, 15, 26, 'Коллекционная модель Букля состоит из множества потрясающих элементов, а также специального механизма внутри. С его помощью можно плавно поднимать-опускать крылья птицы.', 'images/products/2.jpg');
|
|
150
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (3, 'JVL42J', 'Музыкальные инструменты для детей, ксилофон, барабаны, развивающие игрушки, игрушки для детей', 1, 2750.00, 4, 2, 1, 15, 0, 'Откройте мир музыки для вашего ребенка с этой уникальной игрушкой! Это многофункциональное музыкальное чудо объединяет в себе всё, что нужно для творческого развития.', 'images/products/3.jpg');
|
|
151
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (4, 'F895RB', 'Машинка игрушка диско шар светящаяся музыкальная', 1, 368.00, 2, 1, 4, 6, 7, 'Светящаяся музыкальная машина с диско шаром переливается разными цветами, играет ритмичные мелодии, объезжает препятствия и крутится, поэтому с ней точно не будет скучно.', 'images/products/4.jpg');
|
|
152
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (5, '3XBOTN', 'Игровой набор Hot Wheels Action Loop Cyclone Challenge Track, с машинкой и удобным хранением, HTK16', 1, 3426.00, 2, 2, 2, 10, 21, 'Игровой набор Hot Wheels Action Loop Cyclone Challenge Track - это уникальная игра, которая позволит вам испытать себя и своих друзей в скорости и ловкости. Этот набор состоит из металлической дорожки с циклоном, которая создает потрясающий эффект и добавляет дополнительную сложность в игру.', 'images/products/5.jpg');
|
|
153
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (6, '3L7RCZ', 'Игровой набор с деревянными машинками Стройплощадка Кран-Паркс, Junion', 1, 7400.00, 2, 3, 2, 15, 0, 'Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.', 'images/products/6.jpg');
|
|
154
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (7, 'S72AM3', 'Синтезатор детский с микрофоном 61 клавиша', 1, 1749.00, 1, 3, 1, 10, 35, 'Откройте для ребенка дверь в мир музыки с детским синтезатором! Этот компактный инструмент с микрофоном станет верным другом для юных музыкантов, помогая им развивать творческий потенциал и получать удовольствие от игры.', 'images/products/7.jpg');
|
|
155
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (8, '2G3280', 'Деревянный игровой набор JUNION Стройплощадка "Кран-Паркс" с подъёмным, строительным краном и машинками, 18 предметов, подвижные элементы', 1, 1624.00, 5, 3, 2, 9, 20, 'Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.', 'images/products/8.jpg');
|
|
156
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (9, 'MIO8YV', 'Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей', 1, 305.00, 5, 2, 1, 9, 31, 'Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей', 'images/products/9.jpg');
|
|
157
|
+
INSERT INTO products(product_id, article, product_name, unit_id, price, supplier_id, manufacturer_id, category_id, discount, stock_quantity, description, image_path) VALUES (10, 'UER2QD', 'Большой набор опытов и экспериментов для детей 14 в 1', 1, 2506.00, 5, 2, 2, 8, 27, 'Большой набор опытов и экспериментов для детей 14 в 1', 'images/products/10.jpg');
|
|
158
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (1, 1, 1, 7, '2025-02-27', '2025-04-20', '901');
|
|
159
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (1, 1, 2);
|
|
160
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (1, 2, 2);
|
|
161
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (2, 1, 11, 8, '2024-09-28', '2025-04-21', '902');
|
|
162
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (2, 3, 1);
|
|
163
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (2, 4, 1);
|
|
164
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (3, 1, 2, 9, '2025-03-21', '2025-04-22', '903');
|
|
165
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (3, 5, 10);
|
|
166
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (3, 6, 10);
|
|
167
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (4, 1, 11, 10, '2025-02-20', '2025-04-23', '904');
|
|
168
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (4, 7, 5);
|
|
169
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (4, 8, 4);
|
|
170
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (5, 1, 2, 7, '2025-03-17', '2025-04-24', '905');
|
|
171
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (5, 9, 2);
|
|
172
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (5, 10, 2);
|
|
173
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (6, 1, 15, 8, '2025-03-01', '2025-04-25', '906');
|
|
174
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (6, 1, 2);
|
|
175
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (6, 2, 2);
|
|
176
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (7, 1, 3, 9, '2025-02-28', '2025-04-26', '907');
|
|
177
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (7, 3, 1);
|
|
178
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (7, 4, 1);
|
|
179
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (8, 2, 19, 10, '2025-03-31', '2025-04-27', '908');
|
|
180
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (8, 5, 10);
|
|
181
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (8, 6, 10);
|
|
182
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (9, 2, 5, 9, '2025-04-02', '2025-04-28', '909');
|
|
183
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (9, 7, 5);
|
|
184
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (9, 8, 4);
|
|
185
|
+
INSERT INTO orders(order_id, status_id, pickup_point_id, customer_id, order_date, delivery_date, receive_code) VALUES (10, 2, 19, 10, '2025-04-03', '2025-04-29', '910');
|
|
186
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (10, 9, 2);
|
|
187
|
+
INSERT INTO order_items(order_id, product_id, quantity) VALUES (10, 10, 2);
|
|
188
|
+
|
|
189
|
+
SELECT setval(pg_get_serial_sequence('roles','role_id'), COALESCE(MAX(role_id), 1), true) FROM roles;
|
|
190
|
+
SELECT setval(pg_get_serial_sequence('users','user_id'), COALESCE(MAX(user_id), 1), true) FROM users;
|
|
191
|
+
SELECT setval(pg_get_serial_sequence('categories','category_id'), COALESCE(MAX(category_id), 1), true) FROM categories;
|
|
192
|
+
SELECT setval(pg_get_serial_sequence('manufacturers','manufacturer_id'), COALESCE(MAX(manufacturer_id), 1), true) FROM manufacturers;
|
|
193
|
+
SELECT setval(pg_get_serial_sequence('suppliers','supplier_id'), COALESCE(MAX(supplier_id), 1), true) FROM suppliers;
|
|
194
|
+
SELECT setval(pg_get_serial_sequence('units','unit_id'), COALESCE(MAX(unit_id), 1), true) FROM units;
|
|
195
|
+
SELECT setval(pg_get_serial_sequence('products','product_id'), COALESCE(MAX(product_id), 1), true) FROM products;
|
|
196
|
+
SELECT setval(pg_get_serial_sequence('pickup_points','pickup_point_id'), COALESCE(MAX(pickup_point_id), 1), true) FROM pickup_points;
|
|
197
|
+
SELECT setval(pg_get_serial_sequence('order_statuses','status_id'), COALESCE(MAX(status_id), 1), true) FROM order_statuses;
|
|
198
|
+
SELECT setval(pg_get_serial_sequence('orders','order_id'), COALESCE(MAX(order_id), 1), true) FROM orders;
|
|
199
|
+
SELECT setval(pg_get_serial_sequence('order_items','order_item_id'), COALESCE(MAX(order_item_id), 1), true) FROM order_items;
|
|
200
|
+
COMMIT;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import psycopg2
|
|
3
|
+
from psycopg2.extras import RealDictCursor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_connection():
|
|
7
|
+
return psycopg2.connect(
|
|
8
|
+
host=os.getenv('DB_HOST', 'localhost'),
|
|
9
|
+
port=os.getenv('DB_PORT', '5432'),
|
|
10
|
+
dbname=os.getenv('DB_NAME', 'postgres'),
|
|
11
|
+
user=os.getenv('DB_USER', 'postgres'),
|
|
12
|
+
password=os.getenv('DB_PASSWORD', '123456'),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fetch_all(query, params=None):
|
|
17
|
+
with get_connection() as connection:
|
|
18
|
+
with connection.cursor(cursor_factory=RealDictCursor) as cursor:
|
|
19
|
+
cursor.execute(query, params or [])
|
|
20
|
+
return cursor.fetchall()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def fetch_one(query, params=None):
|
|
24
|
+
with get_connection() as connection:
|
|
25
|
+
with connection.cursor(cursor_factory=RealDictCursor) as cursor:
|
|
26
|
+
cursor.execute(query, params or [])
|
|
27
|
+
return cursor.fetchone()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute(query, params=None, returning=False):
|
|
31
|
+
with get_connection() as connection:
|
|
32
|
+
with connection.cursor(cursor_factory=RealDictCursor) as cursor:
|
|
33
|
+
cursor.execute(query, params or [])
|
|
34
|
+
if returning:
|
|
35
|
+
return cursor.fetchone()
|
|
36
|
+
return None
|