From 089ccfa8aeab1076e7fc73dbadd22af62b0825cd Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 14 Dec 2025 20:55:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=B0/=D1=8D=D0=BA=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Создан модуль customers/services/import_export.py согласно best practices - Класс CustomerExporter: содержит логику экспорта в CSV (ранее была в views) - Класс CustomerImporter: заглушка для будущей реализации импорта - Views стали тонкими: customer_export и customer_import делегируют работу сервисам - Улучшена организация кода: соблюдён принцип Single Responsibility - Уменьшен размер views.py на 30 строк - Добавлена подробная документация в docstrings классов и методов - Логику теперь легко тестировать и переиспользовать (например, в Celery tasks) Преимущества: - Чистое разделение ответственности - Упрощённое тестирование - Возможность переиспользования в асинхронных задачах - Соответствие Django best practices --- myproject/customers/services/import_export.py | 106 ++++++++++++++++++ myproject/customers/views.py | 44 ++------ 2 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 myproject/customers/services/import_export.py diff --git a/myproject/customers/services/import_export.py b/myproject/customers/services/import_export.py new file mode 100644 index 0000000..cfd6f3e --- /dev/null +++ b/myproject/customers/services/import_export.py @@ -0,0 +1,106 @@ +""" +Сервис для импорта и экспорта клиентов. + +Этот модуль содержит логику импорта/экспорта клиентов в различных форматах (CSV, Excel). +Разделение на отдельный модуль улучшает организацию кода и следует принципам SRP. +""" +import csv +from django.http import HttpResponse +from django.utils import timezone +from ..models import Customer + + +class CustomerExporter: + """ + Класс для экспорта клиентов в различные форматы. + """ + + @staticmethod + def export_to_csv(): + """ + Экспортирует всех клиентов (кроме системного) в CSV файл. + + Поля экспорта: + - ID + - Имя + - Email + - Телефон + - Дата создания + + Примечание: Баланс кошелька НЕ экспортируется (требование безопасности). + + Returns: + HttpResponse: HTTP ответ с CSV файлом для скачивания + """ + # Создаём HTTP ответ с CSV файлом + response = HttpResponse(content_type='text/csv; charset=utf-8') + timestamp = timezone.now().strftime("%Y%m%d_%H%M%S") + response['Content-Disposition'] = f'attachment; filename="customers_export_{timestamp}.csv"' + + # Добавляем BOM для корректного открытия в Excel + response.write('\ufeff') + + writer = csv.writer(response) + + # Заголовки + writer.writerow([ + 'ID', + 'Имя', + 'Email', + 'Телефон', + 'Дата создания', + ]) + + # Данные (исключаем системного клиента) + customers = Customer.objects.filter(is_system_customer=False).order_by('-created_at') + + for customer in customers: + writer.writerow([ + customer.id, + customer.name or '', + customer.email or '', + str(customer.phone) if customer.phone else '', + customer.created_at.strftime('%Y-%m-%d %H:%M:%S'), + ]) + + return response + + +class CustomerImporter: + """ + Класс для импорта клиентов из различных форматов. + + TODO: Реализовать: + - Парсинг CSV файлов + - Парсинг Excel файлов (.xlsx, .xls) + - Валидация данных (email, телефон) + - Обработка дубликатов + - Пакетное создание клиентов + - Отчёт об ошибках + """ + + def __init__(self): + self.errors = [] + self.success_count = 0 + self.skip_count = 0 + + def import_from_file(self, file, update_existing=False): + """ + Импорт клиентов из загруженного файла. + + Args: + file: Загруженный файл (UploadedFile) + update_existing: Обновлять ли существующих клиентов (по email/телефону) + + Returns: + dict: Результат импорта с статистикой + """ + # TODO: Реализовать логику импорта + return { + 'success': False, + 'message': 'Функция импорта находится в разработке', + 'created': 0, + 'updated': 0, + 'skipped': 0, + 'errors': [] + } diff --git a/myproject/customers/views.py b/myproject/customers/views.py index 1ba03c9..8f69b90 100644 --- a/myproject/customers/views.py +++ b/myproject/customers/views.py @@ -598,9 +598,11 @@ def wallet_withdraw(request, pk): def customer_import(request): """ Импорт клиентов из CSV/Excel файла. - TODO: Реализовать логику импорта """ + from .services.import_export import CustomerImporter + if request.method == 'POST': + importer = CustomerImporter() # TODO: Обработка загруженного файла messages.info(request, 'Функция импорта в разработке') return redirect('customers:customer-list') @@ -615,41 +617,9 @@ def customer_import(request): @manager_or_owner_required def customer_export(request): """ - Экспорт клиентов в CSV/Excel файл. - TODO: Реализовать логику экспорта + Экспорт клиентов в CSV файл. """ - import csv - from django.http import HttpResponse - from django.utils import timezone + from .services.import_export import CustomerExporter - # Создаём HTTP ответ с CSV файлом - response = HttpResponse(content_type='text/csv; charset=utf-8') - response['Content-Disposition'] = f'attachment; filename="customers_export_{timezone.now().strftime("%Y%m%d_%H%M%S")}.csv"' - - # Добавляем BOM для корректного открытия в Excel - response.write('\ufeff') - - writer = csv.writer(response) - - # Заголовки - writer.writerow([ - 'ID', - 'Имя', - 'Email', - 'Телефон', - 'Дата создания', - ]) - - # Данные (исключаем системного клиента) - customers = Customer.objects.filter(is_system_customer=False).order_by('-created_at') - - for customer in customers: - writer.writerow([ - customer.id, - customer.name or '', - customer.email or '', - str(customer.phone) if customer.phone else '', - customer.created_at.strftime('%Y-%m-%d %H:%M:%S'), - ]) - - return response + exporter = CustomerExporter() + return exporter.export_to_csv()