feat: регистрация продавца (FR-01) + палитра B (зелёный/янтарь)

This commit is contained in:
apuc committed 2026-09-23 18:28:45 +03:00
1 parent 43ac518fc2
commit c681c724a5
12 files changed
+335 -8

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
from datetime import date
from django import forms
from django.contrib.auth.forms import UserCreationForm
from apps.accounts.models import User
from apps.catalog.models import LegalStatus
MIN_AGE = 14
MAX_AGE = 35
class SellerStep1Form(UserCreationForm):
"""Шаг 1 — учётная запись и личные данные (ФИО, дата рождения, регион)."""
full_name = forms.CharField(label='ФИО', max_length=255)
birth_date = forms.DateField(
label='Дата рождения',
widget=forms.DateInput(attrs={'type': 'date'}),
)
region = forms.CharField(label='Регион', max_length=120)
class Meta:
model = User
fields = ('username', 'email', 'full_name', 'birth_date', 'region')
def clean_birth_date(self):
birth_date = self.cleaned_data['birth_date']
today = date.today()
age = (
today.year
- birth_date.year
- ((today.month, today.day) < (birth_date.month, birth_date.day))
)
if age < MIN_AGE:
raise forms.ValidationError(
f'Минимальный возраст участника — {MIN_AGE} лет.',
)
if age > MAX_AGE:
raise forms.ValidationError(
f'Максимальный возраст участника — {MAX_AGE} лет.',
)
return birth_date
class SellerStep2Form(forms.Form):
"""Шаг 2 — данные бренда (название, статус, ИНН, город, соцсети, деятельность)."""
brand_name = forms.CharField(label='Название бренда', max_length=200)
legal_status = forms.ChoiceField(
label='Правовой статус', choices=LegalStatus.choices,
)
inn = forms.CharField(label='ИНН', max_length=12, required=False)
city = forms.CharField(label='Город', max_length=120, required=False)
socials = forms.CharField(
label='Ссылки на соцсети (по одной в строке)',
widget=forms.Textarea(attrs={'rows': 3}), required=False,
)
story = forms.CharField(
label='Описание деятельности',
widget=forms.Textarea(attrs={'rows': 4}), max_length=3000, required=False,
)
def clean_inn(self):
inn = self.cleaned_data.get('inn', '').strip()
if inn and not inn.isdigit():
raise forms.ValidationError('ИНН должен содержать только цифры.')
return inn
class SellerStep3Form(forms.Form):
"""Шаг 3 — подтверждения и согласие на обработку ПДн."""
production_photo = forms.ImageField(
label='Фото производства', required=False,
)
statement_doc = forms.FileField(
label='Выписка/справка (PDF, JPG, PNG)', required=False,
)
pd_consent = forms.BooleanField(
label='Согласен(на) на обработку персональных данных',
required=True,
)
@@ -0,0 +1,18 @@
# Generated by Django 5.1.15 on 2026-09-23 15:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='pd_consent',
field=models.BooleanField(default=False, verbose_name='Согласие на обработку ПДн'),
),
]
+3
View File
@@ -22,6 +22,9 @@ class User(AbstractUser):
phone = models.CharField('Телефон', max_length=32, blank=True)
birth_date = models.DateField('Дата рождения', null=True, blank=True)
region = models.CharField('Регион', max_length=120, blank=True)
pd_consent = models.BooleanField(
'Согласие на обработку ПДн', default=False,
)
class Meta:
verbose_name = 'Пользователь'
+10
View File
@@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = 'accounts'
urlpatterns = [
path('register/seller/', views.seller_register, name='seller_register'),
path('register/seller/done/', views.seller_register_done, name='seller_register_done'),
]
+113 -2
View File
@@ -1,3 +1,114 @@
from django.shortcuts import render
from datetime import date
# Create your views here.
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import redirect, render
from django.urls import reverse
from apps.accounts.forms import SellerStep1Form, SellerStep2Form, SellerStep3Form
from apps.accounts.models import Role, User
from apps.catalog.models import Brand, VerificationStatus
from apps.moderation.models import AuditLog, ModerationRequest
SESSION_KEY = 'seller_registration'
def _get_session_data(request):
return request.session.get(SESSION_KEY, {})
def seller_register(request):
"""Многошаговая регистрация продавца (FR-01)."""
step = int(request.GET.get('step', 1))
data = _get_session_data(request)
if step == 1:
form = SellerStep1Form(request.POST or None)
if request.method == 'POST' and form.is_valid():
cleaned = form.cleaned_data
# date нельзя сериализовать в сессию (JSON) — храним ISO-строку.
cleaned['birth_date'] = cleaned['birth_date'].isoformat()
data['step1'] = cleaned
request.session[SESSION_KEY] = data
return redirect(reverse('accounts:seller_register') + '?step=2')
return render(request, 'accounts/seller_register.html', {
'form': form, 'step': 1, 'total_steps': 3,
})
if step == 2:
if 'step1' not in data:
return redirect(reverse('accounts:seller_register'))
form = SellerStep2Form(request.POST or None)
if request.method == 'POST' and form.is_valid():
data['step2'] = form.cleaned_data
request.session[SESSION_KEY] = data
return redirect(reverse('accounts:seller_register') + '?step=3')
return render(request, 'accounts/seller_register.html', {
'form': form, 'step': 2, 'total_steps': 3,
})
if step == 3:
if 'step1' not in data or 'step2' not in data:
return redirect(reverse('accounts:seller_register'))
form = SellerStep3Form(request.POST or None, request.FILES or None)
if request.method == 'POST' and form.is_valid():
_create_seller(request, data, form.cleaned_data)
request.session.pop(SESSION_KEY, None)
return redirect(reverse('accounts:seller_register_done'))
return render(request, 'accounts/seller_register.html', {
'form': form, 'step': 3, 'total_steps': 3,
})
return redirect(reverse('accounts:seller_register'))
def _create_seller(request, data, step3):
s1 = data['step1']
s2 = data['step2']
user = User.objects.create_user(
username=s1['username'],
email=s1.get('email', ''),
password=s1['password1'],
role=Role.SELLER,
full_name=s1['full_name'],
birth_date=date.fromisoformat(s1['birth_date']),
region=s1['region'],
pd_consent=step3['pd_consent'],
)
socials = [
line.strip()
for line in s2.get('socials', '').splitlines()
if line.strip()
]
brand = Brand.objects.create(
user=user,
name=s2['brand_name'],
legal_status=s2['legal_status'],
inn=s2.get('inn', ''),
city=s2.get('city', ''),
region=s1['region'],
socials=socials,
story=s2.get('story', ''),
production_photo=step3.get('production_photo'),
statement_doc=step3.get('statement_doc'),
verification_status=VerificationStatus.PENDING,
)
ModerationRequest.objects.create(
content_type=ContentType.objects.get_for_model(Brand),
object_id=brand.pk,
decision='pending',
)
AuditLog.objects.create(
user=user,
action='seller_registered',
object_repr=f'Бренд «{brand.name}»',
)
return user, brand
def seller_register_done(request):
return render(request, 'accounts/seller_register_done.html')