From a04709d3df046e6b6ee6c34f7cc911c855a178db Mon Sep 17 00:00:00 2001 From: Egor Deev Date: Fri, 2 Oct 2026 12:23:08 +0000 Subject: [PATCH] =?UTF-8?q?populate=5Fdemo=20=D0=BF=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=B0=20=D0=BA=20=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=83=D1=89=D0=B8=D0=BC=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8F?= =?UTF-8?q?=D0=BC,=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88?= =?UTF-8?q?=D0=B0=D1=8F=20load=5Finitial=5Fdata=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit populate_demo падала на импорте несуществующей ArticleCategory, строковых статусах проектов и удалённом поле is_featured; теперь создаёт статусы проектов и работает, это проверяет тест. load_initial_data была загрузчиком старой версии резюме под прежние модели и тоже не запускалась — актуальный контент живёт в админке. --- main/management/commands/load_initial_data.py | 366 ------------------ main/management/commands/populate_demo.py | 27 +- main/tests/test_pages.py | 14 + 3 files changed, 33 insertions(+), 374 deletions(-) delete mode 100644 main/management/commands/load_initial_data.py diff --git a/main/management/commands/load_initial_data.py b/main/management/commands/load_initial_data.py deleted file mode 100644 index 3b30dbe..0000000 --- a/main/management/commands/load_initial_data.py +++ /dev/null @@ -1,366 +0,0 @@ -from datetime import date - -from django.core.management.base import BaseCommand - -from main.models import Article, Category, Education, Experience, Project, SiteSettings, Skill - - -class Command(BaseCommand): - help = 'Загрузка начальных данных из резюме Деева Е.В.' - - def handle(self, *args, **options): - self.stdout.write('Загрузка начальных данных...\n') - - self.load_site_settings() - self.load_skills() - self.load_experiences() - self.load_educations() - self.load_projects() - self.load_categories() - self.load_achievements() - - self.stdout.write(self.style.SUCCESS('\n✓ Все данные успешно загружены!')) - - def load_site_settings(self): - self.stdout.write(' → Настройки сайта...') - - settings, created = SiteSettings.objects.get_or_create(pk=1) - settings.site_name = 'deev.space' - settings.site_description = 'Персональный сайт backend-разработчика. Портфолио проектов, технический блог и контактная информация.' - settings.owner_name = 'Деев Егор Викторович' - settings.owner_title = 'Backend Developer' - settings.owner_bio = '''Опытный разработчик с более чем 3-летним стажем программирования. Начинал профессиональный путь с обучения в Яндекс Лицее, что заложило фундамент для дальнейшего роста. Обладаю подтверждённым опытом руководства командой разработки и управления полным циклом проектов — от концепции до реализации. - -Специализируюсь на создании backend-решений, парсинге данных, автоматизации процессов и интеграции AI-компонентов. Отличаюсь аналитическим складом ума, нацелен на поиск и эффективное решение сложных, нестандартных задач.''' - settings.owner_email = 'egor@deev.space' - settings.owner_phone = '+7 (999) 373-77-37' - settings.owner_city = 'Москва' - settings.telegram_url = 'https://t.me/Egor_Deev' - settings.github_url = 'https://github.com/EDeev' - settings.save() - - self.stdout.write(self.style.SUCCESS(' OK')) - - def load_skills(self): - self.stdout.write(' → Навыки...') - - skills_data = [ - # Backend - ('Python', 'backend', 'devicon-python-plain', 1), - ('C', 'backend', 'devicon-c-plain', 2), - ('C++', 'backend', 'devicon-cplusplus-plain', 3), - # Frontend - ('HTML / CSS', 'frontend', 'devicon-html5-plain', 1), - ('Bootstrap', 'frontend', 'devicon-bootstrap-plain', 2), - ('Figma', 'frontend', 'devicon-figma-plain', 3), - # DevOps - ('Nginx', 'devops', 'devicon-nginx-original', 1), - ('Docker', 'devops', 'devicon-docker-plain', 2), - ('Kubernetes', 'devops', 'devicon-kubernetes-plain', 3), - ('GitLab', 'devops', 'devicon-gitlab-plain', 4), - ('Bash', 'devops', 'devicon-bash-plain', 5), - # Database - ('PostgreSQL', 'database', 'devicon-postgresql-plain', 1), - ('SQLite', 'database', 'devicon-sqlite-plain', 2), - ('Chroma', 'database', 'fas fa-database', 3), - # Tools - ('Git', 'tools', 'devicon-git-plain', 1), - ('WinSCP', 'tools', 'fas fa-server', 2), - ('QT', 'tools', 'devicon-qt-original', 3), - ('Django', 'tools', 'devicon-django-plain', 4), - ('Asyncio', 'tools', 'fas fa-bolt', 5), - ('Flowise', 'tools', 'fas fa-robot', 6), - ] - - for name, category, icon, order in skills_data: - Skill.objects.update_or_create( - name=name, - defaults={'category': category, 'icon': icon, 'order': order} - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(skills_data)} навыков)')) - - def load_experiences(self): - self.stdout.write(' → Опыт работы...') - - experiences_data = [ - { - 'title': 'Тимлид университетского проекта', - 'company': 'МосПолитех', - 'description': 'Руковожу разработкой офисного пакета на основе исходного кода Apache OpenOffice с последующей специализацией под нужды российского бизнеса и государственного сектора России.', - 'responsibilities': '''Сборка из исходного кода на GitHub с последующими правками кода -Разработка backend-части с доработкой сырых функций OpenOffice -Управление и контроль за выполнением командой разработки поставленных задач -Создание и структуризация упрощённого и более специализированного нового интерфейса -Написание более подробной и понятной пользователю документации по работе с офисным пакетом''', - 'technologies': 'C++, Apache OpenOffice, Bash, Git', - 'start_date': date(2024, 1, 1), - 'is_current': True, - 'order': 1 - }, - { - 'title': 'Руководитель команды разработки', - 'company': 'МосПолитех', - 'description': 'Руководил разработкой скриптов и алгоритмов на Python по проекту расширения веб-доступности «EasyAccess».', - 'responsibilities': '''Разработка backend-части с обработкой API запросов на Django -Управление и контроль за выполнением командой разработки крупных задач -Создание структуры API запросов -Реализация запросов к БД на PostgreSQL -Настройка Docker''', - 'technologies': 'Python, Django, Docker, PostgreSQL', - 'start_date': date(2024, 1, 1), - 'end_date': date(2025, 1, 1), - 'is_current': False, - 'order': 2 - } - ] - - for exp_data in experiences_data: - Experience.objects.update_or_create( - title=exp_data['title'], - company=exp_data['company'], - defaults=exp_data - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(experiences_data)} записей)')) - - def load_educations(self): - self.stdout.write(' → Образование...') - - educations_data = [ - { - 'institution': 'Московский Политехнический Университет', - 'institution_short': 'МосПолитех', - 'degree': 'Системная и программная инженерия', - 'education_type': 'university', - 'description': 'Высшее образование по направлению системной и программной инженерии.', - 'achievements': '''Изучение алгоритмов и структур данных -Практика с различными базовыми алгоритмами для низкоуровневых языков -Анализ математических алгоритмов -Изучение С++ в контексте системного программирования -Изучение PostgreSQL и структуры базы данных''', - 'start_year': 2024, - 'is_current': True, - 'icon': 'fas fa-university', - 'order': 1 - }, - { - 'institution': 'Рязанский Государственный Радиотехнический Университет', - 'institution_short': 'РГРТУ', - 'degree': 'Фундаментальная информатика и информационные технологии', - 'education_type': 'university', - 'description': 'Высшее образование в области информационных технологий.', - 'achievements': '''Создание, поддержка и администрирование информационно-коммуникационных систем и баз данных -Разработка и тестирование программного обеспечения -Управление информационными ресурсами -Формирование навыков алгоритмического мышления -Разработка оригинальных алгоритмов и интеллектуальные программные системы''', - 'start_year': 2022, - 'end_year': 2024, - 'is_current': False, - 'icon': 'fas fa-university', - 'order': 2 - }, - { - 'institution': 'Яндекс Лицей / Норильск IT-Cube', - 'institution_short': 'Яндекс Лицей', - 'degree': 'Основы промышленного программирования на Python', - 'education_type': 'course', - 'description': 'Продвинутый курс программирования на Python.', - 'achievements': '''Изучение сложных алгоритмов и решение неординарных задач -Работа с фреймворками: Pygame, PyQT, Request, Aiogram и др. -Изучение структуры и создания базы данных SQLite''', - 'start_year': 2021, - 'end_year': 2022, - 'certificate_number': '2202 54726', - 'is_current': False, - 'icon': 'fas fa-graduation-cap', - 'order': 3 - }, - { - 'institution': 'Яндекс Лицей / Норильск IT-Cube', - 'institution_short': 'Яндекс Лицей', - 'degree': 'Основы программирования на языке Python', - 'education_type': 'course', - 'description': 'Базовый курс программирования на Python.', - 'achievements': '''Обучение основам и базовым библиотекам -Изучение PEP8''', - 'start_year': 2020, - 'end_year': 2021, - 'certificate_number': '2101 54726', - 'is_current': False, - 'icon': 'fas fa-graduation-cap', - 'order': 4 - }, - { - 'institution': 'Stepik', - 'institution_short': 'Stepik', - 'degree': 'Введение в программирование (C++)', - 'education_type': 'course', - 'description': 'Онлайн-курс по основам программирования на C++.', - 'achievements': '''Практические основы программирования на С++ -Изучение базовых конструкций языка''', - 'start_year': 2022, - 'end_year': 2023, - 'certificate_number': '2759904', - 'is_current': False, - 'icon': 'fas fa-laptop-code', - 'order': 5 - }, - { - 'institution': 'Московский Политех', - 'institution_short': 'МосПолитех', - 'degree': 'Управление инвестиционными проектами', - 'education_type': 'course', - 'description': 'Дополнительное образование по управлению проектами.', - 'achievements': '''Обучение основам и продвинутым методам управления инвестиционными проектами -Разработка финансовых моделей -Управление рисками и анализ внутренней и внешней среды проектов -Расчёт бизнес-плана -Изучение «мсп.рф», «Яндекс.трекер» и других бизнес-инструментов''', - 'start_year': 2024, - 'is_current': True, - 'icon': 'fas fa-chart-line', - 'order': 6 - } - ] - - for edu_data in educations_data: - Education.objects.update_or_create( - institution=edu_data['institution'], - degree=edu_data['degree'], - defaults=edu_data - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(educations_data)} записей)')) - - def load_projects(self): - self.stdout.write(' → Проекты...') - - projects_data = [ - { - 'title': 'Abobot', - 'slug': 'abobot', - 'short_description': 'Многофункциональный Telegram-бот для упоминания пользователей внутри чатов.', - 'description': 'Многофункциональный Telegram-бот для упоминания пользователей внутри чатов с продвинутой системой распознавания имён и обработки данных.', - 'features': '''9 команд, 8 текстовых ивентов и 4 нативные функции -Распознавание имён и запись в начальной форме -Запись и обработка данных о группах в обширную БД -Конвертация голосовых сообщений в текст при помощи фреймворка gTTS -Сложный алгоритм оповещения пользователя при упоминании имени в чате''', - 'icon': 'fab fa-telegram', - 'technologies': 'Python, Asyncio, SQLite, Aiogram, gTTS, Request', - 'github_url': 'https://github.com/EDeev/abobot', - 'status': 'beta', - 'card_size': 'featured', - 'users_count': '17.3k+', - 'order': 1 - }, - { - 'title': 'Compile Hub', - 'slug': 'compile-hub', - 'short_description': 'Высокопроизводительный асинхронный сервис для веб-IDE с поддержкой множества языков.', - 'description': 'Высокопроизводительный асинхронный сервис на Python для веб-IDE с поддержкой множества языков программирования.', - 'features': '''Асинхронная queue-based архитектура обработки -Мультиязычная компиляция (C++, Python, JavaScript) -Комплексная система безопасности и rate limiting -Встроенная метрика производительности в реальном времени -Полноценный user management с файловой системой''', - 'icon': 'fas fa-terminal', - 'technologies': 'Python, FastAPI, SQLite, Asyncio', - 'status': 'beta', - 'card_size': 'regular', - 'order': 2 - }, - { - 'title': 'Mobiles Dataset', - 'slug': 'mobiles-dataset', - 'short_description': 'Реляционная система на PostgreSQL для каталогизации смартфонов с интерфейсом на PyQT.', - 'description': 'База данных с приложением на Python. Реляционная система на PostgreSQL для каталогизации технических характеристик и ценовой аналитики смартфонов.', - 'features': '''Функциональный интерфейс на PyQT -Нормализованная структура из 5 взаимосвязанных таблиц -Реализованный полный CRUD функционал -Автоматизированный импорт из CSV с валидацией -Оптимизированные индексы для быстрого поиска''', - 'icon': 'fas fa-mobile-alt', - 'technologies': 'Python, PyQT, PostgreSQL, Psycopg2, Pandas', - 'status': 'completed', - 'card_size': 'regular', - 'order': 3 - }, - { - 'title': 'deev.space', - 'slug': 'deev-space', - 'short_description': 'Личный сайт с информацией обо мне, контактной информацией, достижениями и проектами.', - 'description': 'Личный сайт с информацией обо мне, контактной информацией, достижениями и вкладками с проектами.', - 'features': '''Развёртывание сервера на Nginx -Управление сайтом на Django -Разработка интерфейса с использованием Bootstrap и готовых темплейтов -Шаблонизация HTML кода с использованием Jinja логики -Привязка БД и метрик Яндекса''', - 'icon': 'fas fa-globe', - 'technologies': 'Python, Django, SQLite, Nginx, Bootstrap, Request', - 'demo_url': 'https://deev.space', - 'status': 'beta', - 'card_size': 'small', - 'users_count': '20 посещений/мес.', - 'order': 4 - } - ] - - for proj_data in projects_data: - Project.objects.update_or_create( - slug=proj_data['slug'], - defaults=proj_data - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(projects_data)} проектов)')) - - def load_categories(self): - self.stdout.write(' → Категории блога...') - - categories = [ - ('Технические гайды', 'tech-guides', 'fas fa-book', 1), - ('Личные истории', 'personal', 'fas fa-user', 2), - ('Размышления', 'thoughts', 'fas fa-lightbulb', 3), - ('Достижения', 'achievements', 'fas fa-trophy', 4), - ] - - for name, slug, icon, order in categories: - Category.objects.update_or_create( - slug=slug, - defaults={'name': name, 'icon': icon, 'order': order} - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(categories)} категорий)')) - - def load_achievements(self): - self.stdout.write(' → Достижения...') - - achievements_category = Category.objects.filter(slug='achievements').first() - - achievements_data = [ - { - 'title': 'Международный конкурс научно-практических работ', - 'slug': 'naukoemkie-tehnologii-2024', - 'sub_title': '2 место в номинации «Научно-исследовательская работа»', - 'excerpt': 'Международный конкурс научно-практических работ и проектов в рамках Международной конференции «Наукоёмкие технологии — основа современного цифрового промышленного производства»', - 'post': '''Получено 2 место в номинации «Научно-исследовательская работа» на Международном конкурсе научно-практических работ и проектов в рамках Международной конференции «Наукоёмкие технологии — основа современного цифрового промышленного производства». - -**Тема работы:** «Разработка веб-интерфейса с адаптивным дизайном для пользователей с особыми возможностями здоровья в системе 1С» - -Работа была посвящена созданию доступного веб-интерфейса, учитывающего потребности пользователей с ограниченными возможностями здоровья.''', - 'category': achievements_category, - 'is_achievement': True, - 'achievement_icon': 'fas fa-medal', - 'achievement_date': date(2024, 1, 1), - } - ] - - for ach_data in achievements_data: - Article.objects.update_or_create( - slug=ach_data['slug'], - defaults=ach_data - ) - - self.stdout.write(self.style.SUCCESS(f' OK ({len(achievements_data)} достижений)')) diff --git a/main/management/commands/populate_demo.py b/main/management/commands/populate_demo.py index 830ca02..e93750a 100644 --- a/main/management/commands/populate_demo.py +++ b/main/management/commands/populate_demo.py @@ -10,10 +10,11 @@ from django.core.management.base import BaseCommand from main.models import ( Article, - ArticleCategory, + Category, Education, Experience, Project, + ProjectStatus, SiteSettings, Skill, ) @@ -61,6 +62,16 @@ class Command(BaseCommand): for data in skills_data: Skill.objects.get_or_create(**data) + # Статусы проектов + statuses = {} + for order, (slug, name, color, is_release) in enumerate([ + ('completed', 'Релиз', 'success', True), + ('in_development', 'В разработке', 'warning', False), + ]): + statuses[slug], _ = ProjectStatus.objects.get_or_create( + slug=slug, defaults={'name': name, 'color': color, 'is_release': is_release, 'order': order} + ) + # Создание проектов projects_data = [ { @@ -68,17 +79,17 @@ class Command(BaseCommand): 'slug': 'taskmaster-pro', 'short_description': 'Система управления задачами с AI-рекомендациями', 'technologies': 'Python, Django, PostgreSQL, Redis, Celery', - 'status': 'completed', + 'status': statuses['completed'], 'card_size': 'featured', 'icon': 'fas fa-tasks', - 'is_featured': True, + 'show_on_homepage': True, }, { 'title': 'DataParser', 'slug': 'dataparser', 'short_description': 'Высоконагруженный парсер данных с автоматизацией', 'technologies': 'Python, Scrapy, PostgreSQL, Docker', - 'status': 'completed', + 'status': statuses['completed'], 'card_size': 'regular', 'icon': 'fas fa-database', }, @@ -87,7 +98,7 @@ class Command(BaseCommand): 'slug': 'api-gateway', 'short_description': 'Микросервисный API Gateway с авторизацией', 'technologies': 'Python, FastAPI, Redis, JWT', - 'status': 'in_development', + 'status': statuses['in_development'], 'card_size': 'regular', 'icon': 'fas fa-server', }, @@ -104,10 +115,10 @@ class Command(BaseCommand): ] for data in categories_data: - ArticleCategory.objects.get_or_create(slug=data['slug'], defaults=data) + Category.objects.get_or_create(slug=data['slug'], defaults=data) # Создание статей - python_cat = ArticleCategory.objects.get(slug='python') + python_cat = Category.objects.get(slug='python') articles_data = [ { 'title': 'Асинхронное программирование в Python', @@ -122,7 +133,7 @@ class Command(BaseCommand): 'slug': 'django-orm-optimization', 'excerpt': 'Как ускорить запросы к базе данных...', 'post': '

Полный текст статьи об оптимизации Django ORM...

', - 'category': ArticleCategory.objects.get(slug='django'), + 'category': Category.objects.get(slug='django'), 'is_published': True, }, ] diff --git a/main/tests/test_pages.py b/main/tests/test_pages.py index 748c941..2634cd0 100644 --- a/main/tests/test_pages.py +++ b/main/tests/test_pages.py @@ -61,3 +61,17 @@ def test_all_static_references_exist(): if not Path(settings.BASE_DIR, 'static', ref).exists() ] assert not missing + + +@pytest.mark.django_db +def test_populate_demo_fills_site(client): + import io + + from django.core.management import call_command + + from main.models import Article, Project + + call_command('populate_demo', stdout=io.StringIO()) + assert Project.objects.exists() and Article.objects.exists() + for name in ('index', 'projects', 'blog', 'about'): + assert client.get(reverse(name), HTTP_USER_AGENT=UA).status_code == 200