This commit is contained in:
2025-11-24 11:31:29 +09:00
parent ce7119e9e9
commit 1da6180658
30 changed files with 4352 additions and 272 deletions

View File

@@ -2,6 +2,8 @@ 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)
@@ -57,18 +59,45 @@ class Client(models.Model):
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()
published_date = models.DateTimeField(auto_now_add=True)
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 = 'Блоги'
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)
@@ -136,6 +165,37 @@ class Project(models.Model):
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)
featured = models.BooleanField(default=False)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='portfolio_items')
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 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')
@@ -247,6 +307,119 @@ class AboutPage(models.Model):
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):
"""Настройки футера - все данные управляются из админки"""
@@ -374,7 +547,12 @@ class TeamMember(models.Model):
# 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')
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')