django-modules-forms 1.0.0

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.
@@ -0,0 +1,130 @@
1
+ from django import forms
2
+ from django.contrib.auth.forms import UserCreationForm
3
+ from .models import CustomUser, Application, Review
4
+ import re
5
+
6
+ class CustomUserRegistrationForm(UserCreationForm):
7
+ def __init__(self, *args, **kwargs):
8
+ super().__init__(*args, **kwargs)
9
+
10
+ self.fields['password1'].help_text = 'Минимум 8 символов'
11
+ self.fields['password2'].help_text = 'Повторите пароль для подтверждения'
12
+ self.fields['password1'].label = 'Пароль'
13
+ self.fields['password2'].label = 'Подтверждение пароля'
14
+
15
+ self.fields['username'].help_text = 'Только латинские буквы и цифры, не менее 6 символов'
16
+ self.fields['fio'].help_text = 'Только кириллические символы и пробелы'
17
+ self.fields['phone'].help_text = 'Формат: +7(XXX)XXX-XX-XX'
18
+ self.fields['email'].help_text = 'Введите действующий email адрес'
19
+
20
+ self.fields['password1'].error_messages = {
21
+ 'required': 'Обязательное поле',
22
+ }
23
+ self.fields['password2'].error_messages = {
24
+ 'required': 'Обязательное поле',
25
+ }
26
+
27
+ def clean_username(self):
28
+ username = self.cleaned_data['username']
29
+ if not re.match(r'^[a-zA-Z0-9]{6,}$', username):
30
+ raise forms.ValidationError('Логин должен содержать только латинские буквы и цифры, минимум 6 символов')
31
+ if CustomUser.objects.filter(username=username).exists():
32
+ raise forms.ValidationError('Пользователь с таким логином уже существует')
33
+ return username
34
+
35
+ def clean_fio(self):
36
+ fio = self.cleaned_data['fio']
37
+ if not re.match(r'^[а-яА-ЯёЁ\s]+$', fio):
38
+ raise forms.ValidationError('ФИО должно содержать только кириллические символы и пробелы')
39
+ return fio
40
+
41
+ def clean_phone(self):
42
+ phone = self.cleaned_data['phone']
43
+ if not re.match(r'^\+\d{1}\(\d{3}\)\d{3}-\d{2}-\d{2}$', phone):
44
+ raise forms.ValidationError('Телефон должен быть в формате: +7(XXX)XXX-XX-XX')
45
+ if CustomUser.objects.filter(phone=phone).exists():
46
+ raise forms.ValidationError('Пользователь с таким телефоном уже существует')
47
+ return phone
48
+
49
+ def clean_email(self):
50
+ email = self.cleaned_data['email']
51
+ if CustomUser.objects.filter(email=email).exists():
52
+ raise forms.ValidationError('Пользователь с таким email уже существует')
53
+ return email
54
+
55
+ def clean(self):
56
+ cleaned_data = super().clean()
57
+ password1 = cleaned_data.get("password1")
58
+ password2 = cleaned_data.get("password2")
59
+ if password1 and password2 and password1 != password2:
60
+ raise forms.ValidationError({
61
+ 'password2': 'Пароли не совпадают'
62
+ })
63
+
64
+ class Meta:
65
+ model = CustomUser
66
+ fields = ['username', 'fio', 'phone', 'email', 'password1', 'password2']
67
+ widgets = {
68
+ 'username': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Введите логин'}),
69
+ 'fio': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Введите ФИО'}),
70
+ 'phone': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '+7(XXX)XXX-XX-XX', 'id': 'phone-input'}),
71
+ 'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Введите email'}),
72
+ 'password1': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Введите пароль'}),
73
+ 'password2': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Повторите пароль'}),
74
+ }
75
+ labels = {
76
+ 'username': 'Логин',
77
+ 'fio': 'ФИО',
78
+ 'phone': 'Телефон',
79
+ 'email': 'Email',
80
+ 'password1': 'Пароль',
81
+ 'password2': 'Подтверждение пароля',
82
+ }
83
+ error_messages = {
84
+ 'username': {'required': 'Обязательное поле'},
85
+ 'fio': {'required': 'Обязательное поле'},
86
+ 'phone': {'required': 'Обязательное поле'},
87
+ 'email': {'required': 'Обязательное поле', 'invalid': 'Введите корректный email адрес'},
88
+ }
89
+
90
+ class ApplicationForm(forms.ModelForm):
91
+ def __init__(self, *args, **kwargs):
92
+ super().__init__(*args, **kwargs)
93
+ self.fields['desired_start_date'].help_text = 'Формат: ДД.ММ.ГГГГ'
94
+ self.fields['course'].help_text = 'Введите название курса'
95
+
96
+ class Meta:
97
+ model = Application
98
+ fields = ['course', 'desired_start_date', 'payment_method']
99
+ widgets = {
100
+ 'course': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Введите название курса'}),
101
+ 'desired_start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
102
+ 'payment_method': forms.Select(attrs={'class': 'form-control'}),
103
+ }
104
+ labels = {
105
+ 'course': 'Наименование курса',
106
+ 'desired_start_date': 'Желаемая дата начала обучения',
107
+ 'payment_method': 'Способ оплаты',
108
+ }
109
+ error_messages = {
110
+ 'course': {'required': 'Обязательное поле'},
111
+ 'desired_start_date': {'required': 'Обязательное поле'},
112
+ 'payment_method': {'required': 'Обязательное поле'},
113
+ }
114
+
115
+ class ReviewForm(forms.ModelForm):
116
+ class Meta:
117
+ model = Review
118
+ fields = ['rating', 'text']
119
+ widgets = {
120
+ 'rating': forms.Select(attrs={'class': 'form-control'}),
121
+ 'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
122
+ }
123
+ labels = {
124
+ 'rating': 'Оценка',
125
+ 'text': 'Текст отзыва',
126
+ }
127
+ error_messages = {
128
+ 'rating': {'required': 'Обязательное поле'},
129
+ 'text': {'required': 'Обязательное поле'},
130
+ }
@@ -0,0 +1,77 @@
1
+ # Generated by Django 6.0.4 on 2026-04-13 17:24
2
+
3
+ import django.contrib.auth.models
4
+ import django.core.validators
5
+ import django.db.models.deletion
6
+ import django.utils.timezone
7
+ from django.conf import settings
8
+ from django.db import migrations, models
9
+
10
+
11
+ class Migration(migrations.Migration):
12
+
13
+ initial = True
14
+
15
+ dependencies = [
16
+ ('auth', '0012_alter_user_first_name_max_length'),
17
+ ]
18
+
19
+ operations = [
20
+ migrations.CreateModel(
21
+ name='CustomUser',
22
+ fields=[
23
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+ ('password', models.CharField(max_length=128, verbose_name='password')),
25
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
26
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
27
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
28
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
29
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
30
+ ('username', models.CharField(error_messages={'unique': 'Пользователь с таким логином уже существует.'}, max_length=150, unique=True, verbose_name='Логин')),
31
+ ('fio', models.CharField(max_length=255, validators=[django.core.validators.RegexValidator(message='ФИО должно содержать только кириллические символы и пробелы', regex='^[а-яА-ЯёЁ\\s]+$')], verbose_name='ФИО')),
32
+ ('phone', models.CharField(max_length=15, validators=[django.core.validators.RegexValidator(message='Телефон должен быть в формате: 8(XXX)XXX-XX-XX', regex='^8\\(\\d{3}\\)\\d{3}-\\d{2}-\\d{2}$')], verbose_name='Телефон')),
33
+ ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')),
34
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
35
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
36
+ ],
37
+ options={
38
+ 'verbose_name': 'Пользователь',
39
+ 'verbose_name_plural': 'Пользователи',
40
+ },
41
+ managers=[
42
+ ('objects', django.contrib.auth.models.UserManager()),
43
+ ],
44
+ ),
45
+ migrations.CreateModel(
46
+ name='Application',
47
+ fields=[
48
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
49
+ ('course', models.CharField(max_length=50, verbose_name='Курс')),
50
+ ('desired_start_date', models.DateField(verbose_name='Желаемая дата начала обучения')),
51
+ ('payment_method', models.CharField(choices=[('cash', 'Наличными'), ('transfer', 'Переводом по номеру телефона')], max_length=20, verbose_name='Способ оплаты')),
52
+ ('status', models.CharField(choices=[('new', 'Новая'), ('in_progress', 'Идет обучение'), ('completed', 'Обучение завершено')], default='new', max_length=20, verbose_name='Статус')),
53
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
54
+ ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
55
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
56
+ ],
57
+ options={
58
+ 'verbose_name': 'Заявка',
59
+ 'verbose_name_plural': 'Заявки',
60
+ 'ordering': ['-created_at'],
61
+ },
62
+ ),
63
+ migrations.CreateModel(
64
+ name='Review',
65
+ fields=[
66
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
67
+ ('text', models.TextField(verbose_name='Текст отзыва')),
68
+ ('rating', models.IntegerField(choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)], verbose_name='Оценка')),
69
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
70
+ ('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='review', to='main.application', verbose_name='Заявка')),
71
+ ],
72
+ options={
73
+ 'verbose_name': 'Отзыв',
74
+ 'verbose_name_plural': 'Отзывы',
75
+ },
76
+ ),
77
+ ]
@@ -0,0 +1,19 @@
1
+ # Generated by Django 6.0.4 on 2026-04-13 17:31
2
+
3
+ import django.core.validators
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('main', '0001_initial'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='customuser',
16
+ name='phone',
17
+ field=models.CharField(max_length=16, validators=[django.core.validators.RegexValidator(message='Телефон должен быть в формате: +7(XXX)XXX-XX-XX', regex='^\\+\\d{1}\\(\\d{3}\\)\\d{3}-\\d{2}-\\d{2}$')], verbose_name='Телефон'),
18
+ ),
19
+ ]
File without changes
@@ -0,0 +1,101 @@
1
+ from django.db import models
2
+ from django.contrib.auth.models import AbstractUser
3
+ from django.core.validators import RegexValidator
4
+ import re
5
+
6
+ class CustomUser(AbstractUser):
7
+ username_validator = RegexValidator(
8
+ regex=r'^[a-zA-Z0-9]{6,}$',
9
+ message='Логин должен содержать только латинские буквы и цифры, минимум 6 символов'
10
+ )
11
+
12
+ fio_validator = RegexValidator(
13
+ regex=r'^[а-яА-ЯёЁ\s]+$',
14
+ message='ФИО должно содержать только кириллические символы и пробелы'
15
+ )
16
+
17
+ phone_validator = RegexValidator(
18
+ regex=r'^\+\d{1}\(\d{3}\)\d{3}-\d{2}-\d{2}$',
19
+ message='Телефон должен быть в формате: +7(XXX)XXX-XX-XX'
20
+ )
21
+
22
+ username = models.CharField(
23
+ max_length=150,
24
+ unique=True,
25
+ verbose_name='Логин',
26
+ error_messages={
27
+ 'unique': "Пользователь с таким логином уже существует.",
28
+ },
29
+ )
30
+ fio = models.CharField(
31
+ max_length=255,
32
+ validators=[fio_validator],
33
+ verbose_name='ФИО'
34
+ )
35
+ phone = models.CharField(
36
+ max_length=16,
37
+ validators=[phone_validator],
38
+ verbose_name='Телефон'
39
+ )
40
+ email = models.EmailField(unique=True, verbose_name='Email')
41
+
42
+ first_name = None
43
+ last_name = None
44
+
45
+ REQUIRED_FIELDS = ['email', 'fio', 'phone']
46
+
47
+ class Meta:
48
+ verbose_name = 'Пользователь'
49
+ verbose_name_plural = 'Пользователи'
50
+
51
+ def __str__(self):
52
+ return f"{self.fio} ({self.username})"
53
+
54
+ class Application(models.Model):
55
+ PAYMENT_METHOD_CHOICES = [
56
+ ('cash', 'Наличными'),
57
+ ('transfer', 'Переводом по номеру телефона'),
58
+ ]
59
+
60
+ STATUS_CHOICES = [
61
+ ('new', 'Новая'),
62
+ ('in_progress', 'Идет обучение'),
63
+ ('completed', 'Обучение завершено'),
64
+ ]
65
+
66
+ user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='Пользователь')
67
+ course = models.CharField(max_length=50, verbose_name='Курс')
68
+ desired_start_date = models.DateField(verbose_name='Желаемая дата начала обучения')
69
+ payment_method = models.CharField(max_length=20, choices=PAYMENT_METHOD_CHOICES, verbose_name='Способ оплаты')
70
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='new', verbose_name='Статус')
71
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
72
+ updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата обновления')
73
+
74
+ class Meta:
75
+ verbose_name = 'Заявка'
76
+ verbose_name_plural = 'Заявки'
77
+ ordering = ['-created_at']
78
+
79
+ def __str__(self):
80
+ return f"Заявка {self.id} - {self.user.fio} - {self.course}"
81
+
82
+ class Review(models.Model):
83
+ application = models.OneToOneField(
84
+ Application,
85
+ on_delete=models.CASCADE,
86
+ verbose_name='Заявка',
87
+ related_name='review'
88
+ )
89
+ text = models.TextField(verbose_name='Текст отзыва')
90
+ rating = models.IntegerField(
91
+ choices=[(i, i) for i in range(1, 6)],
92
+ verbose_name='Оценка'
93
+ )
94
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
95
+
96
+ class Meta:
97
+ verbose_name = 'Отзыв'
98
+ verbose_name_plural = 'Отзывы'
99
+
100
+ def __str__(self):
101
+ return f"Отзыв на заявку {self.application.id}"
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,84 @@
1
+ from django.shortcuts import render, redirect, get_object_or_404
2
+ from django.contrib.auth import login, logout, authenticate
3
+ from django.contrib.auth.decorators import login_required
4
+ from django.contrib import messages
5
+ from django.http import HttpResponseForbidden
6
+ from .models import Application, Review
7
+ from .forms import CustomUserRegistrationForm, ApplicationForm, ReviewForm
8
+
9
+ def home(request):
10
+ return render(request, 'home.html')
11
+
12
+ def register(request):
13
+ if request.method == 'POST':
14
+ form = CustomUserRegistrationForm(request.POST)
15
+ if form.is_valid():
16
+ user = form.save()
17
+ login(request, user)
18
+ messages.success(request, 'Регистрация прошла успешно!')
19
+ return redirect('applications')
20
+ else:
21
+ form = CustomUserRegistrationForm()
22
+ return render(request, 'register.html', {'form': form})
23
+
24
+ def custom_login(request):
25
+ if request.method == 'POST':
26
+ username = request.POST['username']
27
+ password = request.POST['password']
28
+ user = authenticate(request, username=username, password=password)
29
+ if user is not None:
30
+ login(request, user)
31
+ messages.success(request, f'Добро пожаловать, {user.fio}!')
32
+ return redirect('applications')
33
+ else:
34
+ messages.error(request, 'Неверный логин или пароль')
35
+ return render(request, 'login.html')
36
+
37
+ @login_required
38
+ def applications(request):
39
+ user_applications = Application.objects.filter(user=request.user).order_by('-created_at')
40
+ return render(request, 'applications.html', {'applications': user_applications})
41
+
42
+ @login_required
43
+ def create_application(request):
44
+ if request.method == 'POST':
45
+ form = ApplicationForm(request.POST)
46
+ if form.is_valid():
47
+ application = form.save(commit=False)
48
+ application.user = request.user
49
+ application.save()
50
+ messages.success(request, 'Заявка успешно создана и отправлена на рассмотрение!')
51
+ return redirect('applications')
52
+ else:
53
+ form = ApplicationForm()
54
+ return render(request, 'create_application.html', {'form': form})
55
+
56
+ @login_required
57
+ def add_review(request, application_id):
58
+ application = get_object_or_404(Application, id=application_id, user=request.user)
59
+ if application.status != 'completed':
60
+ return HttpResponseForbidden("Отзыв можно оставить только для завершенных курсов")
61
+ if hasattr(application, 'review'):
62
+ messages.error(request, 'Отзыв уже оставлен для этой заявки')
63
+ return redirect('applications')
64
+ if request.method == 'POST':
65
+ form = ReviewForm(request.POST)
66
+ if form.is_valid():
67
+ review = form.save(commit=False)
68
+ review.application = application
69
+ review.save()
70
+ messages.success(request, 'Отзыв успешно добавлен!')
71
+ return redirect('applications')
72
+ else:
73
+ form = ReviewForm()
74
+ return render(request, 'add_review.html', {'form': form, 'application': application})
75
+
76
+ def custom_logout(request):
77
+ logout(request)
78
+ return redirect('home')
79
+
80
+ def robots_txt(request):
81
+ return render(request, 'robots.txt', content_type='text/plain')
82
+
83
+ def sitemap_xml(request):
84
+ return render(request, 'sitemap.xml', content_type='application/xml')
@@ -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', 'kurochki.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()
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "django-modules-forms",
3
+ "version": "1.0.0",
4
+ "description": "Django проект с неоновым дизайном",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "django-modules-forms": "index.js"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node postinstall.js"
11
+ },
12
+ "keywords": ["django", "admin", "neon"],
13
+ "author": "NeAlexS",
14
+ "license": "MIT"
15
+ }