feat: добавлена модель TeamMember для управления командой через админку
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
Service, Project, Client, Order, Review, BlogPost,
|
||||
Category, ServiceRequest, AboutPage, FooterSettings
|
||||
Category, ServiceRequest, AboutPage, FooterSettings, TeamMember
|
||||
)
|
||||
from .forms import ProjectForm
|
||||
|
||||
@@ -150,3 +150,33 @@ class FooterSettingsAdmin(admin.ModelAdmin):
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return not FooterSettings.objects.filter(is_active=True).exists()
|
||||
|
||||
|
||||
@admin.register(TeamMember)
|
||||
class TeamMemberAdmin(admin.ModelAdmin):
|
||||
list_display = ('full_name', 'position', 'order', 'is_active', 'email', 'updated_at')
|
||||
list_filter = ('is_active', 'position')
|
||||
search_fields = ('first_name', 'last_name', 'position', 'email')
|
||||
list_editable = ('order', 'is_active')
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('first_name', 'last_name', 'position', 'photo')
|
||||
}),
|
||||
('О специалисте', {
|
||||
'fields': ('bio', 'specialization')
|
||||
}),
|
||||
('Контакты', {
|
||||
'fields': ('email', 'phone', 'telegram', 'linkedin', 'github'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Настройки отображения', {
|
||||
'fields': ('order', 'is_active')
|
||||
}),
|
||||
)
|
||||
|
||||
def full_name(self, obj):
|
||||
return obj.full_name
|
||||
full_name.short_description = 'ФИО'
|
||||
full_name.admin_order_field = 'last_name'
|
||||
|
||||
|
||||
65
smartsoltech/web/management/commands/create_team_members.py
Normal file
65
smartsoltech/web/management/commands/create_team_members.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from web.models import TeamMember
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Создает тестовых членов команды'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Проверяем, есть ли уже члены команды
|
||||
if TeamMember.objects.exists():
|
||||
self.stdout.write(self.style.WARNING('⚠️ Члены команды уже существуют'))
|
||||
return
|
||||
|
||||
team_members = [
|
||||
{
|
||||
'first_name': 'Алексей',
|
||||
'last_name': 'Чой',
|
||||
'position': 'CEO & Founder',
|
||||
'bio': 'Визионер и лидер команды с более чем 5-летним опытом в IT-индустрии. Специализируется на стратегическом планировании и управлении проектами.',
|
||||
'specialization': 'Стратегическое планирование, управление проектами, бизнес-аналитика',
|
||||
'email': 'alexey@smartsoltech.kr',
|
||||
'telegram': 'alexey_choi',
|
||||
'linkedin': 'https://linkedin.com/in/alexey-choi',
|
||||
'github': 'https://github.com/alexeychoi',
|
||||
'order': 1,
|
||||
'is_active': True
|
||||
},
|
||||
{
|
||||
'first_name': 'Анна',
|
||||
'last_name': 'Ким',
|
||||
'position': 'Lead Developer',
|
||||
'bio': 'Опытный full-stack разработчик со страстью к созданию масштабируемых и эффективных веб-приложений. Эксперт в React, Django и cloud технологиях.',
|
||||
'specialization': 'React, Django, Docker, PostgreSQL, AWS',
|
||||
'email': 'anna@smartsoltech.kr',
|
||||
'telegram': 'anna_kim_dev',
|
||||
'linkedin': 'https://linkedin.com/in/anna-kim',
|
||||
'github': 'https://github.com/annakim',
|
||||
'order': 2,
|
||||
'is_active': True
|
||||
},
|
||||
{
|
||||
'first_name': 'Дмитрий',
|
||||
'last_name': 'Пак',
|
||||
'position': 'UI/UX Designer',
|
||||
'bio': 'Креативный дизайнер, создающий интуитивные и привлекательные пользовательские интерфейсы. Специализируется на UX-исследованиях и современном веб-дизайне.',
|
||||
'specialization': 'Figma, Adobe XD, Sketch, UI Design, UX Research',
|
||||
'email': 'dmitry@smartsoltech.kr',
|
||||
'telegram': 'dmitry_pak',
|
||||
'linkedin': 'https://linkedin.com/in/dmitry-pak',
|
||||
'order': 3,
|
||||
'is_active': True
|
||||
},
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
for member_data in team_members:
|
||||
member = TeamMember.objects.create(**member_data)
|
||||
created_count += 1
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'✅ Создан: {member.full_name} - {member.position}')
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'\n✨ Готово! Создано {created_count} членов команды')
|
||||
)
|
||||
39
smartsoltech/web/migrations/0011_teammember.py
Normal file
39
smartsoltech/web/migrations/0011_teammember.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.1.1 on 2025-11-24 00:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('web', '0010_aboutpage_footersettings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TeamMember',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Имя')),
|
||||
('last_name', models.CharField(max_length=100, verbose_name='Фамилия')),
|
||||
('position', models.CharField(max_length=200, verbose_name='Должность')),
|
||||
('photo', models.ImageField(blank=True, null=True, upload_to='static/img/team/', verbose_name='Фотография')),
|
||||
('bio', models.TextField(blank=True, help_text='Краткое описание специалиста', verbose_name='Биография')),
|
||||
('specialization', models.TextField(blank=True, help_text='Области экспертизы, навыки', verbose_name='Специализация')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='Email')),
|
||||
('phone', models.CharField(blank=True, max_length=50, verbose_name='Телефон')),
|
||||
('telegram', models.CharField(blank=True, max_length=100, verbose_name='Telegram')),
|
||||
('linkedin', models.URLField(blank=True, verbose_name='LinkedIn')),
|
||||
('github', models.URLField(blank=True, verbose_name='GitHub')),
|
||||
('order', models.IntegerField(default=0, help_text='Чем меньше число, тем выше в списке', verbose_name='Порядок сортировки')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Член команды',
|
||||
'verbose_name_plural': 'Команда',
|
||||
'ordering': ['order', 'last_name', 'first_name'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -341,3 +341,66 @@ class FooterSettings(models.Model):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class TeamMember(models.Model):
|
||||
"""Модель для членов команды"""
|
||||
|
||||
# Basic Info
|
||||
first_name = models.CharField(max_length=100, verbose_name='Имя')
|
||||
last_name = models.CharField(max_length=100, verbose_name='Фамилия')
|
||||
position = models.CharField(max_length=200, verbose_name='Должность')
|
||||
|
||||
# Photo
|
||||
photo = models.ImageField(
|
||||
upload_to='static/img/team/',
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Фотография'
|
||||
)
|
||||
|
||||
# Bio
|
||||
bio = models.TextField(
|
||||
blank=True,
|
||||
verbose_name='Биография',
|
||||
help_text='Краткое описание специалиста'
|
||||
)
|
||||
|
||||
# Skills/Specialization
|
||||
specialization = models.TextField(
|
||||
blank=True,
|
||||
verbose_name='Специализация',
|
||||
help_text='Области экспертизы, навыки'
|
||||
)
|
||||
|
||||
# Social Links
|
||||
email = models.EmailField(blank=True, verbose_name='Email')
|
||||
phone = models.CharField(max_length=50, blank=True, verbose_name='Телефон')
|
||||
telegram = models.CharField(max_length=100, blank=True, verbose_name='Telegram')
|
||||
linkedin = models.URLField(blank=True, verbose_name='LinkedIn')
|
||||
github = models.URLField(blank=True, verbose_name='GitHub')
|
||||
|
||||
# Order and Status
|
||||
order = models.IntegerField(
|
||||
default=0,
|
||||
verbose_name='Порядок сортировки',
|
||||
help_text='Чем меньше число, тем выше в списке'
|
||||
)
|
||||
is_active = models.BooleanField(default=True, verbose_name='Активен')
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Создано')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='Обновлено')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Член команды'
|
||||
verbose_name_plural = 'Команда'
|
||||
ordering = ['order', 'last_name', 'first_name']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name} - {self.position}"
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
|
||||
|
||||
90
smartsoltech/web/templates/web/team_section.html
Normal file
90
smartsoltech/web/templates/web/team_section.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<!-- Team Section -->
|
||||
<section class="section-padding" id="team">
|
||||
<div class="container-modern">
|
||||
<div class="text-center mb-5">
|
||||
<span class="badge bg-gradient text-white mb-3 px-3 py-2 rounded-pill">
|
||||
{{ about.team_badge|default:"👥 Команда" }}
|
||||
</span>
|
||||
<h2 class="display-5 fw-bold mb-3">
|
||||
{{ about.team_title|default:"Познакомьтесь с нашей командой"|safe }}
|
||||
</h2>
|
||||
<p class="lead text-muted max-width-600 mx-auto">
|
||||
{{ about.team_description|default:"Талантливые профессионалы, которые воплощают ваши идеи в реальность" }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if team_members %}
|
||||
<div class="row g-4">
|
||||
{% for member in team_members %}
|
||||
<div class="col-lg-4 col-md-6" data-aos="fade-up">
|
||||
<div class="card-modern text-center h-100">
|
||||
<div class="position-relative">
|
||||
{% if member.photo %}
|
||||
<img src="{{ member.photo.url }}"
|
||||
alt="{{ member.full_name }}"
|
||||
class="team-avatar mx-auto mb-3 rounded-circle"
|
||||
style="width: 120px; height: 120px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="team-avatar mx-auto mb-3" style="width: 120px; height: 120px; background: var(--gradient-primary); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="text-white" style="font-size: 2.5rem; font-weight: 700;">
|
||||
{{ member.first_name|first }}{{ member.last_name|first }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="mb-2">{{ member.full_name }}</h5>
|
||||
<p class="text-primary mb-3">{{ member.position }}</p>
|
||||
|
||||
{% if member.bio %}
|
||||
<p class="text-muted small mb-3">
|
||||
{{ member.bio|truncatewords:30 }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if member.specialization %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-code me-1"></i>
|
||||
{{ member.specialization|truncatewords:10 }}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-center gap-2">
|
||||
{% if member.linkedin %}
|
||||
<a href="{{ member.linkedin }}" target="_blank" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
|
||||
<i class="fab fa-linkedin-in"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if member.github %}
|
||||
<a href="{{ member.github }}" target="_blank" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
|
||||
<i class="fab fa-github"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if member.telegram %}
|
||||
<a href="https://t.me/{{ member.telegram }}" target="_blank" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
|
||||
<i class="fab fa-telegram-plane"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if member.email %}
|
||||
<a href="mailto:{{ member.email }}" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<p class="text-muted">Информация о команде скоро появится</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from .models import Service, Project, Client, BlogPost, Review, Order, ServiceRequest, Category, AboutPage, FooterSettings
|
||||
from .models import Service, Project, Client, BlogPost, Review, Order, ServiceRequest, Category, AboutPage, FooterSettings, TeamMember
|
||||
from django.db.models import Avg
|
||||
from comunication.models import TelegramSettings
|
||||
import qrcode
|
||||
@@ -88,7 +88,11 @@ def services_view(request):
|
||||
|
||||
def about_view(request):
|
||||
about_page = AboutPage.objects.filter(is_active=True).first()
|
||||
return render(request, 'web/about_modern.html', {'about': about_page})
|
||||
team_members = TeamMember.objects.filter(is_active=True).order_by('order', 'last_name')
|
||||
return render(request, 'web/about_modern.html', {
|
||||
'about': about_page,
|
||||
'team_members': team_members
|
||||
})
|
||||
|
||||
def create_service_request(request, service_id):
|
||||
if request.method == 'POST':
|
||||
|
||||
Reference in New Issue
Block a user