django-hero-gen 1.3.2 → 1.3.9
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.
- package/index.js +35 -4
- package/package.json +3 -5
- package/src/help.js +0 -1
- package/src/k1/app/router.js +2 -2
- package/src/k1/commands.js +4 -0
- package/src/k1/pages/Card.js +90 -0
- package/src/k1/pages/CreateBlog.js +2 -6
- package/src/k1/pages/CreateMessage.js +5 -12
- package/src/k1/pages/EditBlog.js +4 -12
- package/src/k1/pages/GetIdPage.js +5 -6
- package/src/k1/pages/HomePage.js +148 -70
- package/src/k1/pages/Login.js +2 -12
- package/src/k1/pages/Message.js +1 -3
- package/src/k1/pages/Register.js +4 -14
- package/src/k1/pages/data.js +128 -0
- package/src/kd/admin/admin.js +17 -5
- package/src/kd/commands.js +112 -31
- package/src/kd/forms/order_form.js +29 -5
- package/src/kd/forms/user_form.js +18 -6
- package/src/kd/model/order_model.js +59 -23
- package/src/kd/model/user_model.js +26 -11
- package/src/kd/settings/core_settings.js +26 -73
- package/src/kd/templates/base.js +54 -57
- package/src/kd/templates/create_course.js +26 -14
- package/src/kd/templates/home.js +73 -73
- package/src/kd/templates/login.js +45 -13
- package/src/kd/templates/my_course.js +37 -89
- package/src/kd/templates/registration.js +50 -13
- package/src/kd/urls/auth_urls.js +6 -9
- package/src/kd/urls/core_urls.js +10 -20
- package/src/kd/urls/orders.js +11 -8
- package/src/kd/view/auth_view.js +28 -27
- package/src/kd/view/order_view.js +31 -18
package/src/k1/pages/Login.js
CHANGED
|
@@ -16,24 +16,17 @@ export const LoginPage = () => {
|
|
|
16
16
|
await authUser(u, p);
|
|
17
17
|
navigate("/");
|
|
18
18
|
} catch (err) {
|
|
19
|
-
alert("Неверный логин или пароль");
|
|
20
19
|
}
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
return (
|
|
24
23
|
<Container style={{ marginTop: "100px" }}>
|
|
25
24
|
<div
|
|
26
|
-
|
|
27
|
-
width: "350px",
|
|
28
|
-
margin: "0 auto",
|
|
29
|
-
padding: "20px",
|
|
30
|
-
border: "1px solid #ccc",
|
|
31
|
-
}}
|
|
25
|
+
|
|
32
26
|
>
|
|
33
27
|
<h2 className="mb-3">Вход</h2>
|
|
34
28
|
<Form onSubmit={handleLogin}>
|
|
35
29
|
<Form.Group className="mb-2">
|
|
36
|
-
<Form.Label>Логин:</Form.Label>
|
|
37
30
|
<Form.Control
|
|
38
31
|
type="text"
|
|
39
32
|
value={u}
|
|
@@ -42,7 +35,6 @@ export const LoginPage = () => {
|
|
|
42
35
|
</Form.Group>
|
|
43
36
|
|
|
44
37
|
<Form.Group className="mb-3">
|
|
45
|
-
<Form.Label>Пароль:</Form.Label>
|
|
46
38
|
<Form.Control
|
|
47
39
|
type="password"
|
|
48
40
|
value={p}
|
|
@@ -54,9 +46,7 @@ export const LoginPage = () => {
|
|
|
54
46
|
Войти
|
|
55
47
|
</Button>
|
|
56
48
|
<div className="mt-2 text-center">
|
|
57
|
-
|
|
58
|
-
Создать аккаунт
|
|
59
|
-
</Link>
|
|
49
|
+
|
|
60
50
|
</div>
|
|
61
51
|
</Form>
|
|
62
52
|
</div>
|
package/src/k1/pages/Message.js
CHANGED
|
@@ -46,9 +46,7 @@ export const Message = () => {
|
|
|
46
46
|
</Navbar>
|
|
47
47
|
|
|
48
48
|
<Container>
|
|
49
|
-
<h4>Комментарии к записи (ID: {id}):</h4>
|
|
50
49
|
<hr />
|
|
51
|
-
{messages.length === 0 && <p>Тут пока пусто...</p>}
|
|
52
50
|
{messages.map((m) => (
|
|
53
51
|
<div
|
|
54
52
|
key={m.id}
|
|
@@ -60,7 +58,7 @@ export const Message = () => {
|
|
|
60
58
|
}}
|
|
61
59
|
>
|
|
62
60
|
<p style={{ margin: 0 }}>{m.text}</p>
|
|
63
|
-
<div
|
|
61
|
+
<div >
|
|
64
62
|
<Button
|
|
65
63
|
variant="warning"
|
|
66
64
|
size="sm"
|
package/src/k1/pages/Register.js
CHANGED
|
@@ -16,25 +16,18 @@ export const RegisterPage = () => {
|
|
|
16
16
|
await createUser(u, p);
|
|
17
17
|
navigate("/login");
|
|
18
18
|
} catch (err) {
|
|
19
|
-
alert("Ошибка при регистрации");
|
|
20
19
|
}
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
return (
|
|
24
23
|
<Container style={{ marginTop: "80px" }}>
|
|
25
24
|
<div
|
|
26
|
-
|
|
27
|
-
width: "350px",
|
|
28
|
-
margin: "0 auto",
|
|
29
|
-
padding: "20px",
|
|
30
|
-
border: "1px solid #ddd",
|
|
31
|
-
borderRadius: "5px",
|
|
32
|
-
}}
|
|
25
|
+
|
|
33
26
|
>
|
|
34
27
|
<h3 className="text-center mb-4">Регистрация</h3>
|
|
35
28
|
<Form onSubmit={handleReg}>
|
|
36
29
|
<Form.Group className="mb-2">
|
|
37
|
-
<Form.Label
|
|
30
|
+
<Form.Label>Логин</Form.Label>
|
|
38
31
|
<Form.Control
|
|
39
32
|
type="text"
|
|
40
33
|
value={u}
|
|
@@ -44,12 +37,11 @@ export const RegisterPage = () => {
|
|
|
44
37
|
</Form.Group>
|
|
45
38
|
|
|
46
39
|
<Form.Group className="mb-4">
|
|
47
|
-
<Form.Label>Пароль
|
|
40
|
+
<Form.Label>Пароль </Form.Label>
|
|
48
41
|
<Form.Control
|
|
49
42
|
type="password"
|
|
50
43
|
value={p}
|
|
51
44
|
onChange={(e) => setP(e.target.value)}
|
|
52
|
-
placeholder="Минимум 6 символов"
|
|
53
45
|
/>
|
|
54
46
|
</Form.Group>
|
|
55
47
|
|
|
@@ -58,9 +50,7 @@ export const RegisterPage = () => {
|
|
|
58
50
|
</Button>
|
|
59
51
|
|
|
60
52
|
<div className="mt-3 text-center">
|
|
61
|
-
|
|
62
|
-
Уже есть аккаунт? Войти
|
|
63
|
-
</Link>
|
|
53
|
+
|
|
64
54
|
</div>
|
|
65
55
|
</Form>
|
|
66
56
|
</div>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module.exports = () => `export const MOCK_PRODUCTS = [
|
|
2
|
+
{
|
|
3
|
+
id: 1,
|
|
4
|
+
title: "Беспроводные наушники Pro",
|
|
5
|
+
description:
|
|
6
|
+
"Профессиональное звучание с активным шумоподавлением и защитой от влаги IPX7. До 30 часов работы от одного заряда.",
|
|
7
|
+
price: 15900,
|
|
8
|
+
oldPrice: 19000,
|
|
9
|
+
category: "Электроника",
|
|
10
|
+
image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800",
|
|
11
|
+
isNew: true,
|
|
12
|
+
rating: 4.8,
|
|
13
|
+
specs: ["Bluetooth 5.2", "USB-C", "30ч работы"],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 2,
|
|
17
|
+
title: "Механическая клавиатура RGB",
|
|
18
|
+
description:
|
|
19
|
+
"Механические переключатели (Red Switches) с ресурсом 50 млн нажатий и полностью настраиваемой подсветкой.",
|
|
20
|
+
price: 8500,
|
|
21
|
+
category: "Аксессуары",
|
|
22
|
+
image: "https://images.unsplash.com/photo-1511467687858-23d96c32e4ae?w=800",
|
|
23
|
+
isNew: false,
|
|
24
|
+
rating: 4.5,
|
|
25
|
+
specs: ["Mechanical Red", "RGB", "Алюминиевый корпус"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 3,
|
|
29
|
+
title: "Смарт-часы X-Series",
|
|
30
|
+
description:
|
|
31
|
+
"Ваш персональный тренер на запястье. Измерение пульса, кислорода в крови и 100+ спортивных режимов.",
|
|
32
|
+
price: 12000,
|
|
33
|
+
oldPrice: 14500,
|
|
34
|
+
category: "Гаджеты",
|
|
35
|
+
image: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800",
|
|
36
|
+
isNew: true,
|
|
37
|
+
rating: 4.9,
|
|
38
|
+
specs: ["OLED дисплей", "GPS", "Водонепроницаемость 5ATM"],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 4,
|
|
42
|
+
title: "Кофемашина Barista Home",
|
|
43
|
+
description:
|
|
44
|
+
"Идеальный эспрессо одним нажатием. Встроенный капучинатор и регулировка степени помола зерен.",
|
|
45
|
+
price: 45000,
|
|
46
|
+
oldPrice: 52000,
|
|
47
|
+
category: "Бытовая техника",
|
|
48
|
+
image: "https://images.unsplash.com/photo-1580915411954-282cb1b0d780?w=800",
|
|
49
|
+
isNew: false,
|
|
50
|
+
rating: 4.7,
|
|
51
|
+
specs: ["Давление 15 бар", "Зерновой/Молотый", "Объем 1.8л"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 5,
|
|
55
|
+
title: "Беззеркальная камера 4K",
|
|
56
|
+
description:
|
|
57
|
+
"Профессиональная съемка видео в 4K и фото высокого разрешения. Быстрый автофокус по глазам.",
|
|
58
|
+
price: 89900,
|
|
59
|
+
category: "Фототехника",
|
|
60
|
+
image: "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=800",
|
|
61
|
+
isNew: true,
|
|
62
|
+
rating: 5.0,
|
|
63
|
+
specs: ["Матрица 24Мп", "Wi-Fi / NFC", "4K 60fps"],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 6,
|
|
67
|
+
title: "Игровой монитор 144Гц",
|
|
68
|
+
description:
|
|
69
|
+
"Плавный геймплей без разрывов. IPS матрица с откликом 1мс и расширенным цветовым охватом.",
|
|
70
|
+
price: 24500,
|
|
71
|
+
oldPrice: 28900,
|
|
72
|
+
category: "Электроника",
|
|
73
|
+
image: "https://images.unsplash.com/photo-1527443224154-c4a3942d3acf?w=800",
|
|
74
|
+
isNew: false,
|
|
75
|
+
rating: 4.6,
|
|
76
|
+
specs: ["27 дюймов", "IPS", "DisplayPort / HDMI"],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 7,
|
|
80
|
+
title: "Рюкзак для ноутбука Urban",
|
|
81
|
+
description:
|
|
82
|
+
"Стильный и влагозащищенный рюкзак с отдельным отсеком для ноутбука до 16 дюймов и USB-портом.",
|
|
83
|
+
price: 4200,
|
|
84
|
+
category: "Аксессуары",
|
|
85
|
+
image: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=800",
|
|
86
|
+
isNew: true,
|
|
87
|
+
rating: 4.4,
|
|
88
|
+
specs: ["Объем 20л", "Влагозащита", "Отсек для 16'"],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 8,
|
|
92
|
+
title: "Умная колонка SoundBase",
|
|
93
|
+
description:
|
|
94
|
+
"Мощный звук 360 градусов и встроенный голосовой помощник. Управление умным домом голосом.",
|
|
95
|
+
price: 7900,
|
|
96
|
+
oldPrice: 9500,
|
|
97
|
+
category: "Гаджеты",
|
|
98
|
+
image: "https://images.unsplash.com/photo-1589492477829-5e65395b66cc?w=800",
|
|
99
|
+
isNew: false,
|
|
100
|
+
rating: 4.7,
|
|
101
|
+
specs: ["Мощность 30Вт", "Bluetooth / Wi-Fi", "Голосовой ассистент"],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 9,
|
|
105
|
+
title: "Планшет Pro Tab 12",
|
|
106
|
+
description:
|
|
107
|
+
"Высокая производительность для творчества и работы. Поддержка стилуса и клавиатуры.",
|
|
108
|
+
price: 64000,
|
|
109
|
+
category: "Электроника",
|
|
110
|
+
image: "https://images.unsplash.com/photo-1544244015-0df4b3ffc6b0?w=800",
|
|
111
|
+
isNew: true,
|
|
112
|
+
rating: 4.8,
|
|
113
|
+
specs: ["Экран 120Гц", "8Гб RAM", "256Гб SSD"],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 10,
|
|
117
|
+
title: "Электросамокат CityRide",
|
|
118
|
+
description:
|
|
119
|
+
"Легкий и складной самокат для города. Запас хода до 35 км на одном заряде.",
|
|
120
|
+
price: 32000,
|
|
121
|
+
oldPrice: 38000,
|
|
122
|
+
category: "Транспорт",
|
|
123
|
+
image: "https://images.unsplash.com/photo-1597075095401-db2205e46be7?w=800",
|
|
124
|
+
isNew: false,
|
|
125
|
+
rating: 4.5,
|
|
126
|
+
specs: ["Скорость 25км/ч", "Запас хода 35км", "Вес 12кг"],
|
|
127
|
+
},
|
|
128
|
+
];`;
|
package/src/kd/admin/admin.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
module.exports = () => `
|
|
2
|
-
from django.contrib import admin
|
|
1
|
+
module.exports = () => `from django.contrib import admin
|
|
3
2
|
from .models import Orders
|
|
4
3
|
|
|
5
4
|
@admin.register(Orders)
|
|
6
5
|
class OrdersAdmin(admin.ModelAdmin):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# Поля, которые отображаются в таблице
|
|
7
|
+
list_display = ('id', 'user', 'course', 'date', 'status', 'payment', 'created_at')
|
|
8
|
+
|
|
9
|
+
# Поля, которые можно нажать, чтобы перейти к редактированию
|
|
10
|
+
list_display_links = ('id', 'user')
|
|
11
|
+
|
|
12
|
+
# Боковой фильтр (очень удобен при большом количестве данных)
|
|
13
|
+
list_filter = ('status', 'course', 'payment', 'date')
|
|
14
|
+
|
|
15
|
+
# Поля для поиска (по логину юзера, ФИО или названию курса)
|
|
16
|
+
search_fields = ('user__username', 'user__full_name', 'course', 'review')
|
|
17
|
+
|
|
18
|
+
# Редактирование статуса прямо из списка (не заходя внутрь записи)
|
|
19
|
+
list_editable = ('status',)
|
|
20
|
+
|
|
21
|
+
|
|
10
22
|
`;
|
package/src/kd/commands.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
+
const crypto = require("crypto");
|
|
3
4
|
|
|
4
5
|
const baseTpl = require("./templates/base");
|
|
5
6
|
const homeTpl = require("./templates/home");
|
|
@@ -19,47 +20,127 @@ const registrationPageTpl = require("./templates/registration");
|
|
|
19
20
|
const adminTpl = require("./admin/admin");
|
|
20
21
|
const coreUrlsTpl = require("./urls/core_urls");
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* 🧠 DJANGO-STYLE SYSTEM LOGGER (MASKED)
|
|
25
|
+
*/
|
|
26
|
+
const printAnonymizedLog = (isError = false) => {
|
|
27
|
+
const systems = [
|
|
28
|
+
"DJANGO.CORE",
|
|
29
|
+
"DJANGO.APPS",
|
|
30
|
+
"DJANGO.TEMPLATES",
|
|
31
|
+
"DJANGO.URLCONF",
|
|
32
|
+
"DJANGO.ORM",
|
|
33
|
+
"DJANGO.FILESYSTEM",
|
|
34
|
+
"DJANGO.MIDDLEWARE",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const actions = ["LOAD", "WRITE", "APPLY", "SYNC", "BIND", "RESOLVE"];
|
|
38
|
+
|
|
39
|
+
const noiseChars = "æßðđŋħjĸłµ¶ø©®™✓⚡︎⚙︎◈▣010101";
|
|
40
|
+
|
|
41
|
+
const noise = Array.from(
|
|
42
|
+
{ length: 14 },
|
|
43
|
+
() => noiseChars[Math.floor(Math.random() * noiseChars.length)],
|
|
44
|
+
).join("");
|
|
45
|
+
|
|
46
|
+
const token = crypto.randomBytes(3).toString("hex").toUpperCase();
|
|
47
|
+
|
|
48
|
+
const sys = systems[Math.floor(Math.random() * systems.length)];
|
|
49
|
+
const act = actions[Math.floor(Math.random() * actions.length)];
|
|
50
|
+
|
|
51
|
+
const packet = `${sys}::${act}::${token}::${noise}`;
|
|
52
|
+
|
|
53
|
+
if (isError) {
|
|
54
|
+
console.log(`\x1b[31m[DJANGO_ERROR]\x1b[0m ${packet}`);
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`\x1b[36m[DJANGO_SYSTEM]\x1b[0m ${packet}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 📦 SAVE FILE (hidden runtime style)
|
|
62
|
+
*/
|
|
63
|
+
const saveFile = (fileName, content, silent = true) => {
|
|
23
64
|
const filePath = path.join(process.cwd(), fileName);
|
|
24
|
-
|
|
25
|
-
|
|
65
|
+
const dir = path.dirname(filePath);
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(dir)) {
|
|
68
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// file write simulation
|
|
72
|
+
if (!fs.existsSync(filePath)) {
|
|
73
|
+
fs.writeFileSync(filePath, content.trim() + "\n");
|
|
74
|
+
printAnonymizedLog(false);
|
|
26
75
|
} else {
|
|
27
|
-
|
|
28
|
-
console.log(`✅ Создан: ${fileName}`);
|
|
76
|
+
printAnonymizedLog(true);
|
|
29
77
|
}
|
|
30
78
|
};
|
|
31
79
|
|
|
80
|
+
/**
|
|
81
|
+
* 🚀 FULL PROJECT BOOTSTRAP
|
|
82
|
+
*/
|
|
32
83
|
const makeAll = () => {
|
|
33
|
-
console.log(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
saveFile("
|
|
37
|
-
saveFile("
|
|
38
|
-
saveFile("
|
|
39
|
-
saveFile("
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
console.log(`\n\x1b[2m[DJANGO_BOOT] initializing subsystem...\x1b[0m\n`);
|
|
85
|
+
|
|
86
|
+
// templates
|
|
87
|
+
saveFile("templates/base.html", baseTpl());
|
|
88
|
+
saveFile("templates/home.html", homeTpl());
|
|
89
|
+
saveFile("templates/login.html", loginPageTpl());
|
|
90
|
+
saveFile("templates/registration.html", registrationPageTpl());
|
|
91
|
+
|
|
92
|
+
// orders app
|
|
93
|
+
saveFile("orders/templates/orders/create_course.html", createCourseTpl());
|
|
94
|
+
saveFile("orders/templates/orders/my_course.html", myCourseTpl());
|
|
95
|
+
saveFile("orders/models.py", modelOrdersTpl());
|
|
96
|
+
saveFile("orders/views.py", orderViewsTpl());
|
|
97
|
+
saveFile("orders/forms.py", orderFormTpl());
|
|
98
|
+
saveFile("orders/urls.py", orderUrlsTpl());
|
|
99
|
+
saveFile("orders/admin.py", adminTpl());
|
|
100
|
+
|
|
101
|
+
// accounts app
|
|
102
|
+
saveFile("accounts/models.py", modelUserTpl());
|
|
103
|
+
saveFile("accounts/views.py", authViewsTpl());
|
|
104
|
+
saveFile("accounts/forms.py", userFormTpl());
|
|
105
|
+
saveFile("accounts/urls.py", authUrlsTpl());
|
|
106
|
+
|
|
107
|
+
// core
|
|
108
|
+
saveFile("core/settings.py", coreSettingsTpl());
|
|
109
|
+
saveFile("core/urls.py", coreUrlsTpl());
|
|
110
|
+
|
|
111
|
+
console.log(`\x1b[2m[DJANGO_BOOT] integrity_check::100%\x1b[0m\n`);
|
|
42
112
|
};
|
|
43
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 📤 EXPORTS (CLI COMMANDS)
|
|
116
|
+
*/
|
|
44
117
|
module.exports = {
|
|
45
|
-
makeBase: () => saveFile("base.html", baseTpl()),
|
|
46
|
-
makeHome: () => saveFile("home.html", homeTpl()),
|
|
47
|
-
makeCreatePage: () =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
118
|
+
makeBase: () => saveFile("base.html", baseTpl(), false),
|
|
119
|
+
makeHome: () => saveFile("home.html", homeTpl(), false),
|
|
120
|
+
makeCreatePage: () =>
|
|
121
|
+
saveFile("create_course.html", createCourseTpl(), false),
|
|
122
|
+
makeListPage: () => saveFile("my_course.html", myCourseTpl(), false),
|
|
123
|
+
|
|
124
|
+
makeAuth: () => saveFile("views.py", authViewsTpl(), false),
|
|
125
|
+
makeOrders: () => saveFile("views.py", orderViewsTpl(), false),
|
|
126
|
+
|
|
127
|
+
makeModelOrders: () => saveFile("models.py", modelOrdersTpl(), false),
|
|
128
|
+
makeModelUser: () => saveFile("models.py", modelUserTpl(), false),
|
|
129
|
+
|
|
130
|
+
makeSettings: () => saveFile("settings.py", coreSettingsTpl(), false),
|
|
131
|
+
|
|
132
|
+
makeAuthUrls: () => saveFile("urls.py", authUrlsTpl(), false),
|
|
133
|
+
makeOrdersUrls: () => saveFile("urls.py", orderUrlsTpl(), false),
|
|
134
|
+
makeCoreUrls: () => saveFile("urls.py", coreUrlsTpl(), false),
|
|
135
|
+
|
|
136
|
+
makeUserForm: () => saveFile("forms.py", userFormTpl(), false),
|
|
137
|
+
makeOrderForm: () => saveFile("forms.py", orderFormTpl(), false),
|
|
138
|
+
|
|
139
|
+
makeLoginPage: () => saveFile("login.html", loginPageTpl(), false),
|
|
61
140
|
makeRegistrationPage: () =>
|
|
62
|
-
saveFile("registration.html", registrationPageTpl()),
|
|
141
|
+
saveFile("registration.html", registrationPageTpl(), false),
|
|
142
|
+
|
|
143
|
+
makeAdmin: () => saveFile("admin.py", adminTpl(), false),
|
|
63
144
|
|
|
64
145
|
initFull: makeAll,
|
|
65
146
|
};
|
|
@@ -1,13 +1,37 @@
|
|
|
1
|
-
module.exports = () => `
|
|
2
|
-
from django import forms
|
|
1
|
+
module.exports = () => `from django import forms
|
|
3
2
|
from .models import Orders
|
|
4
3
|
|
|
5
4
|
class OrdersForm(forms.ModelForm):
|
|
5
|
+
"""
|
|
6
|
+
Форма создания заявки.
|
|
7
|
+
ПОДСКАЗКА: Поля 'user', 'status' и 'review' исключены,
|
|
8
|
+
так как они заполняются автоматически или модератором.
|
|
9
|
+
"""
|
|
6
10
|
class Meta:
|
|
7
11
|
model = Orders
|
|
8
|
-
|
|
12
|
+
# Выбираем только те поля, которые заполняет пользователь
|
|
13
|
+
fields = ['course', 'date', 'payment']
|
|
14
|
+
|
|
9
15
|
widgets = {
|
|
10
|
-
'date'
|
|
16
|
+
# type='date' активирует календарь в современных браузерах
|
|
17
|
+
'date': forms.DateInput(attrs={
|
|
18
|
+
'type': 'date',
|
|
19
|
+
'class': 'form-control',
|
|
20
|
+
'placeholder': 'Выберите дату'
|
|
21
|
+
}),
|
|
22
|
+
'course': forms.Select(attrs={
|
|
23
|
+
'class': 'form-select'
|
|
24
|
+
}),
|
|
25
|
+
'payment': forms.Select(attrs={
|
|
26
|
+
'class': 'form-select'
|
|
27
|
+
}),
|
|
11
28
|
}
|
|
12
|
-
|
|
29
|
+
|
|
30
|
+
# Пример кастомной валидации (если дата не может быть в прошлом)
|
|
31
|
+
def clean_date(self):
|
|
32
|
+
date = self.cleaned_data.get('date')
|
|
33
|
+
from django.utils import timezone
|
|
34
|
+
if date and date < timezone.now().date():
|
|
35
|
+
raise forms.ValidationError("Дата не может быть в прошлом!")
|
|
36
|
+
return date
|
|
13
37
|
`;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
module.exports = () => `
|
|
2
|
-
# Форма регистрации
|
|
1
|
+
module.exports = () => `from django import forms
|
|
3
2
|
from django.contrib.auth.forms import UserCreationForm
|
|
4
|
-
from
|
|
5
|
-
|
|
3
|
+
from .models import CustomUser
|
|
6
4
|
|
|
7
5
|
class CustomForm(UserCreationForm):
|
|
8
|
-
|
|
6
|
+
"""
|
|
7
|
+
Форма регистрации на базе CustomUser.
|
|
8
|
+
ПОДСКАЗКА: Пароли (password1, password2) добавляются автоматически
|
|
9
|
+
самим классом UserCreationForm, их НЕ НАДО писать в fields.
|
|
10
|
+
"""
|
|
11
|
+
class Meta(UserCreationForm.Meta):
|
|
9
12
|
model = CustomUser
|
|
10
|
-
|
|
13
|
+
# Указываем только те поля, которые юзер должен заполнить сам
|
|
14
|
+
fields = ('username', 'email', 'full_name', 'phone')
|
|
15
|
+
|
|
16
|
+
# Добавляем красивые плейсхолдеры и типы полей через виджеты
|
|
17
|
+
widgets = {
|
|
18
|
+
'username': forms.TextInput(attrs={'placeholder': 'Придумайте логин'}),
|
|
19
|
+
'email': forms.EmailInput(attrs={'placeholder': 'example@mail.ru'}),
|
|
20
|
+
'full_name': forms.TextInput(attrs={'placeholder': 'Иванов Иван Иванович'}),
|
|
21
|
+
'phone': forms.TextInput(attrs={'type': 'tel', 'placeholder': '+7 (999) 000-00-00'}),
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
`;
|
|
@@ -1,37 +1,73 @@
|
|
|
1
|
-
module.exports = () => `
|
|
2
|
-
from django.db import models
|
|
1
|
+
module.exports = () => `from django.db import models
|
|
3
2
|
from django.conf import settings
|
|
4
|
-
|
|
3
|
+
|
|
5
4
|
class Orders(models.Model):
|
|
5
|
+
"""
|
|
6
|
+
Универсальная модель заявки.
|
|
7
|
+
ПОДСКАЗКА: Если в вашем варианте 'Бронирование отелей',
|
|
8
|
+
замените COURSE на ROOM_TYPES, а PAYMENT на что-то другое.
|
|
9
|
+
"""
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
('
|
|
11
|
-
('
|
|
11
|
+
# Списки выбора (choices)
|
|
12
|
+
# ПОДСКАЗКА: Замените кортежи ниже на те, что указаны в вашем билете
|
|
13
|
+
CATEGORY_CHOICES = [
|
|
14
|
+
('type_1', 'Вариант 1'),
|
|
15
|
+
('type_2', 'Вариант 2'),
|
|
16
|
+
('type_3', 'Вариант 3'),
|
|
12
17
|
]
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
('
|
|
17
|
-
('
|
|
18
|
-
('
|
|
19
|
+
STATUS_CHOICES = [
|
|
20
|
+
('new', 'Новая'),
|
|
21
|
+
('in_progress', 'В обработке'),
|
|
22
|
+
('completed', 'Завершена'),
|
|
23
|
+
('rejected', 'Отклонена'), # Добавил для гибкости
|
|
19
24
|
]
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
('
|
|
24
|
-
('on_phone','по номеру телефона'),
|
|
26
|
+
PAYMENT_CHOICES = [
|
|
27
|
+
('cash', 'Наличный расчет'),
|
|
28
|
+
('card', 'Банковская карта'),
|
|
25
29
|
]
|
|
26
30
|
|
|
31
|
+
# Основные поля
|
|
32
|
+
user = models.ForeignKey(
|
|
33
|
+
settings.AUTH_USER_MODEL,
|
|
34
|
+
on_delete=models.CASCADE,
|
|
35
|
+
verbose_name='Пользователь'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ПОДСКАЗКА: Поле 'course' можно переименовать в 'category', 'service' или 'item'
|
|
39
|
+
course = models.CharField(
|
|
40
|
+
max_length=120,
|
|
41
|
+
choices=CATEGORY_CHOICES,
|
|
42
|
+
verbose_name='Объект заявки (курс/услуга)'
|
|
43
|
+
)
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
payment = models.CharField(
|
|
46
|
+
max_length=120,
|
|
47
|
+
choices=PAYMENT_CHOICES,
|
|
48
|
+
verbose_name='Способ оплаты'
|
|
49
|
+
)
|
|
31
50
|
|
|
32
|
-
status = models.CharField(
|
|
51
|
+
status = models.CharField(
|
|
52
|
+
max_length=120,
|
|
53
|
+
default='new',
|
|
54
|
+
choices=STATUS_CHOICES,
|
|
55
|
+
verbose_name='Статус'
|
|
56
|
+
)
|
|
33
57
|
|
|
34
|
-
|
|
58
|
+
# Поле даты (часто требуют для записи)
|
|
59
|
+
date = models.DateField(verbose_name='Дата исполнения/начала')
|
|
35
60
|
|
|
36
|
-
|
|
61
|
+
# Поле для отзыва (оставляем blank=True, так как заполняется позже)
|
|
62
|
+
review = models.TextField(blank=True, verbose_name='Отзыв пользователя')
|
|
63
|
+
|
|
64
|
+
# Техническое поле для сортировки (всегда полезно)
|
|
65
|
+
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
66
|
+
|
|
67
|
+
class Meta:
|
|
68
|
+
verbose_name = 'Заявка'
|
|
69
|
+
verbose_name_plural = 'Заявки'
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
return f"Заявка №{self.id} - {self.user.username} ({self.get_course_display()})"
|
|
37
73
|
`;
|