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

@@ -1,7 +1,8 @@
from django.contrib import admin
from .models import (
Service, Project, Client, Order, Review, BlogPost,
Category, ServiceRequest, AboutPage, FooterSettings, TeamMember
Service, Project, Client, Order, Review, BlogPost,
Category, ServiceRequest, AboutPage, FooterSettings, TeamMember,
PortfolioItem, PrivacyPolicy, TermsOfUse, NewsArticle, CareerVacancy
)
from .forms import ProjectForm
@@ -36,13 +37,53 @@ class ReviewAdmin(admin.ModelAdmin):
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ('title', 'published_date')
search_fields = ('title',)
list_display = ('title', 'status', 'author', 'published_date')
list_filter = ('status', 'published_date')
search_fields = ('title', 'excerpt', 'author__username')
prepopulated_fields = {'slug': ('title',)}
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name','description')
search_fields = ('name',)
@admin.register(PortfolioItem)
class PortfolioItemAdmin(admin.ModelAdmin):
list_display = ('title', 'client_name', 'completion_date', 'featured', 'is_active')
list_filter = ('featured', 'is_active', 'completion_date')
search_fields = ('title', 'client_name')
prepopulated_fields = {'slug': ('title',)}
@admin.register(NewsArticle)
class NewsArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'is_published', 'published_date', 'created_at')
list_filter = ('is_published', 'published_date', 'created_at')
search_fields = ('title', 'excerpt')
prepopulated_fields = {'slug': ('title',)}
@admin.register(CareerVacancy)
class CareerVacancyAdmin(admin.ModelAdmin):
list_display = ('title', 'location', 'employment_type', 'is_active', 'posted_at')
list_filter = ('employment_type', 'is_active', 'posted_at')
search_fields = ('title', 'location')
prepopulated_fields = {'slug': ('title',)}
@admin.register(PrivacyPolicy)
class PrivacyPolicyAdmin(admin.ModelAdmin):
list_display = ('version', 'effective_date', 'is_active')
list_filter = ('is_active', 'effective_date')
search_fields = ('version',)
@admin.register(TermsOfUse)
class TermsOfUseAdmin(admin.ModelAdmin):
list_display = ('version', 'effective_date', 'is_active')
list_filter = ('is_active', 'effective_date')
search_fields = ('version',)
@admin.register(ServiceRequest)
class ServiceRequestAdmin(admin.ModelAdmin):

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2025-11-24 00:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0011_teammember'),
]
operations = [
migrations.AlterField(
model_name='teammember',
name='telegram',
field=models.CharField(blank=True, help_text='Username без @ (например: trevor1985)', max_length=100, verbose_name='Telegram'),
),
]

View File

@@ -0,0 +1,140 @@
# Generated by Django 5.1.1 on 2025-11-24 00:41
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0012_alter_teammember_telegram'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CareerVacancy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('slug', models.SlugField(blank=True, max_length=220, unique=True)),
('location', models.CharField(blank=True, max_length=200)),
('employment_type', models.CharField(choices=[('FT', 'Полная занятость'), ('PT', 'Частичная занятость'), ('CT', 'Контракт'), ('IN', 'Стажировка')], default='FT', max_length=2)),
('responsibilities', models.TextField()),
('requirements', models.TextField()),
('desirable', models.TextField(blank=True)),
('salary_min', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
('salary_max', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
('is_active', models.BooleanField(default=True)),
('posted_at', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name': 'Вакансия',
'verbose_name_plural': 'Вакансии',
'ordering': ['-posted_at'],
},
),
migrations.CreateModel(
name='NewsArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('slug', models.SlugField(blank=True, max_length=220, unique=True)),
('excerpt', models.CharField(blank=True, max_length=300)),
('content', models.TextField()),
('image', models.ImageField(blank=True, null=True, upload_to='static/img/news/')),
('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)),
],
options={
'verbose_name': 'Новость',
'verbose_name_plural': 'Новости',
'ordering': ['-published_date', '-created_at'],
},
),
migrations.CreateModel(
name='PrivacyPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.CharField(default='1.0', max_length=50)),
('content', models.TextField()),
('effective_date', models.DateField(auto_now_add=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Политика конфиденциальности',
'verbose_name_plural': 'Политики конфиденциальности',
},
),
migrations.CreateModel(
name='TermsOfUse',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.CharField(default='1.0', max_length=50)),
('content', models.TextField()),
('effective_date', models.DateField(auto_now_add=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Условия использования',
'verbose_name_plural': 'Условия использования',
},
),
migrations.AlterModelOptions(
name='blogpost',
options={'ordering': ['-published_date'], 'verbose_name': 'Запись блога', 'verbose_name_plural': 'Записи блога'},
),
migrations.AddField(
model_name='blogpost',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blog_posts', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='blogpost',
name='excerpt',
field=models.CharField(blank=True, max_length=400),
),
migrations.AddField(
model_name='blogpost',
name='slug',
field=models.SlugField(blank=True, max_length=220, unique=True),
),
migrations.AddField(
model_name='blogpost',
name='status',
field=models.CharField(choices=[('draft', 'Черновик'), ('published', 'Опубликовано')], default='draft', max_length=20),
),
migrations.AddField(
model_name='blogpost',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='blogpost',
name='views',
field=models.PositiveIntegerField(default=0),
),
migrations.CreateModel(
name='PortfolioItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('slug', models.SlugField(blank=True, max_length=220, unique=True)),
('description', models.TextField()),
('client_name', models.CharField(blank=True, max_length=200)),
('completion_date', models.DateField(blank=True, null=True)),
('image', models.ImageField(blank=True, null=True, upload_to='static/img/portfolio/')),
('featured', models.BooleanField(default=False)),
('is_active', models.BooleanField(default=True)),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='portfolio_items', to='web.category')),
],
options={
'verbose_name': 'Портфолио',
'verbose_name_plural': 'Портфолио',
'ordering': ['-completion_date'],
},
),
]

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')

View File

@@ -180,108 +180,8 @@
</div>
</section>
<!-- 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">
👥 Команда
</span>
<h2 class="display-5 fw-bold mb-3">
Познакомьтесь с <span class="text-gradient">нашей командой</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Талантливые профессионалы, которые воплощают ваши идеи в реальность
</p>
</div>
<div class="row g-4">
<!-- Team Member 1 -->
<div class="col-lg-4 col-md-6">
<div class="card-modern text-center h-100">
<div class="position-relative">
<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;">
<i class="fas fa-user text-white fa-3x"></i>
</div>
</div>
<div class="card-body">
<h5 class="mb-2">Алексей Чой</h5>
<p class="text-primary mb-3">CEO & Founder</p>
<p class="text-muted small mb-3">
Визионер и лидер команды с более чем 5-летним опытом в IT-индустрии.
Специализируется на стратегическом планировании и управлении проектами.
</p>
<div class="d-flex justify-content-center gap-2">
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-linkedin-in"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-github"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-telegram-plane"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Team Member 2 -->
<div class="col-lg-4 col-md-6">
<div class="card-modern text-center h-100">
<div class="position-relative">
<div class="team-avatar mx-auto mb-3" style="width: 120px; height: 120px; background: var(--gradient-accent); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
<i class="fas fa-user text-white fa-3x"></i>
</div>
</div>
<div class="card-body">
<h5 class="mb-2">Анна Ким</h5>
<p class="text-success mb-3">Lead Developer</p>
<p class="text-muted small mb-3">
Опытный full-stack разработчик со страстью к созданию масштабируемых
и эффективных веб-приложений. Эксперт в React, Django и cloud технологиях.
</p>
<div class="d-flex justify-content-center gap-2">
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-linkedin-in"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-github"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Team Member 3 -->
<div class="col-lg-4 col-md-6">
<div class="card-modern text-center h-100">
<div class="position-relative">
<div class="team-avatar mx-auto mb-3" style="width: 120px; height: 120px; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
<i class="fas fa-user text-white fa-3x"></i>
</div>
</div>
<div class="card-body">
<h5 class="mb-2">Дмитрий Пак</h5>
<p class="text-warning mb-3">UI/UX Designer</p>
<p class="text-muted small mb-3">
Креативный дизайнер, создающий интуитивные и привлекательные пользовательские интерфейсы.
Специализируется на UX-исследованиях и современном веб-дизайне.
</p>
<div class="d-flex justify-content-center gap-2">
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-dribbble"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-behance"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Team Section - Dynamic from Database -->
{% include 'web/team_section.html' %}
<!-- Technologies Section -->
<section class="section-padding bg-light">

View File

@@ -0,0 +1,50 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}{{ post.title }} - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 800px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item"><a href="{% url 'blog_list' %}">Блог</a></li>
<li class="breadcrumb-item active">{{ post.title }}</li>
</ol>
</nav>
<!-- Post Header -->
<div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">{{ post.title }}</h1>
<div class="d-flex justify-content-center align-items-center gap-3 text-muted">
<span><i class="far fa-calendar"></i> {{ post.published_date|date:"d.m.Y" }}</span>
{% if post.author %}
<span><i class="far fa-user"></i> {{ post.author.username }}</span>
{% endif %}
<span><i class="far fa-eye"></i> {{ post.views }} просмотров</span>
</div>
</div>
<!-- Post Image -->
{% if post.image %}
<div class="mb-5">
<img src="{{ post.image.url }}" class="img-fluid rounded" alt="{{ post.title }}" style="width: 100%; max-height: 500px; object-fit: cover;">
</div>
{% endif %}
<!-- Post Content -->
<div class="post-content mb-5">
{{ post.content|linebreaks }}
</div>
<!-- Back to Blog -->
<div class="text-center mt-5">
<a href="{% url 'blog_list' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> Вернуться к блогу
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Блог - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<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">
📝 Блог
</span>
<h2 class="display-5 fw-bold mb-3">
Наш <span class="text-gradient">блог</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Статьи, советы и идеи от нашей команды
</p>
</div>
<div class="row g-4">
{% if posts %}
{% for post in posts %}
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-modern h-100">
{% if post.image %}
<img src="{{ post.image.url }}" class="card-img-top" alt="{{ post.title }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="card-img-top bg-gradient-primary d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-blog fa-3x text-white"></i>
</div>
{% endif %}
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<small class="text-muted">
<i class="far fa-calendar"></i> {{ post.published_date|date:"d.m.Y" }}
</small>
{% if post.author %}
<small class="text-muted">
<i class="far fa-user"></i> {{ post.author.username }}
</small>
{% endif %}
</div>
<h5 class="card-title">{{ post.title }}</h5>
{% if post.excerpt %}
<p class="card-text text-muted">{{ post.excerpt|truncatewords:20 }}</p>
{% else %}
<p class="card-text text-muted">{{ post.content|truncatewords:20|striptags }}</p>
{% endif %}
<div class="d-flex justify-content-between align-items-center">
<a href="{% url 'blog_detail' post.slug %}" class="btn btn-primary btn-sm">
Читать далее <i class="fas fa-arrow-right ms-1"></i>
</a>
<small class="text-muted">
<i class="far fa-eye"></i> {{ post.views }}
</small>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-5">
<i class="fas fa-inbox fa-4x text-muted mb-3"></i>
<p class="text-muted">Пока нет опубликованных постов</p>
</div>
{% endif %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,96 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}{{ vacancy.title }} - Вакансии - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 900px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item"><a href="{% url 'career_list' %}">Вакансии</a></li>
<li class="breadcrumb-item active">{{ vacancy.title }}</li>
</ol>
</nav>
<!-- Vacancy Header -->
<div class="mb-5">
<h1 class="display-5 fw-bold mb-3">{{ vacancy.title }}</h1>
<div class="d-flex flex-wrap gap-3 mb-3">
{% if vacancy.location %}
<span class="text-muted">
<i class="fas fa-map-marker-alt"></i> {{ vacancy.location }}
</span>
{% endif %}
<span class="badge bg-primary">
{{ vacancy.get_employment_type_display }}
</span>
<span class="text-muted">
<i class="far fa-calendar"></i> Опубликовано: {{ vacancy.posted_at|date:"d.m.Y" }}
</span>
</div>
{% if vacancy.salary_min or vacancy.salary_max %}
<div class="mb-3">
<strong class="text-primary h4">
{% if vacancy.salary_min and vacancy.salary_max %}
${{ vacancy.salary_min|floatformat:0 }} - ${{ vacancy.salary_max|floatformat:0 }}
{% elif vacancy.salary_min %}
От ${{ vacancy.salary_min|floatformat:0 }}
{% else %}
До ${{ vacancy.salary_max|floatformat:0 }}
{% endif %}
</strong>
</div>
{% endif %}
</div>
<!-- Responsibilities -->
<div class="card-modern mb-4">
<div class="card-body">
<h3 class="mb-4"><i class="fas fa-tasks text-primary me-2"></i> Обязанности</h3>
<div class="vacancy-content">
{{ vacancy.responsibilities|linebreaks }}
</div>
</div>
</div>
<!-- Requirements -->
<div class="card-modern mb-4">
<div class="card-body">
<h3 class="mb-4"><i class="fas fa-check-circle text-success me-2"></i> Требования</h3>
<div class="vacancy-content">
{{ vacancy.requirements|linebreaks }}
</div>
</div>
</div>
<!-- Desirable -->
{% if vacancy.desirable %}
<div class="card-modern mb-4">
<div class="card-body">
<h3 class="mb-4"><i class="fas fa-star text-warning me-2"></i> Будет плюсом</h3>
<div class="vacancy-content">
{{ vacancy.desirable|linebreaks }}
</div>
</div>
</div>
{% endif %}
<!-- Apply Button -->
<div class="text-center mt-5 mb-4">
<a href="mailto:hr@smartsoltech.kr?subject=Отклик на вакансию: {{ vacancy.title }}" class="btn btn-primary btn-lg">
<i class="fas fa-paper-plane me-2"></i> Откликнуться на вакансию
</a>
</div>
<!-- Back to Careers -->
<div class="text-center mt-4">
<a href="{% url 'career_list' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> Все вакансии
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,77 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Вакансии - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<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">
💼 Карьера
</span>
<h2 class="display-5 fw-bold mb-3">
Открытые <span class="text-gradient">вакансии</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Присоединяйтесь к нашей команде профессионалов
</p>
</div>
<div class="row justify-content-center">
<div class="col-lg-10">
{% if vacancies %}
{% for vacancy in vacancies %}
<div class="card-modern mb-4" data-aos="fade-up" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h4 class="mb-2">{{ vacancy.title }}</h4>
<div class="d-flex gap-3 mb-3">
{% if vacancy.location %}
<span class="text-muted">
<i class="fas fa-map-marker-alt"></i> {{ vacancy.location }}
</span>
{% endif %}
<span class="badge bg-primary">
{{ vacancy.get_employment_type_display }}
</span>
</div>
<p class="text-muted mb-0">
{{ vacancy.responsibilities|truncatewords:30|striptags }}
</p>
</div>
<div class="col-md-4 text-md-end d-flex flex-column justify-content-center">
{% if vacancy.salary_min or vacancy.salary_max %}
<div class="mb-3">
<strong class="text-primary">
{% if vacancy.salary_min and vacancy.salary_max %}
${{ vacancy.salary_min|floatformat:0 }} - ${{ vacancy.salary_max|floatformat:0 }}
{% elif vacancy.salary_min %}
От ${{ vacancy.salary_min|floatformat:0 }}
{% else %}
До ${{ vacancy.salary_max|floatformat:0 }}
{% endif %}
</strong>
</div>
{% endif %}
<a href="{% url 'career_detail' vacancy.slug %}" class="btn btn-primary">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-briefcase fa-4x text-muted mb-3"></i>
<h4 class="text-muted">В данный момент нет открытых вакансий</h4>
<p class="text-muted">Следите за обновлениями или отправьте инициативное резюме</p>
</div>
{% endif %}
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -66,20 +66,30 @@
</a>
</li>
<li class="mb-2">
<a href="#" class="text-light opacity-75 text-decoration-none hover-primary">
<a href="{% url 'portfolio_list' %}" class="text-light opacity-75 text-decoration-none hover-primary">
Портфолио
</a>
</li>
<li class="mb-2">
<a href="#" class="text-light opacity-75 text-decoration-none hover-primary">
<a href="{% url 'about' %}#team" class="text-light opacity-75 text-decoration-none hover-primary">
Команда
</a>
</li>
<li class="mb-2">
<a href="#" class="text-light opacity-75 text-decoration-none hover-primary">
<a href="{% url 'career_list' %}" class="text-light opacity-75 text-decoration-none hover-primary">
Карьера
</a>
</li>
<li class="mb-2">
<a href="{% url 'blog_list' %}" class="text-light opacity-75 text-decoration-none hover-primary">
Блог
</a>
</li>
<li class="mb-2">
<a href="{% url 'news_list' %}" class="text-light opacity-75 text-decoration-none hover-primary">
Новости
</a>
</li>
</ul>
</div>
@@ -134,12 +144,12 @@
<div class="d-md-flex justify-content-md-end">
<ul class="list-inline mb-0">
<li class="list-inline-item">
<a href="#" class="text-light opacity-75 text-decoration-none hover-primary small">
<a href="{% url 'privacy_policy' %}" class="text-light opacity-75 text-decoration-none hover-primary small">
Политика конфиденциальности
</a>
</li>
<li class="list-inline-item ms-3">
<a href="#" class="text-light opacity-75 text-decoration-none hover-primary small">
<a href="{% url 'terms_of_use' %}" class="text-light opacity-75 text-decoration-none hover-primary small">
Условия использования
</a>
</li>

View File

@@ -256,6 +256,316 @@
</div>
</section>
<!-- Portfolio Section -->
<section class="section-padding bg-light">
<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">
💼 Портфолио
</span>
<h2 class="display-5 fw-bold mb-3">
Наши <span class="text-gradient">работы</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Избранные проекты, которыми мы гордимся
</p>
</div>
<div class="row g-4" id="portfolio-preview">
{% if featured_portfolio %}
{% for item in featured_portfolio %}
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-modern h-100 overflow-hidden">
{% if item.image %}
<img src="{{ item.image.url }}" class="card-img-top" alt="{{ item.title }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="card-img-top bg-gradient-primary d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-briefcase fa-3x text-white"></i>
</div>
{% endif %}
<div class="card-body">
{% if item.category %}
<span class="badge bg-primary mb-2">{{ item.category.name }}</span>
{% endif %}
<h5 class="card-title">{{ item.title }}</h5>
<p class="card-text text-muted">{{ item.description|truncatewords:10 }}</p>
<a href="{% url 'portfolio_detail' item.slug %}" class="btn btn-outline-primary btn-sm">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
{% endfor %}
{% else %}
<!-- Fallback content if no portfolio items -->
<div class="col-lg-4 col-md-6" data-aos="fade-up">
<div class="card-modern h-100 overflow-hidden">
<div class="card-img-top bg-gradient-primary d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-briefcase fa-3x text-white"></i>
</div>
<div class="card-body">
<span class="badge bg-primary mb-2">Веб-разработка</span>
<h5 class="card-title">Корпоративный сайт</h5>
<p class="card-text text-muted">Современный корпоративный сайт с CMS</p>
<a href="{% url 'portfolio_list' %}" class="btn btn-outline-primary btn-sm">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="100">
<div class="card-modern h-100 overflow-hidden">
<div class="card-img-top bg-gradient-accent d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-mobile-alt fa-3x text-white"></i>
</div>
<div class="card-body">
<span class="badge bg-success mb-2">Мобильные приложения</span>
<h5 class="card-title">E-commerce приложение</h5>
<p class="card-text text-muted">Мобильное приложение для онлайн-торговли</p>
<a href="{% url 'portfolio_list' %}" class="btn btn-outline-primary btn-sm">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="200">
<div class="card-modern h-100 overflow-hidden">
<div class="card-img-top bg-gradient-success d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-chart-line fa-3x text-white"></i>
</div>
<div class="card-body">
<span class="badge bg-warning mb-2">Аналитика</span>
<h5 class="card-title">Система аналитики</h5>
<p class="card-text text-muted">Платформа для бизнес-аналитики</p>
<a href="{% url 'portfolio_list' %}" class="btn btn-outline-primary btn-sm">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
{% endif %}
</div>
<div class="text-center mt-5">
<a href="{% url 'portfolio_list' %}" class="btn btn-primary-modern btn-lg">
Смотреть все проекты <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
</section>
<!-- Blog & News Section -->
<section class="section-padding">
<div class="container-modern">
<div class="row g-5">
<!-- Latest Blog Posts -->
<div class="col-lg-6">
<div class="mb-4">
<span class="badge bg-primary mb-2">📝 Блог</span>
<h3 class="fw-bold mb-3">Последние статьи</h3>
</div>
<div class="d-flex flex-column gap-3">
{% if recent_blog_posts %}
{% for post in recent_blog_posts %}
<div class="card-modern border-start border-primary border-4" data-aos="fade-right" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> {{ post.published_date|date:"d.m.Y" }}
</small>
<h6 class="mt-2 mb-2">{{ post.title }}</h6>
<p class="small text-muted mb-2">
{% if post.excerpt %}
{{ post.excerpt|truncatewords:15 }}
{% else %}
{{ post.content|truncatewords:15|striptags }}
{% endif %}
</p>
<a href="{% url 'blog_detail' post.slug %}" class="text-primary text-decoration-none small">
Читать далее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="card-modern border-start border-primary border-4" data-aos="fade-right">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> 20 ноября 2025
</small>
<h6 class="mt-2 mb-2">Тренды веб-разработки 2025</h6>
<p class="small text-muted mb-2">Обзор главных технологий и подходов в современной веб-разработке...</p>
<a href="{% url 'blog_list' %}" class="text-primary text-decoration-none small">
Читать далее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
<div class="card-modern border-start border-primary border-4" data-aos="fade-right" data-aos-delay="100">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> 15 ноября 2025
</small>
<h6 class="mt-2 mb-2">Как выбрать стек технологий</h6>
<p class="small text-muted mb-2">Практические советы по выбору технологий для вашего проекта...</p>
<a href="{% url 'blog_list' %}" class="text-primary text-decoration-none small">
Читать далее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
{% endif %}
</div>
<div class="mt-4">
<a href="{% url 'blog_list' %}" class="btn btn-outline-primary">
Все статьи <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
<!-- Latest News -->
<div class="col-lg-6">
<div class="mb-4">
<span class="badge bg-success mb-2">📰 Новости</span>
<h3 class="fw-bold mb-3">Последние новости</h3>
</div>
<div class="d-flex flex-column gap-3">
{% if recent_news %}
{% for article in recent_news %}
<div class="card-modern border-start border-success border-4" data-aos="fade-left" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> {{ article.published_date|date:"d.m.Y" }}
</small>
<h6 class="mt-2 mb-2">{{ article.title }}</h6>
<p class="small text-muted mb-2">
{% if article.excerpt %}
{{ article.excerpt|truncatewords:15 }}
{% else %}
{{ article.content|truncatewords:15|striptags }}
{% endif %}
</p>
<a href="{% url 'news_detail' article.slug %}" class="text-success text-decoration-none small">
Узнать больше <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="card-modern border-start border-success border-4" data-aos="fade-left">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> 22 ноября 2025
</small>
<h6 class="mt-2 mb-2">Новый проект запущен</h6>
<p class="small text-muted mb-2">Мы рады объявить о запуске нового масштабного проекта...</p>
<a href="{% url 'news_list' %}" class="text-success text-decoration-none small">
Узнать больше <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
<div class="card-modern border-start border-success border-4" data-aos="fade-left" data-aos-delay="100">
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> 18 ноября 2025
</small>
<h6 class="mt-2 mb-2">Расширение команды</h6>
<p class="small text-muted mb-2">SmartSolTech открывает новые вакансии для талантливых специалистов...</p>
<a href="{% url 'news_list' %}" class="text-success text-decoration-none small">
Узнать больше <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
{% endif %}
</div>
<div class="mt-4">
<a href="{% url 'news_list' %}" class="btn btn-outline-success">
Все новости <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Career Section -->
<section class="section-padding bg-light">
<div class="container-modern">
<div class="row align-items-center g-5">
<div class="col-lg-6" data-aos="fade-right">
<span class="badge bg-gradient text-white mb-3 px-3 py-2 rounded-pill">
🚀 Карьера
</span>
<h2 class="display-6 fw-bold mb-4">
Присоединяйтесь к <span class="text-gradient">нашей команде</span>
</h2>
<p class="lead text-muted mb-4">
Мы ищем талантливых специалистов, которые разделяют нашу страсть к технологиям и инновациям.
</p>
<div class="d-flex flex-column gap-3 mb-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary text-white rounded-3 me-3">
<i class="fas fa-chart-line"></i>
</div>
<div>
<h6 class="mb-1">Профессиональный рост</h6>
<p class="text-muted small mb-0">Возможности для развития и обучения</p>
</div>
</div>
<div class="d-flex align-items-start">
<div class="feature-icon bg-success text-white rounded-3 me-3">
<i class="fas fa-users"></i>
</div>
<div>
<h6 class="mb-1">Команда профессионалов</h6>
<p class="text-muted small mb-0">Работайте с лучшими специалистами</p>
</div>
</div>
<div class="d-flex align-items-start">
<div class="feature-icon bg-warning text-white rounded-3 me-3">
<i class="fas fa-laptop-house"></i>
</div>
<div>
<h6 class="mb-1">Гибкий график</h6>
<p class="text-muted small mb-0">Удаленная работа и гибкое расписание</p>
</div>
</div>
</div>
<a href="{% url 'career_list' %}" class="btn btn-primary-modern btn-lg">
Смотреть вакансии <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
<div class="col-lg-6" data-aos="fade-left">
<div class="position-relative">
<div class="card-modern p-5 text-center bg-gradient text-white">
<i class="fas fa-briefcase fa-5x mb-4 opacity-50"></i>
<h4 class="fw-bold mb-3">Открыто вакансий</h4>
<div class="display-4 fw-bold mb-3">
{% if active_vacancies_count > 0 %}
{{ active_vacancies_count }}+
{% else %}
0
{% endif %}
</div>
<p class="mb-4 opacity-90">Найдите свою идеальную позицию</p>
<a href="{% url 'career_list' %}" class="btn btn-light btn-lg">
Посмотреть все
</a>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="section-padding bg-gradient text-white">
<div class="container-modern text-center">

View File

@@ -0,0 +1,47 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}{{ article.title }} - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 800px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item"><a href="{% url 'news_list' %}">Новости</a></li>
<li class="breadcrumb-item active">{{ article.title }}</li>
</ol>
</nav>
<!-- Article Header -->
<div class="text-center mb-5">
<span class="badge bg-primary mb-3">Новость</span>
<h1 class="display-5 fw-bold mb-3">{{ article.title }}</h1>
<div class="text-muted">
<i class="far fa-calendar"></i> {{ article.published_date|date:"d.m.Y H:i" }}
</div>
</div>
<!-- Article Image -->
{% if article.image %}
<div class="mb-5">
<img src="{{ article.image.url }}" class="img-fluid rounded" alt="{{ article.title }}" style="width: 100%; max-height: 500px; object-fit: cover;">
</div>
{% endif %}
<!-- Article Content -->
<div class="article-content mb-5">
{{ article.content|linebreaks }}
</div>
<!-- Back to News -->
<div class="text-center mt-5">
<a href="{% url 'news_list' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> Все новости
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Новости - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<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">
📰 Новости
</span>
<h2 class="display-5 fw-bold mb-3">
Последние <span class="text-gradient">новости</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Будьте в курсе всех событий компании
</p>
</div>
<div class="row g-4">
{% if news %}
{% for article in news %}
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-modern h-100">
{% if article.image %}
<img src="{{ article.image.url }}" class="card-img-top" alt="{{ article.title }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="card-img-top bg-gradient-accent d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="fas fa-newspaper fa-3x text-white"></i>
</div>
{% endif %}
<div class="card-body">
<small class="text-muted">
<i class="far fa-calendar"></i> {{ article.published_date|date:"d.m.Y H:i" }}
</small>
<h5 class="card-title mt-2">{{ article.title }}</h5>
{% if article.excerpt %}
<p class="card-text text-muted">{{ article.excerpt }}</p>
{% else %}
<p class="card-text text-muted">{{ article.content|truncatewords:20|striptags }}</p>
{% endif %}
<a href="{% url 'news_detail' article.slug %}" class="btn btn-primary btn-sm">
Читать далее <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-5">
<i class="fas fa-inbox fa-4x text-muted mb-3"></i>
<p class="text-muted">Пока нет новостей</p>
</div>
{% endif %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}{{ item.title }} - Портфолио - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 1000px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item"><a href="{% url 'portfolio_list' %}">Портфолио</a></li>
<li class="breadcrumb-item active">{{ item.title }}</li>
</ol>
</nav>
<!-- Project Header -->
<div class="text-center mb-5">
{% if item.category %}
<span class="badge bg-primary mb-3">{{ item.category.name }}</span>
{% endif %}
<h1 class="display-5 fw-bold mb-3">{{ item.title }}</h1>
{% if item.client_name %}
<p class="text-muted">
<i class="fas fa-user"></i> Клиент: {{ item.client_name }}
</p>
{% endif %}
{% if item.completion_date %}
<p class="text-muted">
<i class="far fa-calendar"></i> Дата завершения: {{ item.completion_date|date:"d.m.Y" }}
</p>
{% endif %}
</div>
<!-- Project Image -->
{% if item.image %}
<div class="mb-5">
<img src="{{ item.image.url }}" class="img-fluid rounded" alt="{{ item.title }}" style="width: 100%; max-height: 600px; object-fit: cover;">
</div>
{% endif %}
<!-- Project Description -->
<div class="card-modern mb-5">
<div class="card-body">
<h3 class="mb-4">О проекте</h3>
<div class="project-description">
{{ item.description|linebreaks }}
</div>
</div>
</div>
<!-- Back to Portfolio -->
<div class="text-center mt-5">
<a href="{% url 'portfolio_list' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> Все проекты
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,83 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Портфолио - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<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">
💼 Портфолио
</span>
<h2 class="display-5 fw-bold mb-3">
Наши <span class="text-gradient">работы</span>
</h2>
<p class="lead text-muted max-width-600 mx-auto">
Проекты, которыми мы гордимся
</p>
</div>
<!-- Category Filters -->
{% if categories %}
<div class="text-center mb-4">
<div class="btn-group" role="group">
<a href="{% url 'portfolio_list' %}" class="btn btn-outline-primary {% if not request.GET.category %}active{% endif %}">
Все
</a>
{% for category in categories %}
<a href="?category={{ category.id }}" class="btn btn-outline-primary {% if request.GET.category == category.id|stringformat:'s' %}active{% endif %}">
{{ category.name }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
<div class="row g-4">
{% if items %}
{% for item in items %}
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="{{ forloop.counter0|add:'00' }}">
<div class="card-modern h-100 overflow-hidden">
{% if item.image %}
<img src="{{ item.image.url }}" class="card-img-top" alt="{{ item.title }}" style="height: 250px; object-fit: cover;">
{% else %}
<div class="card-img-top bg-gradient-primary d-flex align-items-center justify-content-center" style="height: 250px;">
<i class="fas fa-briefcase fa-3x text-white"></i>
</div>
{% endif %}
<div class="card-body">
{% if item.category %}
<span class="badge bg-primary mb-2">{{ item.category.name }}</span>
{% endif %}
<h5 class="card-title">{{ item.title }}</h5>
{% if item.client_name %}
<p class="text-muted small mb-2">
<i class="fas fa-user"></i> {{ item.client_name }}
</p>
{% endif %}
<p class="card-text text-muted">{{ item.description|truncatewords:15 }}</p>
<div class="d-flex justify-content-between align-items-center">
<a href="{% url 'portfolio_detail' item.slug %}" class="btn btn-primary btn-sm">
Подробнее <i class="fas fa-arrow-right ms-1"></i>
</a>
{% if item.completion_date %}
<small class="text-muted">
{{ item.completion_date|date:"Y" }}
</small>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12 text-center py-5">
<i class="fas fa-inbox fa-4x text-muted mb-3"></i>
<p class="text-muted">Портфолио пусто</p>
</div>
{% endif %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Политика конфиденциальности - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 900px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item active">Политика конфиденциальности</li>
</ol>
</nav>
{% if policy %}
<!-- Header -->
<div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">Политика конфиденциальности</h1>
<p class="text-muted">
Версия {{ policy.version }} | Действует с {{ policy.effective_date|date:"d.m.Y" }}
</p>
</div>
<!-- Content -->
<div class="card-modern">
<div class="card-body">
<div class="legal-content">
{{ policy.content|linebreaks }}
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-file-alt fa-4x text-muted mb-3"></i>
<h4 class="text-muted">Политика конфиденциальности не найдена</h4>
<p class="text-muted">Содержимое будет добавлено в ближайшее время</p>
</div>
{% endif %}
<!-- Back Button -->
<div class="text-center mt-5">
<a href="{% url 'home' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> На главную
</a>
</div>
</div>
</section>
<style>
.legal-content {
line-height: 1.8;
}
.legal-content h2, .legal-content h3, .legal-content h4 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.legal-content ul, .legal-content ol {
margin-bottom: 1.5rem;
}
</style>
{% endblock %}

View File

@@ -65,7 +65,7 @@
{% 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;">
<a href="tg://resolve?domain={{ member.telegram }}" title="Открыть в Telegram: @{{ member.telegram }}" class="btn btn-outline-primary btn-sm rounded-circle" style="width: 40px; height: 40px;">
<i class="fab fa-telegram-plane"></i>
</a>
{% endif %}

View File

@@ -0,0 +1,63 @@
{% extends 'web/base_modern.html' %}
{% load static %}
{% block title %}Условия использования - SmartSolTech{% endblock %}
{% block content %}
<section class="section-padding">
<div class="container" style="max-width: 900px;">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Главная</a></li>
<li class="breadcrumb-item active">Условия использования</li>
</ol>
</nav>
{% if terms %}
<!-- Header -->
<div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">Условия использования</h1>
<p class="text-muted">
Версия {{ terms.version }} | Действует с {{ terms.effective_date|date:"d.m.Y" }}
</p>
</div>
<!-- Content -->
<div class="card-modern">
<div class="card-body">
<div class="legal-content">
{{ terms.content|linebreaks }}
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-file-contract fa-4x text-muted mb-3"></i>
<h4 class="text-muted">Условия использования не найдены</h4>
<p class="text-muted">Содержимое будет добавлено в ближайшее время</p>
</div>
{% endif %}
<!-- Back Button -->
<div class="text-center mt-5">
<a href="{% url 'home' %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i> На главную
</a>
</div>
</div>
</section>
<style>
.legal-content {
line-height: 1.8;
}
.legal-content h2, .legal-content h3, .legal-content h4 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.legal-content ul, .legal-content ol {
margin-bottom: 1.5rem;
}
</style>
{% endblock %}

View File

@@ -24,6 +24,26 @@ urlpatterns = [
# path('order/<int:pk>/', views.order_detail, name='order_detail'),
path('service/send_telegram_notification/', views.send_telegram_notification, name='send_telegram_notification'),
# path('service/create_request/', views.create_service_request_basic, name='create_service_request_basic'),
# Blog
path('blog/', views.blog_list, name='blog_list'),
path('blog/<slug:slug>/', views.blog_detail, name='blog_detail'),
# News
path('news/', views.news_list, name='news_list'),
path('news/<slug:slug>/', views.news_detail, name='news_detail'),
# Portfolio
path('portfolio/', views.portfolio_list, name='portfolio_list'),
path('portfolio/<slug:slug>/', views.portfolio_detail, name='portfolio_detail'),
# Career
path('career/', views.career_list, name='career_list'),
path('career/<slug:slug>/', views.career_detail, name='career_detail'),
# Legal pages
path('privacy/', views.privacy_policy, name='privacy_policy'),
path('terms/', views.terms_of_use, name='terms_of_use'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,5 +1,9 @@
from django.shortcuts import render, get_object_or_404, redirect
from .models import Service, Project, Client, BlogPost, Review, Order, ServiceRequest, Category, AboutPage, FooterSettings, TeamMember
from .models import (
Service, Project, Client, BlogPost, Review, Order, ServiceRequest,
Category, AboutPage, FooterSettings, TeamMember,
NewsArticle, CareerVacancy, PortfolioItem, PrivacyPolicy, TermsOfUse
)
from django.db.models import Avg
from comunication.models import TelegramSettings
import qrcode
@@ -36,7 +40,28 @@ except Exception as e:
def home(request):
services = Service.objects.all()[:6] # Показываем только первые 6 услуг на главной
return render(request, 'web/home_modern.html', {'services': services})
# Последние посты блога
recent_blog_posts = BlogPost.objects.filter(status=BlogPost.PUBLISHED).order_by('-published_date')[:2]
# Последние новости
recent_news = NewsArticle.objects.filter(is_published=True).order_by('-published_date')[:2]
# Избранные проекты портфолио
featured_portfolio = PortfolioItem.objects.filter(featured=True, is_active=True)[:3]
# Количество активных вакансий
active_vacancies_count = CareerVacancy.objects.filter(is_active=True).count()
context = {
'services': services,
'recent_blog_posts': recent_blog_posts,
'recent_news': recent_news,
'featured_portfolio': featured_portfolio,
'active_vacancies_count': active_vacancies_count,
}
return render(request, 'web/home_modern.html', context)
def service_detail(request, pk):
service = get_object_or_404(Service, pk=pk)
@@ -353,3 +378,79 @@ def check_request_status(request, request_id):
except Exception as e:
logger.error(f"Ошибка при проверке статуса заявки {request_id}: {str(e)}")
return JsonResponse({'error': 'Ошибка сервера'}, status=500)
# ========== Blog Views ==========
def blog_list(request):
"""Список всех опубликованных постов блога"""
posts = BlogPost.objects.filter(status=BlogPost.PUBLISHED).order_by('-published_date')
return render(request, 'web/blog_list.html', {'posts': posts})
def blog_detail(request, slug):
"""Детальная страница поста блога"""
post = get_object_or_404(BlogPost, slug=slug, status=BlogPost.PUBLISHED)
post.views += 1
post.save(update_fields=['views'])
return render(request, 'web/blog_detail.html', {'post': post})
# ========== News Views ==========
def news_list(request):
"""Список всех опубликованных новостей"""
news = NewsArticle.objects.filter(is_published=True).order_by('-published_date')
return render(request, 'web/news_list.html', {'news': news})
def news_detail(request, slug):
"""Детальная страница новости"""
article = get_object_or_404(NewsArticle, slug=slug, is_published=True)
return render(request, 'web/news_detail.html', {'article': article})
# ========== Portfolio Views ==========
def portfolio_list(request):
"""Список всех активных элементов портфолио"""
category_id = request.GET.get('category')
items = PortfolioItem.objects.filter(is_active=True)
if category_id:
items = items.filter(category_id=category_id)
categories = Category.objects.all()
return render(request, 'web/portfolio_list.html', {
'items': items,
'categories': categories
})
def portfolio_detail(request, slug):
"""Детальная страница элемента портфолио"""
item = get_object_or_404(PortfolioItem, slug=slug, is_active=True)
return render(request, 'web/portfolio_detail.html', {'item': item})
# ========== Career Views ==========
def career_list(request):
"""Список всех активных вакансий"""
vacancies = CareerVacancy.objects.filter(is_active=True).order_by('-posted_at')
return render(request, 'web/career_list.html', {'vacancies': vacancies})
def career_detail(request, slug):
"""Детальная страница вакансии"""
vacancy = get_object_or_404(CareerVacancy, slug=slug, is_active=True)
return render(request, 'web/career_detail.html', {'vacancy': vacancy})
# ========== Legal Pages Views ==========
def privacy_policy(request):
"""Страница политики конфиденциальности"""
policy = PrivacyPolicy.objects.filter(is_active=True).first()
return render(request, 'web/privacy_policy.html', {'policy': policy})
def terms_of_use(request):
"""Страница условий использования"""
terms = TermsOfUse.objects.filter(is_active=True).first()
return render(request, 'web/terms_of_use.html', {'terms': terms})