Fix singleton admin: ensure Save button appears by checking count instead of filter

This commit is contained in:
2025-11-24 15:22:01 +09:00
parent 4502862c74
commit 9f313487de
5 changed files with 218 additions and 13 deletions

View File

@@ -74,6 +74,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages',
'web.context_processors.footer_settings', # Custom context processor
'web.context_processors.site_settings', # Site settings (currency, etc.)
'web.context_processors.contact_settings', # Contact settings
],
},
},

View File

@@ -3,7 +3,7 @@ from .models import (
Service, Project, Client, Order, Review, BlogPost,
Category, ServiceRequest, AboutPage, FooterSettings, TeamMember,
PortfolioItem, PrivacyPolicy, TermsOfUse, NewsArticle, CareerVacancy,
SiteSettings, PortfolioImage
SiteSettings, PortfolioImage, ContactSettings
)
from .forms import ProjectForm
@@ -163,7 +163,12 @@ class AboutPageAdmin(admin.ModelAdmin):
)
def has_add_permission(self, request):
return not AboutPage.objects.filter(is_active=True).exists()
# Разрешить создание если нет ни одной записи
return AboutPage.objects.count() == 0
def has_delete_permission(self, request, obj=None):
# Запретить удаление
return False
@admin.register(FooterSettings)
@@ -209,7 +214,12 @@ class FooterSettingsAdmin(admin.ModelAdmin):
)
def has_add_permission(self, request):
return not FooterSettings.objects.filter(is_active=True).exists()
# Разрешить создание если нет ни одной записи
return FooterSettings.objects.count() == 0
def has_delete_permission(self, request, obj=None):
# Запретить удаление единственной активной записи
return False
@admin.register(TeamMember)
@@ -259,3 +269,53 @@ class SiteSettingsAdmin(admin.ModelAdmin):
return False
@admin.register(ContactSettings)
class ContactSettingsAdmin(admin.ModelAdmin):
list_display = ('company_name', 'email', 'phone', 'telegram', 'updated_at')
fieldsets = (
('📋 Основная информация', {
'fields': ('company_name',),
'description': 'Название компании'
}),
('📞 Основные контакты', {
'fields': ('email', 'phone', 'telegram', 'whatsapp'),
'description': 'Главные каналы связи с клиентами'
}),
('📍 Адрес и режим работы', {
'fields': ('address', 'working_hours'),
}),
('🌐 Социальные сети', {
'fields': (
'telegram_url',
'instagram_url',
'linkedin_url',
'facebook_url',
'twitter_url',
'youtube_url',
'github_url'
),
'classes': ('collapse',),
'description': 'Ссылки на страницы в социальных сетях'
}),
('📧 Дополнительные контакты', {
'fields': ('support_email', 'sales_email', 'emergency_phone'),
'classes': ('collapse',),
'description': 'Специализированные контакты (опционально)'
}),
)
def has_add_permission(self, request):
# Запретить создание новых записей (singleton)
return not ContactSettings.objects.exists()
def has_delete_permission(self, request, obj=None):
# Запретить удаление контактов
return False
class Media:
css = {
'all': ('admin/css/forms.css',)
}

View File

@@ -1,4 +1,4 @@
from .models import FooterSettings, SiteSettings
from .models import FooterSettings, SiteSettings, ContactSettings
def footer_settings(request):
@@ -20,3 +20,11 @@ def site_settings(request):
'site_settings': settings,
'currency_symbol': settings.currency_symbol,
}
def contact_settings(request):
"""Context processor для контактных данных"""
contacts = ContactSettings.get_contacts()
return {
'contact_settings': contacts,
}

View File

@@ -631,5 +631,138 @@ class SiteSettings(models.Model):
return settings
class ContactSettings(models.Model):
"""Контактная информация компании"""
# Основные контакты
company_name = models.CharField(
max_length=200,
default='SmartSolTech',
verbose_name='Название компании'
)
email = models.EmailField(
default='info@smartsoltech.kr',
verbose_name='Email',
help_text='Основной email для связи'
)
phone = models.CharField(
max_length=50,
default='+82-10-XXXX-XXXX',
verbose_name='Телефон',
help_text='Контактный телефон'
)
telegram = models.CharField(
max_length=100,
default='@smartsoltech',
verbose_name='Telegram',
help_text='Telegram username (с @)'
)
whatsapp = models.CharField(
max_length=50,
blank=True,
verbose_name='WhatsApp',
help_text='Номер WhatsApp (опционально)'
)
# Адрес и время работы
address = models.TextField(
default='Seoul, South Korea',
verbose_name='Адрес',
help_text='Полный адрес офиса'
)
working_hours = models.CharField(
max_length=200,
default='Пн-Пт: 9:00-18:00',
verbose_name='Время работы',
help_text='График работы'
)
# Социальные сети
telegram_url = models.URLField(
blank=True,
verbose_name='Telegram URL',
help_text='Ссылка на Telegram канал/группу'
)
instagram_url = models.URLField(
blank=True,
verbose_name='Instagram URL'
)
linkedin_url = models.URLField(
blank=True,
verbose_name='LinkedIn URL'
)
facebook_url = models.URLField(
blank=True,
verbose_name='Facebook URL'
)
twitter_url = models.URLField(
blank=True,
verbose_name='Twitter URL'
)
youtube_url = models.URLField(
blank=True,
verbose_name='YouTube URL'
)
github_url = models.URLField(
blank=True,
verbose_name='GitHub URL'
)
# Дополнительная информация
support_email = models.EmailField(
blank=True,
verbose_name='Email поддержки',
help_text='Отдельный email для техподдержки (опционально)'
)
sales_email = models.EmailField(
blank=True,
verbose_name='Email отдела продаж',
help_text='Email для коммерческих запросов (опционально)'
)
emergency_phone = models.CharField(
max_length=50,
blank=True,
verbose_name='Экстренный телефон',
help_text='Телефон для срочных вопросов (опционально)'
)
# Метаданные
updated_at = models.DateTimeField(
auto_now=True,
verbose_name='Обновлено'
)
class Meta:
verbose_name = 'Контактные данные'
verbose_name_plural = 'Контактные данные'
def __str__(self):
return f'Контакты {self.company_name} (обновлено: {self.updated_at.strftime("%d.%m.%Y")})'
def save(self, *args, **kwargs):
# Singleton pattern - только одна запись
self.pk = 1
super().save(*args, **kwargs)
@classmethod
def get_contacts(cls):
"""Получить контактные данные (создать если не существует)"""
contacts, created = cls.objects.get_or_create(pk=1)
return contacts

View File

@@ -326,8 +326,8 @@
<i class="fas fa-envelope fa-2x text-primary"></i>
</div>
<h5 class="card-title fw-bold mb-3">Email</h5>
<a href="mailto:{{ about.contact_email }}" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ about.contact_email }}</p>
<a href="mailto:{{ contact_settings.email }}" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ contact_settings.email }}</p>
</a>
</div>
</div>
@@ -341,8 +341,8 @@
<i class="fas fa-phone fa-2x text-success"></i>
</div>
<h5 class="card-title fw-bold mb-3">Телефон</h5>
<a href="tel:{{ about.contact_phone }}" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ about.contact_phone }}</p>
<a href="tel:{{ contact_settings.phone }}" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ contact_settings.phone }}</p>
</a>
</div>
</div>
@@ -356,15 +356,15 @@
<i class="fab fa-telegram-plane fa-2x text-info"></i>
</div>
<h5 class="card-title fw-bold mb-3">Telegram</h5>
<a href="https://t.me/{{ about.contact_telegram|cut:'@' }}" target="_blank" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ about.contact_telegram }}</p>
<a href="https://t.me/{{ contact_settings.telegram|cut:'@' }}" target="_blank" class="text-decoration-none">
<p class="card-text text-muted mb-0">{{ contact_settings.telegram }}</p>
</a>
</div>
</div>
</div>
<!-- Address Card (optional) -->
{% if about.contact_address %}
<!-- Address Card -->
{% if contact_settings.address %}
<div class="col-lg-3 col-md-6">
<div class="card h-100 border-0 shadow-sm hover-lift">
<div class="card-body text-center p-4">
@@ -372,7 +372,10 @@
<i class="fas fa-map-marker-alt fa-2x text-warning"></i>
</div>
<h5 class="card-title fw-bold mb-3">Адрес</h5>
<p class="card-text text-muted mb-0">{{ about.contact_address }}</p>
<p class="card-text text-muted mb-0">{{ contact_settings.address }}</p>
{% if contact_settings.working_hours %}
<p class="card-text text-muted small mt-2">{{ contact_settings.working_hours }}</p>
{% endif %}
</div>
</div>
</div>