Files
smartsoltech_site/smartsoltech/web/admin.py
Andrew K. Choi e7d6d5262d
Some checks failed
continuous-integration/drone/push Build is failing
Добавлена система проектов с автоматическим ресайзом изображений и адаптивным дизайном
- Удалена старая система портфолио (PortfolioCategory, PortfolioItem)
- Расширена модель Project: slug, categories (M2M), thumbnail, media files, meta fields
- Объединены категории: ProjectCategory удалена, используется общая Category
- Автоматический ресайз thumbnail до 600x400px с умным кропом по центру
- Создан /projects/ - страница списка проектов с фильтрацией по категориям
- Создан /project/<pk>/ - детальная страница проекта с галереей Swiper
- Адаптивный дизайн: 3 карточки в ряд (десктоп), 2 (планшет), 1 (мобильный)
- Параллакс-эффект на изображениях при наведении
- Lazy loading для оптимизации загрузки
- Фильтры категорий в виде пилюль как на странице услуг
- Компактные карточки с фиксированной шириной
- Кликабельные проекты в service_detail с отображением всех медиа
2025-11-26 09:44:14 +09:00

294 lines
12 KiB
Python

from django.contrib import admin
from .models import (
Service, Project, Client, Order, Review, BlogPost, Category, ServiceRequest,
HeroBanner, ContactInfo, Team, Career,
ProjectMedia
)
from .forms import ProjectForm
@admin.register(ContactInfo)
class ContactInfoAdmin(admin.ModelAdmin):
list_display = ('company_name', 'email', 'phone', 'is_active')
list_filter = ('is_active',)
search_fields = ('company_name', 'email', 'phone')
fields = ('company_name', 'email', 'phone', 'telegram', 'address', 'working_hours',
'description', 'call_to_action', 'subtitle', 'is_active')
@admin.register(HeroBanner)
class HeroBannerAdmin(admin.ModelAdmin):
list_display = ('title', 'is_active', 'order', 'created_at')
list_filter = ('is_active', 'created_at')
search_fields = ('title', 'subtitle')
fields = ('title', 'subtitle', 'description', 'image', 'video', 'video_poster',
'button_text', 'button_link', 'is_active', 'order')
list_editable = ('is_active', 'order')
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'price', 'has_video')
search_fields = ('name', 'category')
fields = ('name', 'description', 'price', 'category', 'image', 'video', 'video_poster')
def has_video(self, obj):
return bool(obj.video)
has_video.boolean = True
has_video.short_description = 'Есть видео'
@admin.register(Client)
class ClientAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'email', 'phone_number')
search_fields = ('first_name', 'last_name', 'email')
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'service', 'client', 'client__email', 'client__phone_number', 'status')
list_filter = ('status','client', 'order_date')
search_fields = ('client__first_name', 'service__name','status','client', 'order_date')
@admin.register(Review)
class ReviewAdmin(admin.ModelAdmin):
list_display = ('client', 'service', 'rating', 'review_date', 'has_video')
list_filter = ('rating',)
search_fields = ('client__first_name', 'service__name')
fields = ('client', 'service', 'project', 'rating', 'comment', 'image', 'video', 'video_poster')
def has_video(self, obj):
return bool(obj.video)
has_video.boolean = True
has_video.short_description = 'Есть видео'
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ('title', 'published_date', 'has_video')
search_fields = ('title',)
fields = ('title', 'content', 'image', 'video', 'video_poster')
def has_video(self, obj):
return bool(obj.video)
has_video.boolean = True
has_video.short_description = 'Есть видео'
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug', 'order', 'is_active', 'services_count', 'projects_count')
list_filter = ('is_active',)
search_fields = ('name', 'description')
prepopulated_fields = {'slug': ('name',)}
list_editable = ('order', 'is_active')
ordering = ('order', 'name')
fieldsets = (
('Основная информация', {
'fields': ('name', 'slug', 'description', 'icon')
}),
('Настройки отображения', {
'fields': ('order', 'is_active')
}),
)
def services_count(self, obj):
return obj.services.count()
services_count.short_description = 'Услуг'
def projects_count(self, obj):
return obj.projects.count()
projects_count.short_description = 'Проектов'
@admin.register(ServiceRequest)
class ServiceRequestAdmin(admin.ModelAdmin):
list_display = ('service','token', 'client', 'created_at')
search_fields = ('service','token', 'client')
list_filter = ('service','token','client')
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'position', 'department', 'is_active', 'display_order')
list_filter = ('department', 'is_active', 'show_on_about')
search_fields = ('first_name', 'last_name', 'position', 'skills')
list_editable = ('display_order', 'is_active')
fieldsets = (
('Основная информация', {
'fields': ('first_name', 'last_name', 'position', 'department')
}),
('Контактные данные', {
'fields': ('email', 'phone', 'photo'),
'classes': ('collapse',)
}),
('Профессиональная информация', {
'fields': ('bio', 'skills', 'experience_years')
}),
('Социальные сети', {
'fields': ('linkedin', 'github', 'telegram'),
'classes': ('collapse',)
}),
('Настройки отображения', {
'fields': ('is_active', 'show_on_about', 'display_order')
}),
)
def get_queryset(self, request):
return super().get_queryset(request).order_by('display_order', 'last_name')
@admin.register(Career)
class CareerAdmin(admin.ModelAdmin):
list_display = ('title', 'department', 'experience_level', 'employment_type', 'status', 'is_featured', 'created_at')
list_filter = ('status', 'employment_type', 'experience_level', 'department', 'is_featured')
search_fields = ('title', 'department', 'description', 'required_skills')
list_editable = ('status', 'is_featured')
fieldsets = (
('Основная информация', {
'fields': ('title', 'department', 'location', 'employment_type', 'experience_level')
}),
('Описание вакансии', {
'fields': ('description', 'responsibilities', 'requirements', 'benefits')
}),
('Зарплата', {
'fields': ('salary_min', 'salary_max', 'salary_currency'),
'classes': ('collapse',)
}),
('Навыки', {
'fields': ('required_skills', 'preferred_skills'),
}),
('Контактная информация', {
'fields': ('contact_email', 'contact_person'),
'classes': ('collapse',)
}),
('Статус и метаданные', {
'fields': ('status', 'is_featured', 'application_deadline', 'published_at')
}),
)
readonly_fields = ('created_at', 'updated_at')
def get_queryset(self, request):
return super().get_queryset(request).order_by('-is_featured', '-created_at')
def save_model(self, request, obj, form, change):
if obj.status == 'active' and not obj.published_at:
from django.utils import timezone
obj.published_at = timezone.now()
super().save_model(request, obj, form, change)
actions = ['mark_as_active', 'mark_as_paused', 'mark_as_closed']
def mark_as_active(self, request, queryset):
from django.utils import timezone
updated = queryset.update(status='active')
queryset.filter(published_at__isnull=True).update(published_at=timezone.now())
self.message_user(request, f'{updated} вакансий отмечены как активные.')
mark_as_active.short_description = "Отметить как активные"
def mark_as_paused(self, request, queryset):
updated = queryset.update(status='paused')
self.message_user(request, f'{updated} вакансий приостановлены.')
mark_as_paused.short_description = "Приостановить"
def mark_as_closed(self, request, queryset):
updated = queryset.update(status='closed')
self.message_user(request, f'{updated} вакансий закрыты.')
mark_as_closed.short_description = "Закрыть"
# ============================================
# ПРОЕКТЫ - АДМИНКИ
# ============================================
class ProjectMediaInline(admin.TabularInline):
"""Inline для медиа-файлов проекта"""
model = ProjectMedia
extra = 1
fields = ('media_type', 'image', 'video', 'video_poster', 'embed_code', 'caption', 'order')
ordering = ('order',)
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
"""Админка для проектов"""
list_display = ('name', 'status', 'is_featured', 'display_order', 'categories_display',
'views_count', 'likes_count', 'media_count', 'completion_date')
list_filter = ('status', 'is_featured', 'categories', 'completion_date')
search_fields = ('name', 'description', 'client__first_name', 'client__last_name', 'technologies')
filter_horizontal = ('categories',)
list_editable = ('is_featured', 'display_order', 'status')
ordering = ('-is_featured', '-display_order', '-completion_date')
date_hierarchy = 'completion_date'
inlines = [ProjectMediaInline]
fieldsets = (
('📋 Основная информация', {
'fields': ('name', 'categories', 'status', 'is_featured', 'display_order')
}),
('📝 Описание', {
'fields': ('short_description', 'description', 'image')
}),
('🏢 Детали проекта', {
'fields': ('client', 'service', 'order', 'category', 'project_url', 'github_url',
'technologies', 'duration', 'team_size', 'completion_date')
}),
('🎬 Видео', {
'fields': ('video', 'video_poster'),
'classes': ('collapse',)
}),
('🔍 SEO', {
'fields': ('meta_title', 'meta_description', 'meta_keywords'),
'classes': ('collapse',)
}),
('📊 Статистика', {
'fields': ('views_count', 'likes_count'),
'classes': ('collapse',)
}),
)
readonly_fields = ('views_count', 'likes_count')
def categories_display(self, obj):
return ', '.join([cat.name for cat in obj.categories.all()[:3]])
categories_display.short_description = 'Категории'
def media_count(self, obj):
return obj.media_files.count()
media_count.short_description = 'Медиа'
actions = ['mark_as_completed', 'mark_as_featured']
def mark_as_completed(self, request, queryset):
updated = queryset.update(status='completed')
self.message_user(request, f'{updated} проектов отмечены как завершённые.')
mark_as_completed.short_description = "Отметить как завершённые"
def mark_as_featured(self, request, queryset):
updated = queryset.update(is_featured=True)
self.message_user(request, f'{updated} проектов отмечены как избранные.')
mark_as_featured.short_description = "Отметить как избранные"
@admin.register(ProjectMedia)
class ProjectMediaAdmin(admin.ModelAdmin):
"""Админка для медиа-файлов проектов"""
list_display = ('id', 'project', 'media_type', 'caption', 'order', 'uploaded_at')
list_filter = ('media_type', 'uploaded_at')
search_fields = ('project__name', 'caption', 'alt_text')
list_editable = ('order',)
ordering = ('project', 'order', '-uploaded_at')
fieldsets = (
('Проект', {
'fields': ('project', 'media_type', 'order')
}),
('Изображение', {
'fields': ('image', 'alt_text'),
'classes': ('collapse',)
}),
('Видео', {
'fields': ('video', 'video_poster', 'embed_code'),
'classes': ('collapse',)
}),
('Описание', {
'fields': ('caption',)
}),
)