Files
smartsoltech_site/smartsoltech/web/models.py

631 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import models
from django.contrib.auth.models import AbstractUser, User
import uuid
from django.urls import reverse
from django.utils.text import slugify
from django.conf import settings
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(default='Описание категории')
class Meta:
verbose_name = 'Категория'
verbose_name_plural = 'Категории'
ordering = ['name']
def __str__(self):
return self.name
class Service(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(default='Описание услуги')
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='services')
image = models.ImageField(upload_to='static/img/services/', blank=True, null=True)
class Meta:
verbose_name = 'Услуга'
verbose_name_plural = 'Услуги'
ordering = ['name']
def __str__(self):
return self.name
def average_rating(self):
reviews = self.reviews.all()
if reviews:
return sum(review.rating for review in reviews) / reviews.count()
return 0
def review_count(self):
return self.reviews.count()
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='client_profile', null=True, blank=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
phone_number = models.CharField(max_length=15, unique=True)
image = models.ImageField(upload_to='static/img/customer/', blank=True, null=True)
chat_id = models.CharField(max_length=100, blank=True, null=True) # Telegram chat ID
class Meta:
verbose_name = 'Клиент'
verbose_name_plural = 'Клиенты'
ordering = ['last_name', 'first_name']
def __str__(self):
return f"{self.first_name} {self.last_name} {self.chat_id}"
class BlogPost(models.Model):
DRAFT = 'draft'
PUBLISHED = 'published'
STATUS_CHOICES = [
(DRAFT, 'Черновик'),
(PUBLISHED, 'Опубликовано'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='blog_posts')
excerpt = models.CharField(max_length=400, blank=True)
content = models.TextField()
image = models.ImageField(upload_to='static/img/blog/', blank=True, null=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
published_date = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
views = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = 'Запись блога'
verbose_name_plural = 'Записи блога'
ordering = ['-published_date']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title)[:200]
slug = base
i = 1
while BlogPost.objects.filter(slug=slug).exists():
slug = f"{base}-{i}"
i += 1
self.slug = slug
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog_detail', kwargs={'slug': self.slug})
class ServiceRequest(models.Model):
service = models.ForeignKey(Service, on_delete=models.CASCADE)
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='related_service_requests', null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
token = models.UUIDField(default=uuid.uuid4, unique=True) # Генерация уникального токена
chat_id = models.CharField(max_length=100, blank=True, null=True) # Telegram chat ID
is_verified = models.BooleanField(default=False)
class Meta:
verbose_name = 'Заявка на услугу'
verbose_name_plural = 'Заявки на услуги'
ordering = ['-is_verified', '-created_at']
def __str__(self):
return f"Request for {self.service.name} by {self.client.first_name}"
class Order(models.Model):
service_request = models.OneToOneField(ServiceRequest, on_delete=models.CASCADE, related_name='related_order')
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='related_orders')
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='related_orders')
message = models.TextField(blank=True, null=True)
order_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(
max_length=50,
choices=[
('pending', 'Ожидание'),
('in_progress', 'В процессе'),
('completed', 'Завершен'),
('cancelled', 'Отменён')
],
default='pending'
)
class Meta:
ordering = ['-order_date']
verbose_name = 'Заказ'
verbose_name_plural = 'Заказы'
def __str__(self):
return f"Order #{self.id} by {self.client.first_name}"
def is_completed(self):
return self.status == 'completed'
def get_absolute_url(self):
return reverse('order_detail', kwargs={'pk': self.pk})
class Project(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(default='Описание проекта')
completion_date = models.DateField(blank=True, null=True)
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='projects')
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='projects')
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='project', null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
image = models.ImageField(upload_to='static/img/project/', blank=True, null=True)
status = models.CharField(max_length=50, choices=[('in_progress', 'В процессе'), ('completed', 'Завершен')], default='in_progress')
class Meta:
verbose_name = 'Проект'
verbose_name_plural = 'Проекты'
ordering = ['-completion_date']
def __str__(self):
return self.name
class PortfolioItem(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True, blank=True)
description = models.TextField()
client_name = models.CharField(max_length=200, blank=True)
completion_date = models.DateField(blank=True, null=True)
image = models.ImageField(upload_to='static/img/portfolio/', blank=True, null=True, verbose_name='Главное изображение')
featured = models.BooleanField(default=False)
categories = models.ManyToManyField(Category, blank=True, related_name='portfolio_items', verbose_name='Категории')
is_active = models.BooleanField(default=True)
class Meta:
verbose_name = 'Портфолио'
verbose_name_plural = 'Портфолио'
ordering = ['-completion_date']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title)[:200]
slug = base
i = 1
while PortfolioItem.objects.filter(slug=slug).exists():
slug = f"{base}-{i}"
i += 1
self.slug = slug
super().save(*args, **kwargs)
class PortfolioImage(models.Model):
"""Дополнительные изображения для элемента портфолио"""
portfolio_item = models.ForeignKey(PortfolioItem, on_delete=models.CASCADE, related_name='gallery_images', verbose_name='Элемент портфолио')
image = models.ImageField(upload_to='static/img/portfolio/gallery/', verbose_name='Изображение')
caption = models.CharField(max_length=200, blank=True, verbose_name='Подпись к изображению')
order = models.IntegerField(default=0, verbose_name='Порядок отображения')
class Meta:
verbose_name = 'Изображение портфолио'
verbose_name_plural = 'Изображения портфолио'
ordering = ['order', 'id']
def __str__(self):
return f"{self.portfolio_item.title} - Изображение {self.order}"
class Review(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='reviews')
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='reviews')
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='reviews', blank=True, null=True)
rating = models.IntegerField()
comment = models.TextField()
review_date = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to='static/img/review/', blank=True, null=True)
class Meta:
verbose_name = 'Отзыв'
verbose_name_plural = 'Отзывы'
ordering = ['-review_date']
def __str__(self):
return f"Отзыв от {self.client.first_name} {self.client.last_name} for {self.service.name}"
class AboutPage(models.Model):
"""Модель для страницы 'О нас' - все данные управляются из админки"""
# Hero Section
hero_badge = models.CharField(max_length=100, default='🚀 О нас', verbose_name='Hero Badge')
hero_title = models.CharField(max_length=200, default='Мы создаем цифровое будущее', verbose_name='Заголовок Hero')
hero_description = models.TextField(default='SmartSolTech - это команда профессионалов...', verbose_name='Описание Hero')
# Statistics
stat_projects = models.IntegerField(default=50, verbose_name='Количество проектов')
stat_clients = models.IntegerField(default=30, verbose_name='Количество клиентов')
stat_years = models.IntegerField(default=3, verbose_name='Лет опыта')
stat_support = models.CharField(max_length=50, default='24/7', verbose_name='Поддержка')
# Mission Section
mission_badge = models.CharField(max_length=100, default='🎯 Наша миссия', verbose_name='Mission Badge')
mission_title = models.CharField(max_length=200, default='Делаем технологии доступными', verbose_name='Заголовок миссии')
mission_description = models.TextField(verbose_name='Описание миссии')
mission_point_1_title = models.CharField(max_length=200, default='Инновационные решения', verbose_name='Пункт миссии 1')
mission_point_1_text = models.TextField(default='Используем передовые технологии...', verbose_name='Текст пункта 1')
mission_point_2_title = models.CharField(max_length=200, default='Клиентоориентированность', verbose_name='Пункт миссии 2')
mission_point_2_text = models.TextField(default='Фокусируемся на потребностях...', verbose_name='Текст пункта 2')
mission_point_3_title = models.CharField(max_length=200, default='Непрерывное развитие', verbose_name='Пункт миссии 3')
mission_point_3_text = models.TextField(default='Постоянно совершенствуем...', verbose_name='Текст пункта 3')
# Vision Section
vision_badge = models.CharField(max_length=100, default='🔮 Наше видение', verbose_name='Vision Badge')
vision_title = models.CharField(max_length=200, default='Будущее начинается сегодня', verbose_name='Заголовок видения')
vision_description = models.TextField(verbose_name='Описание видения')
# Skills
skill_1_name = models.CharField(max_length=100, default='Веб-разработка', verbose_name='Навык 1')
skill_1_percent = models.IntegerField(default=95, verbose_name='Процент навыка 1')
skill_2_name = models.CharField(max_length=100, default='Мобильная разработка', verbose_name='Навык 2')
skill_2_percent = models.IntegerField(default=90, verbose_name='Процент навыка 2')
skill_3_name = models.CharField(max_length=100, default='UI/UX Дизайн', verbose_name='Навык 3')
skill_3_percent = models.IntegerField(default=85, verbose_name='Процент навыка 3')
skill_4_name = models.CharField(max_length=100, default='DevOps', verbose_name='Навык 4')
skill_4_percent = models.IntegerField(default=80, verbose_name='Процент навыка 4')
# Team Section
team_badge = models.CharField(max_length=100, default='👥 Команда', verbose_name='Team Badge')
team_title = models.CharField(max_length=200, default='Познакомьтесь с нашей командой', verbose_name='Заголовок команды')
team_description = models.TextField(default='Талантливые профессионалы...', verbose_name='Описание команды')
# Values Section
values_badge = models.CharField(max_length=100, default='💎 Наши ценности', verbose_name='Values Badge')
values_title = models.CharField(max_length=200, default='Что нами движет', verbose_name='Заголовок ценностей')
value_1_icon = models.CharField(max_length=50, default='fa-lightbulb', verbose_name='Иконка ценности 1')
value_1_title = models.CharField(max_length=100, default='Инновации', verbose_name='Ценность 1')
value_1_text = models.TextField(default='Мы постоянно ищем новые решения...', verbose_name='Текст ценности 1')
value_2_icon = models.CharField(max_length=50, default='fa-handshake', verbose_name='Иконка ценности 2')
value_2_title = models.CharField(max_length=100, default='Партнерство', verbose_name='Ценность 2')
value_2_text = models.TextField(default='Строим долгосрочные отношения...', verbose_name='Текст ценности 2')
value_3_icon = models.CharField(max_length=50, default='fa-chart-line', verbose_name='Иконка ценности 3')
value_3_title = models.CharField(max_length=100, default='Результат', verbose_name='Ценность 3')
value_3_text = models.TextField(default='Фокусируемся на достижении целей...', verbose_name='Текст ценности 3')
value_4_icon = models.CharField(max_length=50, default='fa-shield-alt', verbose_name='Иконка ценности 4')
value_4_title = models.CharField(max_length=100, default='Надежность', verbose_name='Ценность 4')
value_4_text = models.TextField(default='Гарантируем качество и безопасность...', verbose_name='Текст ценности 4')
# Contact Section
contact_title = models.CharField(max_length=200, default='Готовы начать проект?', verbose_name='Заголовок контактов')
contact_description = models.TextField(default='Свяжитесь с нами сегодня...', verbose_name='Описание контактов')
# Meta
is_active = models.BooleanField(default=True, verbose_name='Активна')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Обновлено')
class Meta:
verbose_name = 'Страница О нас'
verbose_name_plural = 'Страницы О нас'
def __str__(self):
return f"О нас (обновлено: {self.updated_at.strftime('%d.%m.%Y')})"
def save(self, *args, **kwargs):
# Оставляем только одну активную запись
if self.is_active:
AboutPage.objects.exclude(pk=self.pk).update(is_active=False)
super().save(*args, **kwargs)
class PrivacyPolicy(models.Model):
version = models.CharField(max_length=50, default='1.0')
content = models.TextField()
effective_date = models.DateField(auto_now_add=True)
is_active = models.BooleanField(default=True)
class Meta:
verbose_name = 'Политика конфиденциальности'
verbose_name_plural = 'Политики конфиденциальности'
def __str__(self):
return f"Privacy Policy v{self.version} ({self.effective_date})"
def save(self, *args, **kwargs):
if self.is_active:
PrivacyPolicy.objects.exclude(pk=self.pk).update(is_active=False)
super().save(*args, **kwargs)
class TermsOfUse(models.Model):
version = models.CharField(max_length=50, default='1.0')
content = models.TextField()
effective_date = models.DateField(auto_now_add=True)
is_active = models.BooleanField(default=True)
class Meta:
verbose_name = 'Условия использования'
verbose_name_plural = 'Условия использования'
def __str__(self):
return f"Terms v{self.version} ({self.effective_date})"
def save(self, *args, **kwargs):
if self.is_active:
TermsOfUse.objects.exclude(pk=self.pk).update(is_active=False)
super().save(*args, **kwargs)
class NewsArticle(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True, blank=True)
excerpt = models.CharField(max_length=300, blank=True)
content = models.TextField()
image = models.ImageField(upload_to='static/img/news/', blank=True, null=True)
is_published = models.BooleanField(default=False)
published_date = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Новость'
verbose_name_plural = 'Новости'
ordering = ['-published_date', '-created_at']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title)[:200]
slug = base
i = 1
while NewsArticle.objects.filter(slug=slug).exists():
slug = f"{base}-{i}"
i += 1
self.slug = slug
super().save(*args, **kwargs)
class CareerVacancy(models.Model):
FULL_TIME = 'FT'
PART_TIME = 'PT'
CONTRACT = 'CT'
INTERN = 'IN'
EMPLOYMENT_CHOICES = [
(FULL_TIME, 'Полная занятость'),
(PART_TIME, 'Частичная занятость'),
(CONTRACT, 'Контракт'),
(INTERN, 'Стажировка'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True, blank=True)
location = models.CharField(max_length=200, blank=True)
employment_type = models.CharField(max_length=2, choices=EMPLOYMENT_CHOICES, default=FULL_TIME)
responsibilities = models.TextField()
requirements = models.TextField()
desirable = models.TextField(blank=True)
salary_min = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
salary_max = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
is_active = models.BooleanField(default=True)
posted_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'Вакансия'
verbose_name_plural = 'Вакансии'
ordering = ['-posted_at']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title)[:200]
slug = base
i = 1
while CareerVacancy.objects.filter(slug=slug).exists():
slug = f"{base}-{i}"
i += 1
self.slug = slug
super().save(*args, **kwargs)
class FooterSettings(models.Model):
"""Настройки футера - все данные управляются из админки"""
# Company Info
company_name = models.CharField(max_length=100, default='SmartSolTech', verbose_name='Название компании')
company_description = models.TextField(
default='Мы создаем инновационные IT-решения, которые помогают бизнесу расти...',
verbose_name='Описание компании'
)
company_logo_icon = models.CharField(max_length=50, default='fa-code', verbose_name='Иконка компании')
# Social Links
telegram_url = models.URLField(blank=True, verbose_name='Telegram URL')
instagram_url = models.URLField(blank=True, verbose_name='Instagram URL')
linkedin_url = models.URLField(blank=True, verbose_name='LinkedIn URL')
github_url = models.URLField(blank=True, verbose_name='GitHub URL')
facebook_url = models.URLField(blank=True, verbose_name='Facebook URL')
twitter_url = models.URLField(blank=True, verbose_name='Twitter URL')
# Contact Info
email = models.EmailField(default='info@smartsoltech.kr', verbose_name='Email')
phone = models.CharField(max_length=50, default='+82-10-XXXX-XXXX', verbose_name='Телефон')
address = models.TextField(default='Seoul, South Korea', verbose_name='Адрес')
# Services Links (for footer menu)
show_services_menu = models.BooleanField(default=True, verbose_name='Показывать меню услуг')
services_title = models.CharField(max_length=100, default='Услуги', verbose_name='Заголовок меню услуг')
# Company Links (for footer menu)
show_company_menu = models.BooleanField(default=True, verbose_name='Показывать меню компании')
company_menu_title = models.CharField(max_length=100, default='Компания', verbose_name='Заголовок меню компании')
# Copyright
copyright_text = models.CharField(
max_length=200,
default='© 2025 SmartSolTech. Все права защищены.',
verbose_name='Текст Copyright'
)
# Integration Scripts
google_analytics = models.TextField(
blank=True,
verbose_name='Google Analytics',
help_text='Вставьте код Google Analytics (без тегов <script>)'
)
google_adsense = models.TextField(
blank=True,
verbose_name='Google AdSense',
help_text='Вставьте код Google AdSense (без тегов <script>)'
)
yandex_metrika = models.TextField(
blank=True,
verbose_name='Яндекс Метрика',
help_text='Вставьте код Яндекс Метрики (без тегов <script>)'
)
facebook_pixel = models.TextField(
blank=True,
verbose_name='Facebook Pixel',
help_text='Вставьте код Facebook Pixel (без тегов <script>)'
)
custom_head_scripts = models.TextField(
blank=True,
verbose_name='Скрипты в <head>',
help_text='Дополнительные скрипты для вставки в <head> (с тегами <script>)'
)
custom_body_scripts = models.TextField(
blank=True,
verbose_name='Скрипты перед </body>',
help_text='Дополнительные скрипты для вставки перед закрывающим </body> (с тегами <script>)'
)
# Meta
is_active = models.BooleanField(default=True, verbose_name='Активно')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Обновлено')
class Meta:
verbose_name = 'Настройки футера'
verbose_name_plural = 'Настройки футера'
def __str__(self):
return f"Футер (обновлено: {self.updated_at.strftime('%d.%m.%Y')})"
def save(self, *args, **kwargs):
# Оставляем только одну активную запись
if self.is_active:
FooterSettings.objects.exclude(pk=self.pk).update(is_active=False)
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',
help_text='Username без @ (например: trevor1985)'
)
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}"
class SiteSettings(models.Model):
"""Глобальные настройки сайта"""
currency_symbol = models.CharField(
max_length=10,
default='',
verbose_name='Символ валюты',
help_text='Символ валюты для отображения на сайте (₩, $, ₽, €, ¥ и т.д.)'
)
class Meta:
verbose_name = 'Настройки сайта'
verbose_name_plural = 'Настройки сайта'
def __str__(self):
return f'Настройки сайта (Валюта: {self.currency_symbol})'
def save(self, *args, **kwargs):
# Singleton pattern - только одна запись настроек
self.pk = 1
super().save(*args, **kwargs)
@classmethod
def get_settings(cls):
"""Получить настройки сайта (создать если не существует)"""
settings, created = cls.objects.get_or_create(pk=1)
return settings