fix: исправлены ошибки в системе уведомлений
- Добавлен эндпоинт удаления комментариев к заявкам (DELETE /api/v1/requests/{pk}/comments/{comment_id}/)
- Исправлены references на переменную в сигналах комментариев (comment -> instance)
- Добавлена обработка статуса cancelled для заявок в notification_type_map
- Обновлены процессоры уведомлений в task_base.py (правильные поля моделей)
- Создан скрипт генерации тестовых пользователей (scripts/create_test_users.py)
This commit is contained in:
parent
92113521e5
commit
2ff0eaa538
@ -367,6 +367,36 @@ class RequestViewSet(viewsets.ModelViewSet):
|
||||
logger.info(f"[RequestViewSet.comments] POST: comment created id={saved.id}")
|
||||
return Response(ser.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@action(detail=True, methods=["delete"], url_path="comments/(?P<comment_id>[^/.]+)")
|
||||
def delete_comment(self, request: DRFRequest, pk: int | str | None = None, comment_id: int | str | None = None) -> Response:
|
||||
"""Удаление комментария.
|
||||
|
||||
DELETE /api/v1/requests/{pk}/comments/{comment_id}/
|
||||
|
||||
Права: только автор комментария или staff.
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
req_obj = self.get_object()
|
||||
|
||||
try:
|
||||
comment = RequestComment.objects.get(id=comment_id, request=req_obj)
|
||||
except RequestComment.DoesNotExist:
|
||||
return Response({"detail": "Comment not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Проверка прав: только автор или staff
|
||||
if not (request.user.is_staff or comment.author == request.user):
|
||||
return Response(
|
||||
{"detail": "You don't have permission to delete this comment"},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
logger.info(f"[RequestViewSet.delete_comment] user={request.user.id} deleting comment_id={comment_id}")
|
||||
comment.delete()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
# ---------- Экшены статусов (только бизнес-валидация) ----------
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
|
||||
@ -360,31 +360,36 @@ class EventNotificationProcessor(BaseNotificationProcessor):
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
event = CalendarEvent.objects.select_related('creator').get(id=event_id)
|
||||
event = CalendarEvent.objects.select_related('created_by', 'employee', 'department').get(id=event_id)
|
||||
|
||||
# Собираем всех получателей
|
||||
recipients = set()
|
||||
|
||||
# Участники события
|
||||
for participant in event.participants.all():
|
||||
recipients.add(participant)
|
||||
# Если событие отдела - уведомляем всех сотрудников отдела
|
||||
if event.department:
|
||||
recipients.update(self.get_department_employees([event.department.id]))
|
||||
|
||||
# Департаменты (если есть)
|
||||
if event.departments.exists():
|
||||
recipients.update(self.get_department_employees(event.departments.all()))
|
||||
# Если личное событие сотрудника - уведомляем только его
|
||||
elif event.employee:
|
||||
# Проверяем что сотрудник активен
|
||||
if event.employee.is_active and event.employee.email_verified:
|
||||
recipients.add(event.employee)
|
||||
|
||||
# Исключаем создателя и неактивных
|
||||
recipients = self.get_active_employees(recipients)
|
||||
if event.creator:
|
||||
recipients.discard(event.creator)
|
||||
# Если глобальное событие (нет department и employee) - уведомляем всех активных
|
||||
else:
|
||||
recipients = set(User.objects.filter(is_active=True, email_verified=True))
|
||||
|
||||
# Исключаем создателя
|
||||
if event.created_by:
|
||||
recipients.discard(event.created_by)
|
||||
|
||||
if not recipients:
|
||||
return self.log_result(event_id, 0)
|
||||
return self.log_result('event', event_id, 'skipped', 0)
|
||||
|
||||
# Отправляем уведомления
|
||||
notifications_sent = self.send_notifications(
|
||||
recipients=recipients,
|
||||
notification_type='calendar_event_created',
|
||||
notification_type='event_created',
|
||||
title=f'Новое событие: {event.title}',
|
||||
message=truncate_text(event.description or '', 150),
|
||||
content_object=event,
|
||||
@ -397,10 +402,10 @@ class EventNotificationProcessor(BaseNotificationProcessor):
|
||||
}
|
||||
)
|
||||
|
||||
return self.log_result(event_id, notifications_sent)
|
||||
return self.log_result('event', event_id, 'created', notifications_sent)
|
||||
|
||||
except Exception as exc:
|
||||
return self.handle_exception(exc, f"event {event_id}")
|
||||
self.handle_exception(exc)
|
||||
|
||||
|
||||
class PostNotificationProcessor(BaseNotificationProcessor):
|
||||
@ -410,55 +415,47 @@ class PostNotificationProcessor(BaseNotificationProcessor):
|
||||
"""Обработка уведомлений для поста"""
|
||||
try:
|
||||
from feed.models import Post
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
post = Post.objects.select_related('author').get(id=post_id)
|
||||
|
||||
if post.is_deleted:
|
||||
return {"status": "skipped", "reason": "deleted"}
|
||||
User = get_user_model()
|
||||
post = Post.objects.select_related('author', 'department').get(id=post_id)
|
||||
|
||||
# Собираем получателей
|
||||
recipients = set()
|
||||
|
||||
# Департаменты
|
||||
if post.target_departments.exists():
|
||||
recipients.update(self.get_department_employees(post.target_departments.all()))
|
||||
# Если пост для конкретного отдела - уведомляем сотрудников отдела
|
||||
if post.department:
|
||||
recipients.update(self.get_department_employees([post.department.id]))
|
||||
else:
|
||||
# Иначе уведомляем всех активных сотрудников
|
||||
recipients = set(User.objects.filter(is_active=True, email_verified=True))
|
||||
|
||||
# Конкретные пользователи
|
||||
for user in post.target_users.all():
|
||||
recipients.add(user)
|
||||
|
||||
# Если нет конкретных получателей - всем активным
|
||||
if not recipients:
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
recipients = set(User.objects.filter(is_active=True))
|
||||
|
||||
# Исключаем автора и неактивных
|
||||
recipients = self.get_active_employees(recipients)
|
||||
# Исключаем автора
|
||||
if post.author:
|
||||
recipients.discard(post.author)
|
||||
|
||||
if not recipients:
|
||||
return self.log_result(post_id, 0)
|
||||
return self.log_result('post', post_id, 'skipped', 0)
|
||||
|
||||
# Отправляем уведомления
|
||||
notifications_sent = self.send_notifications(
|
||||
recipients=recipients,
|
||||
notification_type='feed_new_post',
|
||||
title=f'Новая публикация от {post.author.get_full_name() if post.author else "Система"}',
|
||||
message=truncate_text(post.content, 200),
|
||||
title=f'{post.title}',
|
||||
message=truncate_text(post.body, 200),
|
||||
content_object=post,
|
||||
action_url=f'/feed/?post={post.id}',
|
||||
metadata={
|
||||
'post_id': post.id,
|
||||
'author_id': post.author.id if post.author else None,
|
||||
'post_type': post.type,
|
||||
}
|
||||
)
|
||||
|
||||
return self.log_result(post_id, notifications_sent)
|
||||
return self.log_result('post', post_id, 'created', notifications_sent)
|
||||
|
||||
except Exception as exc:
|
||||
return self.handle_exception(exc, f"post {post_id}")
|
||||
self.handle_exception(exc)
|
||||
|
||||
|
||||
class RequestNotificationProcessor(BaseNotificationProcessor):
|
||||
@ -475,76 +472,83 @@ class RequestNotificationProcessor(BaseNotificationProcessor):
|
||||
try:
|
||||
from requests_app.models import Request
|
||||
|
||||
request_obj = Request.objects.select_related('requester', 'handler').get(id=request_id)
|
||||
request_obj = Request.objects.select_related('employee', 'approver').prefetch_related('recipients', 'cc_users').get(id=request_id)
|
||||
|
||||
# Собираем получателей в зависимости от типа уведомления
|
||||
recipients = set()
|
||||
|
||||
if notification_type == 'created':
|
||||
# Уведомляем исполнителя и копии
|
||||
if request_obj.handler:
|
||||
recipients.add(request_obj.handler)
|
||||
# Уведомляем согласующего и получателей
|
||||
if request_obj.approver:
|
||||
recipients.add(request_obj.approver)
|
||||
|
||||
for user in request_obj.recipients.all():
|
||||
recipients.add(user)
|
||||
if user.is_active and getattr(user, 'email_verified', True):
|
||||
recipients.add(user)
|
||||
|
||||
for user in request_obj.cc_users.all():
|
||||
recipients.add(user)
|
||||
if user.is_active and getattr(user, 'email_verified', True):
|
||||
recipients.add(user)
|
||||
|
||||
elif notification_type == 'status_changed':
|
||||
# Уведомляем создателя
|
||||
if request_obj.requester:
|
||||
recipients.add(request_obj.requester)
|
||||
# Уведомляем создателя заявки
|
||||
if request_obj.employee:
|
||||
if request_obj.employee.is_active and getattr(request_obj.employee, 'email_verified', True):
|
||||
recipients.add(request_obj.employee)
|
||||
|
||||
elif notification_type == 'comment_added':
|
||||
# Уведомляем всех участников кроме автора комментария
|
||||
if request_obj.requester:
|
||||
recipients.add(request_obj.requester)
|
||||
if request_obj.handler:
|
||||
recipients.add(request_obj.handler)
|
||||
# Уведомляем всех участников
|
||||
if request_obj.employee:
|
||||
if request_obj.employee.is_active and getattr(request_obj.employee, 'email_verified', True):
|
||||
recipients.add(request_obj.employee)
|
||||
|
||||
if request_obj.approver:
|
||||
if request_obj.approver.is_active and getattr(request_obj.approver, 'email_verified', True):
|
||||
recipients.add(request_obj.approver)
|
||||
|
||||
for user in request_obj.recipients.all():
|
||||
recipients.add(user)
|
||||
if user.is_active and getattr(user, 'email_verified', True):
|
||||
recipients.add(user)
|
||||
|
||||
for user in request_obj.cc_users.all():
|
||||
recipients.add(user)
|
||||
|
||||
# Исключаем неактивных
|
||||
recipients = self.get_active_employees(recipients)
|
||||
if user.is_active and getattr(user, 'email_verified', True):
|
||||
recipients.add(user)
|
||||
|
||||
if not recipients:
|
||||
return self.log_result(request_id, 0)
|
||||
return self.log_result('request', request_id, notification_type, 0)
|
||||
|
||||
# Формируем заголовок
|
||||
title_map = {
|
||||
'created': f'Новая заявка #{request_obj.id}: {request_obj.subject}',
|
||||
'created': f'Новая заявка #{request_obj.id}: {request_obj.title or request_obj.get_type_display()}',
|
||||
'status_changed': f'Изменён статус заявки #{request_obj.id}',
|
||||
'comment_added': f'Новый комментарий к заявке #{request_obj.id}',
|
||||
}
|
||||
|
||||
type_code_map = {
|
||||
'created': 'request_created',
|
||||
'created': 'request_new',
|
||||
'status_changed': 'request_status_changed',
|
||||
'comment_added': 'request_comment_added',
|
||||
'comment_added': 'request_comment',
|
||||
}
|
||||
|
||||
# Отправляем уведомления
|
||||
notifications_sent = self.send_notifications(
|
||||
recipients=recipients,
|
||||
notification_type=type_code_map.get(notification_type, 'request_created'),
|
||||
notification_type=type_code_map.get(notification_type, 'request_new'),
|
||||
title=title_map.get(notification_type, f'Обновление заявки #{request_obj.id}'),
|
||||
message=truncate_text(request_obj.description, 150),
|
||||
message=truncate_text(request_obj.comment or '', 150),
|
||||
content_object=request_obj,
|
||||
action_url=f'/requests/{request_obj.id}/',
|
||||
metadata={
|
||||
'request_id': request_obj.id,
|
||||
'subject': request_obj.subject,
|
||||
'request_type': request_obj.type,
|
||||
'status': request_obj.status,
|
||||
}
|
||||
)
|
||||
|
||||
return self.log_result(request_id, notifications_sent)
|
||||
return self.log_result('request', request_id, notification_type, notifications_sent)
|
||||
|
||||
except Exception as exc:
|
||||
return self.handle_exception(exc, f"request {request_id}")
|
||||
self.handle_exception(exc)
|
||||
|
||||
|
||||
class DocumentNotificationProcessor(BaseNotificationProcessor):
|
||||
@ -554,35 +558,39 @@ class DocumentNotificationProcessor(BaseNotificationProcessor):
|
||||
"""Обработка уведомлений для документа"""
|
||||
try:
|
||||
from documents.models import Document
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
document = Document.objects.select_related('author').get(id=document_id)
|
||||
|
||||
if document.is_deleted:
|
||||
return {"status": "skipped", "reason": "deleted"}
|
||||
User = get_user_model()
|
||||
document = Document.objects.select_related('uploaded_by').prefetch_related('departments', 'recipients').get(id=document_id)
|
||||
|
||||
# Собираем получателей
|
||||
recipients = set()
|
||||
|
||||
# Департаменты
|
||||
if document.departments.exists():
|
||||
recipients.update(self.get_department_employees(document.departments.all()))
|
||||
# Если разослать всем
|
||||
if document.sent_to_all:
|
||||
recipients = set(User.objects.filter(is_active=True, email_verified=True))
|
||||
else:
|
||||
# Департаменты
|
||||
if document.departments.exists():
|
||||
dept_ids = [d.id for d in document.departments.all()]
|
||||
recipients.update(self.get_department_employees(dept_ids))
|
||||
|
||||
# Конкретные получатели
|
||||
for user in document.recipients.all():
|
||||
if user.is_active and getattr(user, 'email_verified', True):
|
||||
recipients.add(user)
|
||||
|
||||
# Конкретные пользователи
|
||||
for user in document.readers.all():
|
||||
recipients.add(user)
|
||||
|
||||
# Исключаем автора и неактивных
|
||||
recipients = self.get_active_employees(recipients)
|
||||
if document.author:
|
||||
recipients.discard(document.author)
|
||||
# Исключаем автора
|
||||
if document.uploaded_by:
|
||||
recipients.discard(document.uploaded_by)
|
||||
|
||||
if not recipients:
|
||||
return self.log_result(document_id, 0)
|
||||
return self.log_result('document', document_id, 'skipped', 0)
|
||||
|
||||
# Отправляем уведомления
|
||||
notifications_sent = self.send_notifications(
|
||||
recipients=recipients,
|
||||
notification_type='document_created',
|
||||
notification_type='document_ready',
|
||||
title=f'Новый документ: {document.title}',
|
||||
message=truncate_text(document.description or '', 150),
|
||||
content_object=document,
|
||||
@ -590,11 +598,10 @@ class DocumentNotificationProcessor(BaseNotificationProcessor):
|
||||
metadata={
|
||||
'document_id': document.id,
|
||||
'document_title': document.title,
|
||||
'document_type': document.document_type,
|
||||
}
|
||||
)
|
||||
|
||||
return self.log_result(document_id, notifications_sent)
|
||||
return self.log_result('document', document_id, 'created', notifications_sent)
|
||||
|
||||
except Exception as exc:
|
||||
return self.handle_exception(exc, f"document {document_id}")
|
||||
self.handle_exception(exc)
|
||||
|
||||
@ -356,6 +356,8 @@ def process_request_notifications_task(
|
||||
'created': 'created',
|
||||
'approved': 'status_changed',
|
||||
'rejected': 'status_changed',
|
||||
'cancelled': 'status_changed',
|
||||
'updated': 'status_changed',
|
||||
'comment': 'comment_added',
|
||||
}
|
||||
return processor.process(request_id, notification_type_map.get(action, 'created'))
|
||||
|
||||
@ -67,7 +67,8 @@ def create_request_notifications(sender, instance, created, **kwargs):
|
||||
|
||||
# Определяем action по статусу
|
||||
action = 'approved' if new_status == 'approved' else \
|
||||
'rejected' if new_status == 'rejected' else 'updated'
|
||||
'rejected' if new_status == 'rejected' else \
|
||||
'cancelled' if new_status == 'cancelled' else 'updated'
|
||||
|
||||
def send_task():
|
||||
"""Отложенная отправка задачи после commit транзакции"""
|
||||
@ -215,8 +216,9 @@ def create_comment_notification(sender, instance, created, **kwargs):
|
||||
notify_request_comment(instance)
|
||||
|
||||
transaction.on_commit(send_task)
|
||||
author = comment.author
|
||||
author = instance.author
|
||||
recipients_set = set()
|
||||
request_obj = instance.request
|
||||
|
||||
# Автор заявки
|
||||
if request_obj.employee.id != author.id:
|
||||
@ -264,14 +266,14 @@ def create_comment_notification(sender, instance, created, **kwargs):
|
||||
title=f"💬 Новый комментарий к заявлению от {employee_name}",
|
||||
message=(
|
||||
f"{author_name} прокомментировал заявление "
|
||||
f'"{request_type}": {comment.text[:100]}'
|
||||
f'"{request_type}": {instance.text[:100]}'
|
||||
),
|
||||
content_object=request_obj,
|
||||
action_url=f"/requests/{request_obj.id}/",
|
||||
metadata={
|
||||
"request_id": request_obj.id,
|
||||
"request_type": request_obj.type,
|
||||
"comment_id": comment.id,
|
||||
"comment_id": instance.id,
|
||||
"author_id": author.id,
|
||||
},
|
||||
)
|
||||
|
||||
277
backend/scripts/create_test_users.py
Normal file
277
backend/scripts/create_test_users.py
Normal file
@ -0,0 +1,277 @@
|
||||
"""
|
||||
Скрипт для создания тестовых пользователей в корпоративном портале.
|
||||
Создает 100+ пользователей с разными ролями и отделами.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Добавляем путь к backend в sys.path
|
||||
backend_dir = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
import django
|
||||
from datetime import date, timedelta
|
||||
import random
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eusrr_backend.settings')
|
||||
django.setup()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from employees.models import Department, Position, EmployeeDepartment
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# Списки для генерации данных
|
||||
FIRST_NAMES = [
|
||||
'Александр', 'Дмитрий', 'Максим', 'Сергей', 'Андрей', 'Алексей', 'Артем', 'Илья',
|
||||
'Кирилл', 'Михаил', 'Иван', 'Даниил', 'Егор', 'Никита', 'Матвей', 'Роман',
|
||||
'Владислав', 'Ярослав', 'Тимофей', 'Владимир', 'Павел', 'Николай', 'Денис', 'Олег',
|
||||
'Анна', 'Мария', 'Елена', 'Ольга', 'Татьяна', 'Наталья', 'Екатерина', 'Ирина',
|
||||
'Юлия', 'Светлана', 'Людмила', 'Галина', 'Анастасия', 'Дарья', 'Виктория', 'Валерия',
|
||||
'Ксения', 'Полина', 'Алина', 'Вероника', 'София', 'Марина', 'Оксана', 'Алла'
|
||||
]
|
||||
|
||||
LAST_NAMES = [
|
||||
'Иванов', 'Петров', 'Сидоров', 'Смирнов', 'Кузнецов', 'Попов', 'Васильев', 'Соколов',
|
||||
'Михайлов', 'Новиков', 'Федоров', 'Морозов', 'Волков', 'Алексеев', 'Лебедев', 'Семенов',
|
||||
'Егоров', 'Павлов', 'Козлов', 'Степанов', 'Николаев', 'Орлов', 'Андреев', 'Макаров',
|
||||
'Никитин', 'Захаров', 'Зайцев', 'Соловьев', 'Борисов', 'Яковлев', 'Григорьев', 'Романов',
|
||||
'Воробьев', 'Сергеев', 'Фролов', 'Дмитриев', 'Ковалев', 'Белов', 'Комаров', 'Жуков',
|
||||
'Иванова', 'Петрова', 'Сидорова', 'Смирнова', 'Кузнецова', 'Попова', 'Васильева', 'Соколова'
|
||||
]
|
||||
|
||||
PATRONYMICS = [
|
||||
'Александрович', 'Дмитриевич', 'Максимович', 'Сергеевич', 'Андреевич', 'Алексеевич',
|
||||
'Артемович', 'Ильич', 'Кириллович', 'Михайлович', 'Иванович', 'Даниилович',
|
||||
'Александровна', 'Дмитриевна', 'Максимовна', 'Сергеевна', 'Андреевна', 'Алексеевна',
|
||||
'Артемовна', 'Ильинична', 'Кирилловна', 'Михайловна', 'Ивановна', 'Данииловна'
|
||||
]
|
||||
|
||||
DEPARTMENTS_DATA = [
|
||||
{'name': 'Разработка', 'code': 'DEV'},
|
||||
{'name': 'Тестирование', 'code': 'QA'},
|
||||
{'name': 'Продажи', 'code': 'SALES'},
|
||||
{'name': 'Маркетинг', 'code': 'MKT'},
|
||||
{'name': 'HR', 'code': 'HR'},
|
||||
{'name': 'Финансы', 'code': 'FIN'},
|
||||
{'name': 'Бухгалтерия', 'code': 'ACC'},
|
||||
{'name': 'Логистика', 'code': 'LOG'},
|
||||
{'name': 'Производство', 'code': 'PROD'},
|
||||
{'name': 'Закупки', 'code': 'PURCH'},
|
||||
]
|
||||
|
||||
POSITIONS_DATA = [
|
||||
'Младший специалист',
|
||||
'Специалист',
|
||||
'Старший специалист',
|
||||
'Ведущий специалист',
|
||||
'Менеджер',
|
||||
'Старший менеджер',
|
||||
'Руководитель группы',
|
||||
'Руководитель отдела',
|
||||
'Директор',
|
||||
'Стажер',
|
||||
]
|
||||
|
||||
|
||||
def create_departments():
|
||||
"""Создание отделов"""
|
||||
print('\n📁 Создание отделов...')
|
||||
departments = []
|
||||
for dept_data in DEPARTMENTS_DATA:
|
||||
dept, created = Department.objects.get_or_create(
|
||||
name=dept_data['name'],
|
||||
defaults={'description': f'Отдел {dept_data["name"]}'}
|
||||
)
|
||||
departments.append(dept)
|
||||
if created:
|
||||
print(f' ✅ Создан отдел: {dept.name}')
|
||||
else:
|
||||
print(f' ℹ️ Отдел существует: {dept.name}')
|
||||
return departments
|
||||
|
||||
|
||||
def create_positions():
|
||||
"""Создание должностей"""
|
||||
print('\n💼 Создание должностей...')
|
||||
positions = []
|
||||
for pos_name in POSITIONS_DATA:
|
||||
pos, created = Position.objects.get_or_create(
|
||||
name=pos_name
|
||||
)
|
||||
positions.append(pos)
|
||||
if created:
|
||||
print(f' ✅ Создана должность: {pos.name}')
|
||||
return positions
|
||||
|
||||
|
||||
def generate_email(first_name, last_name, counter):
|
||||
"""Генерация email"""
|
||||
first = first_name.lower()
|
||||
last = last_name.lower()
|
||||
|
||||
# Транслитерация
|
||||
translit = {
|
||||
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'e',
|
||||
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
|
||||
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
|
||||
'ф': 'f', 'х': 'h', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ъ': '',
|
||||
'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
|
||||
}
|
||||
|
||||
first_en = ''.join(translit.get(c, c) for c in first)
|
||||
last_en = ''.join(translit.get(c, c) for c in last)
|
||||
|
||||
return f'{first_en}.{last_en}{counter}@company.local'
|
||||
|
||||
|
||||
def generate_phone():
|
||||
"""Генерация номера телефона"""
|
||||
return f'+7{random.randint(900, 999)}{random.randint(1000000, 9999999)}'
|
||||
|
||||
|
||||
def generate_birthdate():
|
||||
"""Генерация даты рождения (25-65 лет)"""
|
||||
years_ago = random.randint(25, 65)
|
||||
days_offset = random.randint(0, 364)
|
||||
return date.today() - timedelta(days=years_ago * 365 + days_offset)
|
||||
|
||||
|
||||
def create_users(count=120, departments=None, positions=None):
|
||||
"""Создание пользователей"""
|
||||
print(f'\n👥 Создание {count} пользователей...')
|
||||
|
||||
if not departments:
|
||||
departments = list(Department.objects.all())
|
||||
if not positions:
|
||||
positions = list(Position.objects.all())
|
||||
|
||||
created_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for i in range(1, count + 1):
|
||||
first_name = random.choice(FIRST_NAMES)
|
||||
last_name = random.choice(LAST_NAMES)
|
||||
patronymic = random.choice(PATRONYMICS)
|
||||
|
||||
email = generate_email(first_name, last_name, i)
|
||||
|
||||
# Проверяем, существует ли пользователь
|
||||
if User.objects.filter(email=email).exists():
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
# Создаем пользователя
|
||||
user = User.objects.create_user(
|
||||
email=email,
|
||||
password='test123', # Простой пароль для тестирования
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
patronymic=patronymic,
|
||||
phone_number=generate_phone(),
|
||||
birth_date=generate_birthdate(),
|
||||
is_active=True,
|
||||
email_verified=True,
|
||||
is_staff=random.random() < 0.1, # 10% - staff
|
||||
send_activation_email=False
|
||||
)
|
||||
|
||||
# Назначаем должность
|
||||
position = random.choice(positions)
|
||||
user.position = position
|
||||
user.save()
|
||||
|
||||
# Назначаем отдел(ы)
|
||||
# 70% - один отдел, 25% - два отдела, 5% - три отдела
|
||||
dept_count = random.choices([1, 2, 3], weights=[70, 25, 5])[0]
|
||||
user_departments = random.sample(departments, min(dept_count, len(departments)))
|
||||
|
||||
for dept in user_departments:
|
||||
EmployeeDepartment.objects.create(
|
||||
employee=user,
|
||||
department=dept,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
created_count += 1
|
||||
|
||||
if created_count % 10 == 0:
|
||||
print(f' ✅ Создано {created_count} пользователей...')
|
||||
|
||||
except Exception as e:
|
||||
print(f' ❌ Ошибка создания пользователя {email}: {e}')
|
||||
skipped_count += 1
|
||||
|
||||
return created_count, skipped_count
|
||||
|
||||
|
||||
def print_statistics():
|
||||
"""Вывод статистики"""
|
||||
print('\n' + '=' * 60)
|
||||
print('📊 СТАТИСТИКА БАЗЫ ДАННЫХ')
|
||||
print('=' * 60)
|
||||
|
||||
total_users = User.objects.count()
|
||||
active_users = User.objects.filter(is_active=True).count()
|
||||
staff_users = User.objects.filter(is_staff=True).count()
|
||||
|
||||
print(f'\n👥 Пользователи:')
|
||||
print(f' Всего: {total_users}')
|
||||
print(f' Активных: {active_users}')
|
||||
print(f' Персонал: {staff_users}')
|
||||
|
||||
departments = Department.objects.all()
|
||||
print(f'\n📁 Отделы: {departments.count()}')
|
||||
for dept in departments:
|
||||
emp_count = EmployeeDepartment.objects.filter(
|
||||
department=dept,
|
||||
is_active=True
|
||||
).count()
|
||||
print(f' • {dept.name}: {emp_count} сотрудников')
|
||||
|
||||
positions = Position.objects.all()
|
||||
print(f'\n💼 Должности: {positions.count()}')
|
||||
for pos in positions:
|
||||
emp_count = User.objects.filter(position=pos).count()
|
||||
if emp_count > 0:
|
||||
print(f' • {pos.name}: {emp_count} сотрудников')
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
|
||||
|
||||
def main():
|
||||
print('=' * 60)
|
||||
print('🚀 ГЕНЕРАЦИЯ ТЕСТОВЫХ ПОЛЬЗОВАТЕЛЕЙ')
|
||||
print('=' * 60)
|
||||
|
||||
# Создаем отделы и должности
|
||||
departments = create_departments()
|
||||
positions = create_positions()
|
||||
|
||||
# Создаем пользователей
|
||||
created, skipped = create_users(
|
||||
count=120,
|
||||
departments=departments,
|
||||
positions=positions
|
||||
)
|
||||
|
||||
print(f'\n✅ Создание завершено!')
|
||||
print(f' Создано: {created} пользователей')
|
||||
if skipped > 0:
|
||||
print(f' Пропущено: {skipped} (уже существуют)')
|
||||
|
||||
# Выводим статистику
|
||||
print_statistics()
|
||||
|
||||
print('\n💡 Данные для входа:')
|
||||
print(' Username: user001, user002, ... user120')
|
||||
print(' Password: test123')
|
||||
print(' Email: имя.фамилия1@company.local, и т.д.')
|
||||
print('\n' + '=' * 60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user