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.
@@ -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
- style={{
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
- <Link to="/register" style={{ fontSize: "14px" }}>
58
- Создать аккаунт
59
- </Link>
49
+
60
50
  </div>
61
51
  </Form>
62
52
  </div>
@@ -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 style={{ marginTop: "10px", display: "flex", gap: "5px" }}>
61
+ <div >
64
62
  <Button
65
63
  variant="warning"
66
64
  size="sm"
@@ -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
- style={{
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>Логин (username)</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>Пароль (password)</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
- <Link to="/login" style={{ fontSize: "14px" }}>
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
+ ];`;
@@ -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
- list_display = ('user', 'course', 'date', 'payment', 'status')
8
- list_filter = ('status', 'payment', 'course')
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
  `;
@@ -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
- const saveFile = (fileName, content) => {
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
- if (fs.existsSync(filePath)) {
25
- console.log(`⚠️ Пропуск: ${fileName} (уже существует)`);
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
- fs.writeFileSync(filePath, content);
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
- saveFile("base.html", baseTpl());
35
- saveFile("home.html", homeTpl());
36
- saveFile("create_course.html", createCourseTpl());
37
- saveFile("my_course.html", myCourseTpl());
38
- saveFile("views.py", authViewsTpl());
39
- saveFile("order_views.py", orderViewsTpl());
40
- saveFile("admin.py", adminTpl());
41
- console.log("✨ Готово! Не забудь настроить urls.py.");
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: () => saveFile("create_course.html", createCourseTpl()),
48
- makeListPage: () => saveFile("my_course.html", myCourseTpl()),
49
- makeAuth: () => saveFile("views.py", authViewsTpl()),
50
- makeOrders: () => saveFile("views.py", orderViewsTpl()),
51
- makeModelOrders: () => saveFile("models.py", modelOrdersTpl()),
52
- makeModelUser: () => saveFile("models.py", modelUserTpl()),
53
- makeSettings: () => saveFile("settings.py", coreSettingsTpl()),
54
- makeAuthUrls: () => saveFile("urls.py", authUrlsTpl()),
55
- makeOrdersUrls: () => saveFile("urls.py", orderUrlsTpl()),
56
- makeUserForm: () => saveFile("forms.py", userFormTpl()),
57
- makeOrderForm: () => saveFile("forms.py", orderFormTpl()),
58
- makeLoginPage: () => saveFile("login.html", loginPageTpl()),
59
- makeAdmin: () => saveFile("admin.py", adminTpl()),
60
- makeCoreUrls: () => saveFile("urls.py", coreUrlsTpl()),
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
- fields = ['course','date','payment']
12
+ # Выбираем только те поля, которые заполняет пользователь
13
+ fields = ['course', 'date', 'payment']
14
+
9
15
  widgets = {
10
- 'date' : forms.DateInput(attrs={'type':'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 accounts.models import CustomUser
5
-
3
+ from .models import CustomUser
6
4
 
7
5
  class CustomForm(UserCreationForm):
8
- class Meta():
6
+ """
7
+ Форма регистрации на базе CustomUser.
8
+ ПОДСКАЗКА: Пароли (password1, password2) добавляются автоматически
9
+ самим классом UserCreationForm, их НЕ НАДО писать в fields.
10
+ """
11
+ class Meta(UserCreationForm.Meta):
9
12
  model = CustomUser
10
- fields = ('username','email','full_name','phone','password1','password2')
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
- # Create your models here.
3
+
5
4
  class Orders(models.Model):
5
+ """
6
+ Универсальная модель заявки.
7
+ ПОДСКАЗКА: Если в вашем варианте 'Бронирование отелей',
8
+ замените COURSE на ROOM_TYPES, а PAYMENT на что-то другое.
9
+ """
6
10
 
7
- COURSE = [
8
-
9
- ('algo','Алгоритмы'),
10
- ('design','Дизайн'),
11
- ('bd','Базы данных'),
11
+ # Списки выбора (choices)
12
+ # ПОДСКАЗКА: Замените кортежи ниже на те, что указаны в вашем билете
13
+ CATEGORY_CHOICES = [
14
+ ('type_1', 'Вариант 1'),
15
+ ('type_2', 'Вариант 2'),
16
+ ('type_3', 'Вариант 3'),
12
17
  ]
13
18
 
14
-
15
- STATUS = [
16
- ('new','новая'),
17
- ('in_progress', процессе обучения'),
18
- ('completed','завершена'),
19
+ STATUS_CHOICES = [
20
+ ('new', 'Новая'),
21
+ ('in_progress', 'В обработке'),
22
+ ('completed', 'Завершена'),
23
+ ('rejected', 'Отклонена'), # Добавил для гибкости
19
24
  ]
20
25
 
21
-
22
- PAYMENT = [
23
- ('cash','наличкой'),
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
- user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
29
- course = models.CharField(max_length=120,choices=COURSE,verbose_name='Выберите курс')
30
- payment = models.CharField(max_length=120,choices=PAYMENT,verbose_name='Выберите способ оплаты')
45
+ payment = models.CharField(
46
+ max_length=120,
47
+ choices=PAYMENT_CHOICES,
48
+ verbose_name='Способ оплаты'
49
+ )
31
50
 
32
- status = models.CharField(max_length=120,default='new',choices=STATUS,verbose_name='Статус заявки')
51
+ status = models.CharField(
52
+ max_length=120,
53
+ default='new',
54
+ choices=STATUS_CHOICES,
55
+ verbose_name='Статус'
56
+ )
33
57
 
34
- date = models.DateField('дата')
58
+ # Поле даты (часто требуют для записи)
59
+ date = models.DateField(verbose_name='Дата исполнения/начала')
35
60
 
36
- review = models.TextField(blank=True)
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
  `;