Merge remote-tracking branch 'origin/master'

This commit is contained in:
NikDizell 2026-02-13 10:52:40 +03:00
commit a6fa9277cc
529 changed files with 86595 additions and 118264 deletions

7
.gitignore vendored
View File

@ -23,4 +23,9 @@ ENV/
# OS
.DS_Store
Thumbs.db
Thumbs.db
# Project specific
*.log
*.pot
*.py[co]

View File

@ -1,16 +0,0 @@
"""
ASGI config for OneCprogsite project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
application = get_asgi_application()

View File

@ -1,212 +0,0 @@
"""
Django settings for OneCprogsite project.
Generated by 'django-admin startproject' using Django 4.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os.path
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
# Безопасность cookies для HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'
# Если используете другие cookies
LANGUAGE_COOKIE_SECURE = True
LANGUAGE_COOKIE_HTTPONLY = True
LANGUAGE_COOKIE_SAMESITE = 'Lax'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Или разрешить конкретные домены (Django 4.0+)
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
# ОБЯЗАТЕЛЬНО укажите ваши домены
ALLOWED_HOSTS = [
'nikdizell.ru',
'www.nikdizell.ru',
'localhost',
'127.0.0.1',
'192.168.31.88' # Добавьте IP сервера
]
# Важно для работы за прокси
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
# Дополнительная безопасность
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
CSRF_TRUSTED_ORIGINS = [
'https://nikdizell.ru',
'https://www.nikdizell.ru',
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'programmer.apps.ProgrammerConfig',
'django_bootstrap5',
'django_extensions',
'django.contrib.sites',
'django.contrib.sitemaps',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'programmer.middleware.PageViewMiddleware',
]
ROOT_URLCONF = 'OneCprogsite.urls'
# Кастомный middleware для CSP
class CSPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
return response
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'programmer.context_processors.menu_processor',
'programmer.context_processors.contact_info',
],
},
},
]
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'App',
'USER': 'postgres',
'PASSWORD': 'NikDi94Zell',
'HOST': 'postgres',
'PORT': 5432,
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = []
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# Настройки email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# SERVER_EMAIL = EMAIL_HOST_USER
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
# Email для уведомлений (можно указать несколько через запятую)
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')

View File

@ -1,39 +0,0 @@
"""
URL configuration for OneCprogsite project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from OneCprogsite import settings
from programmer.views import *
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('programmer.urls')),
# path('', index, name='home'),
# path('about/', about, name='about'),
# path('solution/', solution, name='solution'),
# path('ability/', ability, name='ability'),
# path('recall/', recall, name='recall'),
# path('post/<int:post_id>', show_post, name='post'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
handler404 = pageNotFound

View File

@ -1,16 +0,0 @@
"""
WSGI config for OneCprogsite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
application = get_wsgi_application()

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -1,169 +0,0 @@
from django.contrib import admin
from django.utils import timezone
from django.utils.html import format_html
from django.urls import path
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib import messages
from .models import *
class ProgrammerAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class RecallAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class SolutionAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'description', 'implementation')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class HomeAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
@admin.register(CallbackRequest)
class CallbackAdmin(admin.ModelAdmin):
list_display = ('name', 'phone', 'email', 'time_create', 'is_processed', 'is_read', 'new_badge')
list_display_links = ('name', 'phone')
list_editable = ('is_processed', 'is_read')
list_filter = ('time_create', 'is_processed', 'is_read')
search_fields = ('name', 'phone', 'email')
readonly_fields = ('time_create',)
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed']
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification']
def resend_notification(self, request, queryset):
from .utils.email_notifications import send_callback_notification
count = 0
for callback in queryset:
success = send_callback_notification(callback)
if success:
count += 1
self.message_user(request, f'Уведомления отправлены для {count} заявок')
resend_notification.short_description = "Переотправить email уведомления"
def new_badge(self, obj):
if not obj.is_read:
return format_html('<span style="color: red; font-weight: bold;">🆕 НОВАЯ</span>')
return ""
new_badge.short_description = 'Статус'
def get_queryset(self, request):
# Показываем количество непрочитанных в заголовке
unread_count = CallbackRequest.objects.filter(is_read=False).count()
if unread_count > 0:
self.message_user(
request,
f'У вас {unread_count} непрочитанных заявок!',
messages.WARNING
)
return super().get_queryset(request)
def mark_as_read(self, request, queryset):
updated = queryset.update(is_read=True)
self.message_user(request, f'{updated} заявок отмечены как прочитанные')
mark_as_read.short_description = "Отметить как прочитанные"
def mark_as_unread(self, request, queryset):
updated = queryset.update(is_read=False)
self.message_user(request, f'{updated} заявок отмечены как непрочитанные')
mark_as_unread.short_description = "Отметить как непрочитанные"
def mark_as_processed(self, request, queryset):
updated = queryset.update(is_processed=True)
self.message_user(request, f'{updated} заявок отмечены как обработанные')
mark_as_processed.short_description = "Отметить как обработанные"
# Добавляем кастомное представление для статистики
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('callback-stats/', self.admin_site.admin_view(self.callback_stats), name='callback_stats'),
]
return custom_urls + urls
def callback_stats(self, request):
today = timezone.now().date()
week_ago = today - timezone.timedelta(days=7)
stats = {
'total': CallbackRequest.objects.count(),
'today': CallbackRequest.objects.filter(time_create__date=today).count(),
'week': CallbackRequest.objects.filter(time_create__date__gte=week_ago).count(),
'unread': CallbackRequest.objects.filter(is_read=False).count(),
'unprocessed': CallbackRequest.objects.filter(is_processed=False).count(),
}
context = {
**self.admin_site.each_context(request),
'title': 'Статистика заявок',
'stats': stats,
}
return render(request, 'admin/callback_stats.html', context)
class ProgrammerAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class RecallAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class SolutionAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'description', 'implementation')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class HomeAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
@admin.register(PageView)
class PageViewAdmin(admin.ModelAdmin):
list_display = ['url', 'timestamp', 'ip_address']
list_filter = ['timestamp', 'url']
search_fields = ['url', 'ip_address']
date_hierarchy = 'timestamp'
def get_queryset(self, request):
return super().get_queryset(request).order_by('-timestamp')
admin.site.register(Competence, ProgrammerAdmin)
admin.site.register(Recall, RecallAdmin)
admin.site.register(Solution, SolutionAdmin)
admin.site.register(Home, HomeAdmin)

View File

@ -1,7 +0,0 @@
from django.apps import AppConfig
class ProgrammerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'programmer'
verbose_name = 'Программисты'

View File

@ -1,12 +0,0 @@
from .views import menu
from django.conf import settings
def menu_processor(request):
return {'menu': menu}
def contact_info(request):
return {
'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'),
'CONTACT_PHONE': getattr(settings, 'CONTACT_PHONE', '+7 (960) 469-40-88'),
}

View File

@ -1,33 +0,0 @@
from django import forms
from .models import CallbackRequest
class CallbackForm(forms.ModelForm):
class Meta:
model = CallbackRequest
fields = ['name', 'phone', 'email', 'question']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-input',
'placeholder': 'Ваше имя'
}),
'phone': forms.TextInput(attrs={
'class': 'form-input',
'placeholder': '+7 (___) ___-__-__'
}),
'email': forms.EmailInput(attrs={
'class': 'form-input',
'placeholder': 'your@email.com'
}),
'question': forms.Textarea(attrs={
'class': 'form-textarea',
'placeholder': 'Опишите ваш вопрос или задачу...',
'rows': 4
}),
}
labels = {
'name': 'Имя',
'phone': 'Телефон',
'email': 'Электронная почта',
'question': 'Ваш вопрос'
}

View File

@ -1,33 +0,0 @@
# programmer/management/commands/send_daily_summary.py
from django.core.management.base import BaseCommand
from django.utils import timezone
from programmer.utils.email_notifications import send_daily_summary
class Command(BaseCommand):
help = 'Отправляет ежедневную сводку по заявкам на email'
def add_arguments(self, parser):
parser.add_argument(
'--test',
action='store_true',
help='Тестовая отправка (не учитывает реальные данные)',
)
def handle(self, *args, **options):
if options['test']:
self.stdout.write(self.style.WARNING('Тестовая отправка ежедневной сводки...'))
# Здесь можно добавить тестовые данные
else:
self.stdout.write('Отправка ежедневной сводки...')
success = send_daily_summary()
if success:
self.stdout.write(
self.style.SUCCESS(f'Ежедневная сводка отправлена успешно! Время: {timezone.now()}')
)
else:
self.stdout.write(
self.style.WARNING('Ежедневная сводка не отправлена (нет данных или ошибка)')
)

View File

@ -1,29 +0,0 @@
# programmer/management/commands/test_email.py
from django.core.management.base import BaseCommand
from django.conf import settings
from programmer.utils.email_notifications import send_test_email
class Command(BaseCommand):
help = 'Test email configuration'
def handle(self, *args, **options):
self.stdout.write("Testing email configuration...")
# Проверяем настройки
self.stdout.write(f"EMAIL_HOST: {settings.EMAIL_HOST}")
self.stdout.write(f"EMAIL_PORT: {settings.EMAIL_PORT}")
self.stdout.write(f"EMAIL_HOST_USER: {settings.EMAIL_HOST_USER}")
self.stdout.write(f"ADMIN_EMAILS: {settings.ADMIN_EMAILS}")
# Тестируем отправку
success = send_test_email()
if success:
self.stdout.write(
self.style.SUCCESS('✅ Test email sent successfully!')
)
else:
self.stdout.write(
self.style.ERROR('❌ Failed to send test email. Check your email settings.')
)

View File

@ -1,27 +0,0 @@
from django.core.management.base import BaseCommand
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from programmer.models import Home, Solution, Competence, Recall
class Command(BaseCommand):
help = 'Test sitemap generation'
def handle(self, *args, **options):
from programmer.sitemaps import sitemaps
self.stdout.write('Testing sitemap generation...')
for name, sitemap in sitemaps.items():
self.stdout.write(f'\n{name}:')
items = sitemap().items()
self.stdout.write(f' Items found: {len(items)}')
for item in items[:3]: # Показываем первые 3 элемента
try:
url = sitemap().location(item)
self.stdout.write(f' - {url}')
except Exception as e:
self.stdout.write(f' - Error: {e}')
self.stdout.write('\nSitemap test completed!')

View File

@ -1,54 +0,0 @@
from .models import PageView, Visitor
from django.utils import timezone
from django.db import transaction
class PageViewMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Игнорируем статические файлы и админку
if not request.path.startswith('/static/') and not request.path.startswith('/admin/'):
self.track_page_view(request)
response = self.get_response(request)
return response
def track_page_view(self, request):
try:
with transaction.atomic():
# Сохраняем просмотр страницы
PageView.objects.create(
url=request.path,
ip_address=self.get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
referer=request.META.get('HTTP_REFERER', '')
)
# Обновляем статистику посетителя
ip = self.get_client_ip(request)
visitor, created = Visitor.objects.get_or_create(
ip_address=ip,
defaults={
'first_visit': timezone.now(),
'last_visit': timezone.now()
}
)
if not created:
visitor.last_visit = timezone.now()
visitor.visit_count += 1
visitor.save()
except Exception as e:
# Логируем ошибку, но не прерываем выполнение
print(f"Error tracking page view: {e}")
def get_client_ip(self, request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

View File

@ -1,26 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-23 12:47
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Competence',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('content', models.TextField(blank=True)),
('photo', models.ImageField(upload_to='photos/%Y/%m/%d/')),
('time_create', models.DateTimeField(auto_now_add=True)),
('time_update', models.DateTimeField(auto_now=True)),
('is_publiched', models.BooleanField(default=True)),
],
),
]

View File

@ -1,47 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-24 08:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='competence',
options={'ordering': ['time_create', 'title'], 'verbose_name': 'Компитенция', 'verbose_name_plural': 'Компитенции'},
),
migrations.AlterField(
model_name='competence',
name='content',
field=models.TextField(blank=True, verbose_name='Компетенция'),
),
migrations.AlterField(
model_name='competence',
name='is_publiched',
field=models.BooleanField(default=True, verbose_name='Опубликован'),
),
migrations.AlterField(
model_name='competence',
name='photo',
field=models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Фото'),
),
migrations.AlterField(
model_name='competence',
name='time_create',
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания'),
),
migrations.AlterField(
model_name='competence',
name='time_update',
field=models.DateTimeField(auto_now=True, verbose_name='Дата изменения'),
),
migrations.AlterField(
model_name='competence',
name='title',
field=models.CharField(max_length=255, verbose_name='Программист'),
),
]

View File

@ -1,35 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-24 11:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0002_alter_competence_options_alter_competence_content_and_more'),
]
operations = [
migrations.CreateModel(
name='Recall',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Организация')),
('content', models.TextField(blank=True, verbose_name='Отзыв')),
('photo', models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Фото')),
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
],
options={
'verbose_name': 'Отзыв',
'verbose_name_plural': 'Отзывы',
'ordering': ['time_create', 'title'],
},
),
migrations.RenameField(
model_name='competence',
old_name='is_publiched',
new_name='is_published',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-24 12:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('programmer', '0003_recall_rename_is_publiched_competence_is_published'),
]
operations = [
migrations.RenameField(
model_name='recall',
old_name='photo',
new_name='scan',
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-24 12:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0004_rename_photo_recall_scan'),
]
operations = [
migrations.CreateModel(
name='Recall',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Организация')),
('content', models.TextField(blank=True, verbose_name='Отзыв')),
('scan', models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Скан')),
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
],
options={
'verbose_name': 'Отзыв',
'verbose_name_plural': 'Отзывы',
'ordering': ['time_create', 'title'],
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-25 09:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0005_auto_20231124_1519'),
]
operations = [
migrations.AlterField(
model_name='recall',
name='scan',
field=models.ImageField(upload_to='scan/%Y/%m/%d/', verbose_name='Фото'),
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-25 09:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0006_alter_recall_scan'),
]
operations = [
migrations.CreateModel(
name='Solution',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Наименование')),
('description', models.TextField(blank=True, verbose_name='Описание')),
('implementation', models.TextField(blank=True, verbose_name='Реализация')),
('closing', models.TextField(blank=True, verbose_name='Заключение')),
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
],
options={
'verbose_name': 'Проекты',
'verbose_name_plural': 'Проекты',
'ordering': ['time_create', 'title'],
},
),
]

View File

@ -1,30 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-25 10:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0007_solution'),
]
operations = [
migrations.CreateModel(
name='Home',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Наименование')),
('content', models.TextField(blank=True, verbose_name='Статья')),
('home_image', models.ImageField(upload_to='home_image/%Y/%m/%d/', verbose_name='Фото')),
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
],
options={
'verbose_name': 'Главная страница',
'verbose_name_plural': 'Главная страница',
'ordering': ['time_create', 'title'],
},
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 4.2.7 on 2025-11-09 12:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0008_home'),
]
operations = [
migrations.CreateModel(
name='CallbackRequest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Имя')),
('phone', models.CharField(max_length=20, verbose_name='Телефон')),
('email', models.EmailField(max_length=254, verbose_name='Электронная почта')),
('question', models.TextField(verbose_name='Ваш вопрос')),
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('is_processed', models.BooleanField(default=False, verbose_name='Обработано')),
],
options={
'verbose_name': 'Заявка на звонок',
'verbose_name_plural': 'Заявки на звонок',
'ordering': ['-time_create'],
},
),
migrations.AlterModelOptions(
name='competence',
options={'ordering': ['time_create', 'title'], 'verbose_name': 'Компетенция', 'verbose_name_plural': 'Компетенции'},
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 4.2.7 on 2025-11-09 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0009_callbackrequest_alter_competence_options'),
]
operations = [
migrations.AlterField(
model_name='callbackrequest',
name='email',
field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Электронная почта'),
),
migrations.AlterField(
model_name='callbackrequest',
name='question',
field=models.TextField(blank=True, verbose_name='Ваш вопрос'),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 4.2.7 on 2025-11-12 11:43
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('programmer', '0010_alter_callbackrequest_email_and_more'),
]
operations = [
migrations.CreateModel(
name='Visitor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip_address', models.GenericIPAddressField()),
('first_visit', models.DateTimeField(default=django.utils.timezone.now)),
('last_visit', models.DateTimeField(default=django.utils.timezone.now)),
('visit_count', models.IntegerField(default=1)),
],
options={
'indexes': [models.Index(fields=['ip_address'], name='programmer__ip_addr_2c6dca_idx')],
},
),
migrations.CreateModel(
name='PageView',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.CharField(max_length=500)),
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
('ip_address', models.GenericIPAddressField()),
('user_agent', models.TextField(blank=True)),
('referer', models.CharField(blank=True, max_length=500)),
],
options={
'indexes': [models.Index(fields=['url', 'timestamp'], name='programmer__url_9a41b2_idx'), models.Index(fields=['timestamp'], name='programmer__timesta_070072_idx')],
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.7 on 2025-11-14 09:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0011_visitor_pageview'),
]
operations = [
migrations.AddField(
model_name='callbackrequest',
name='is_read',
field=models.BooleanField(default=False, verbose_name='Прочитано'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.7 on 2025-11-14 10:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0012_callbackrequest_is_read'),
]
operations = [
migrations.AddField(
model_name='callbackrequest',
name='notification_sent',
field=models.BooleanField(default=False, verbose_name='Уведомление отправлено'),
),
]

View File

@ -1,180 +0,0 @@
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .utils.email_notifications import send_callback_notification
class Recall(models.Model):
title = models.CharField(max_length=255, verbose_name='Организация')
content = models.TextField(blank=True, verbose_name='Отзыв')
scan = models.ImageField(upload_to="scan/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Отзыв'
verbose_name_plural = 'Отзывы'
ordering = ['time_create', 'title']
def get_seo_title(self):
return f"Отзыв от {self.title} | Программист 1С"
def get_seo_description(self):
if self.content:
clean_content = self.content[:160].replace('\n', ' ').strip()
return f"Отзыв о работе программиста 1С от {self.title}. {clean_content}..."
return f"Отзыв клиента {self.title} о работе программиста 1С Николая Сердюк"
class Competence(models.Model):
title = models.CharField(max_length=255, verbose_name='Программист')
content = models.TextField(blank=True, verbose_name='Компетенция')
photo = models.ImageField(upload_to="photos/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Компетенция'
verbose_name_plural = 'Компетенции'
ordering = ['time_create', 'title']
class Solution(models.Model):
title = models.CharField(max_length=255, verbose_name='Наименование')
description = models.TextField(blank=True, verbose_name='Описание')
implementation = models.TextField(blank=True, verbose_name='Реализация')
closing = models.TextField(blank=True, verbose_name='Заключение')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Проекты'
verbose_name_plural = 'Проекты'
ordering = ['time_create', 'title']
def get_seo_title(self):
"""Генерирует SEO-заголовок для проекта"""
return f"Проект: {self.title} | Автоматизация 1С"
def get_seo_description(self):
"""Генерирует SEO-описание для проекта"""
if self.description:
clean_desc = self.description[:160].replace('\n', ' ').strip()
return f"Проект автоматизации: {self.title}. {clean_desc}..."
return f"Реализация проекта {self.title} - программист 1С Николай Сердюк"
def get_meta_keywords(self):
"""Автоматические ключевые слова для проекта"""
base_keywords = ["проект 1С", "автоматизация 1С", "внедрение 1С"]
title_words = self.title.lower().split()
return base_keywords + title_words
class Home(models.Model):
title = models.CharField(max_length=255, verbose_name='Наименование')
content = models.TextField(blank=True, verbose_name='Статья')
home_image = models.ImageField(upload_to="home_image/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Главная страница'
verbose_name_plural = 'Главная страница'
ordering = ['time_create', 'title']
class CallbackRequest(models.Model):
name = models.CharField(max_length=100, verbose_name='Имя')
phone = models.CharField(max_length=20, verbose_name='Телефон')
email = models.EmailField(blank=True, null=True, verbose_name='Электронная почта') # Сделать необязательным
question = models.TextField(blank=True, verbose_name='Ваш вопрос') # Сделать необязательным
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
is_processed = models.BooleanField(default=False, verbose_name='Обработано')
is_read = models.BooleanField(default=False, verbose_name='Прочитано')
notification_sent = models.BooleanField(default=False, verbose_name='Уведомление отправлено')
def __str__(self):
return f"{self.name} - {self.phone}"
class Meta:
verbose_name = 'Заявка на звонок'
verbose_name_plural = 'Заявки на звонок'
ordering = ['-time_create']
# Сигнал для отправки уведомления при создании заявки
@receiver(post_save, sender=CallbackRequest)
def send_callback_email_notification(sender, instance, created, **kwargs):
if created and not instance.notification_sent:
# Отправляем email уведомление
success = send_callback_notification(instance)
if success:
instance.notification_sent = True
# Сохраняем без повторного вызова сигнала
sender.objects.filter(pk=instance.pk).update(notification_sent=True)
class PageView(models.Model):
url = models.CharField(max_length=500)
timestamp = models.DateTimeField(default=timezone.now)
ip_address = models.GenericIPAddressField()
user_agent = models.TextField(blank=True)
referer = models.CharField(max_length=500, blank=True)
class Meta:
indexes = [
models.Index(fields=['url', 'timestamp']),
models.Index(fields=['timestamp']),
]
class Visitor(models.Model):
ip_address = models.GenericIPAddressField()
first_visit = models.DateTimeField(default=timezone.now)
last_visit = models.DateTimeField(default=timezone.now)
visit_count = models.IntegerField(default=1)
class Meta:
indexes = [
models.Index(fields=['ip_address']),
]
@receiver([post_save, post_delete], sender=Home)
@receiver([post_save, post_delete], sender=Solution)
@receiver([post_save, post_delete], sender=Competence)
@receiver([post_save, post_delete], sender=Recall)
def clear_sitemap_cache(sender, **kwargs):
"""Очищаем кэш sitemap при изменении контента"""
cache.delete('sitemap_cache')

View File

@ -1,64 +0,0 @@
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from .models import Home, Solution, Competence, Recall
class StaticViewSitemap(Sitemap):
priority = 1.0
changefreq = 'monthly'
def items(self):
return ['home', 'about', 'solution', 'ability', 'recall']
def location(self, item):
return reverse(item)
class HomeSitemap(Sitemap):
changefreq = 'weekly'
priority = 1.0
def items(self):
return Home.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
# УБИРАЕМ метод location - используем default
# Django автоматически сгенерирует правильные URL
class SolutionSitemap(Sitemap):
changefreq = 'weekly'
priority = 0.9
def items(self):
return Solution.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
class CompetenceSitemap(Sitemap):
changefreq = 'monthly'
priority = 0.8
def items(self):
return Competence.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
class RecallSitemap(Sitemap):
changefreq = 'monthly'
priority = 0.7
def items(self):
return Recall.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
# Упрощаем sitemaps - убираем HomeSitemap если он дублирует главную
sitemaps = {
'static': StaticViewSitemap,
'solutions': SolutionSitemap,
'competence': CompetenceSitemap,
'recall': RecallSitemap,
}

View File

@ -1,299 +0,0 @@
/* competence.css - Стили для страницы компетенций */
/* Основные стили для страницы компетенций */
.competence-item {
display: flex;
gap: 2rem;
align-items: flex-start;
padding: 2rem;
background: var(--bg-card);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
border-left: 4px solid var(--secondary);
transition: var(--transition);
border: 1px solid var(--border-light);
}
.competence-item:hover {
transform: translateX(8px);
box-shadow: var(--shadow-xl);
border-color: var(--primary-light);
}
.competence-scan-wrapper {
flex-shrink: 0;
}
.competence-scan-container {
width: 280px;
cursor: pointer;
border-radius: var(--radius-lg);
overflow: hidden;
border: 2px solid var(--border-light);
transition: var(--transition);
position: relative;
box-shadow: var(--shadow-md);
}
.competence-scan-container:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: var(--shadow-xl);
border-color: var(--primary);
}
.competence-scan {
width: 100%;
height: auto;
display: block;
transition: var(--transition);
}
.competence-content {
flex: 1;
min-width: 0;
}
.competence-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 1rem;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.competence-description {
line-height: 1.7;
font-size: 1.05rem;
color: var(--text-primary);
}
.competence-description p {
margin-bottom: 1rem;
}
.competence-description p:last-child {
margin-bottom: 0;
}
.scan-hint {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 1rem;
text-align: center;
opacity: 0;
transition: var(--transition);
transform: translateY(10px);
}
.competence-scan-container:hover .scan-hint {
opacity: 1;
transform: translateY(0);
}
/* Стили для модального окна с изображением компетенций */
.modal.competence-modal {
background-color: rgba(15, 19, 31, 0.95);
backdrop-filter: blur(10px);
}
.modal.competence-modal .modal-content {
background: transparent;
border: none;
box-shadow: none;
max-width: 95vw;
max-height: 95vh;
margin: 2% auto;
}
.modal.competence-modal .modal-header {
background: var(--bg-card);
border-bottom: 2px solid var(--border-light);
padding: 1.5rem 2rem;
}
.modal.competence-modal .modal-header h3 {
margin: 0;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal.competence-modal .modal-body {
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.modal-image {
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
display: block;
margin: 0 auto;
border-radius: var(--radius-md);
box-shadow: var(--shadow-xl);
transition: all 0.3s ease;
}
/* Анимации для модального окна */
.modal.competence-modal {
transition: opacity 0.3s ease;
opacity: 0;
}
.modal.competence-modal.active {
opacity: 1;
}
.modal.competence-modal .modal-content {
transform: scale(0.7);
transition: transform 0.3s ease;
}
.modal.competence-modal.active .modal-content {
transform: scale(1);
}
/* Улучшенные тени и границы */
.competence-scan-container {
border: 2px solid var(--border-light);
box-shadow: var(--shadow-lg);
}
.competence-scan-container:hover {
border-color: var(--primary-light);
box-shadow: var(--shadow-xl);
}
/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.competence-item {
flex-direction: column;
padding: 1.5rem;
gap: 1.5rem;
}
.competence-scan-container {
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.competence-title {
font-size: 1.375rem;
text-align: center;
}
.modal-image {
max-width: 95vw;
max-height: 70vh;
}
.modal.competence-modal .modal-content {
margin: 10% auto;
}
.modal.competence-modal .modal-header {
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.competence-item {
padding: 1.25rem;
}
.competence-scan-container {
max-width: 100%;
}
.competence-title {
font-size: 1.25rem;
}
.modal.competence-modal .modal-content {
margin: 5% auto;
max-width: 98vw;
}
.modal.competence-modal .modal-body {
padding: 0.5rem;
}
.modal.competence-modal .modal-header {
padding: 1rem;
}
.modal.competence-modal .modal-header h3 {
font-size: 1.1rem;
}
}
/* Стили для светлой темы */
@media (prefers-color-scheme: light) {
.modal.competence-modal {
background-color: rgba(0, 0, 0, 0.8);
}
.modal.competence-modal .modal-header {
background: var(--bg-primary);
}
.scan-hint {
background: linear-gradient(transparent, rgba(0,0,0,0.7));
}
.competence-item {
background: var(--bg-primary);
}
.competence-description {
color: var(--text-primary);
}
}
/* Улучшенные стили для сетки компетенций */
.competence-grid {
display: grid;
gap: 2rem;
}
.competence-grid .modern-card {
padding: 0;
overflow: hidden;
}
.competence-grid .modern-card::before {
height: 4px;
background: var(--gradient-secondary);
}
/* Анимации появления */
.fade-in {
animation: fadeInUp 0.8s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -1,228 +0,0 @@
/* recall.css - Стили для страницы отзывов */
/* Основные стили для страницы отзывов */
.recall-item {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.recall-content {
display: flex;
gap: 2rem;
align-items: flex-start;
}
.recall-scan-wrapper {
flex-shrink: 0;
}
.recall-scan-container {
width: 280px;
cursor: pointer;
border-radius: var(--radius-lg);
overflow: hidden;
border: 2px solid var(--border-light);
transition: var(--transition);
position: relative;
box-shadow: var(--shadow-md);
}
.recall-scan-container:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: var(--shadow-xl);
border-color: var(--primary);
}
.recall-scan {
width: 100%;
height: auto;
display: block;
transition: var(--transition);
}
.recall-text {
flex: 1;
min-width: 0;
line-height: 1.7;
font-size: 1.05rem;
color: var(--text-primary);
}
.recall-text p {
margin-bottom: 1rem;
}
.recall-text p:last-child {
margin-bottom: 0;
}
.scan-hint {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 1rem;
text-align: center;
opacity: 0;
transition: var(--transition);
transform: translateY(10px);
}
.recall-scan-container:hover .scan-hint {
opacity: 1;
transform: translateY(0);
}
/* Стили для модального окна с изображением */
.modal.image-modal {
background-color: rgba(15, 19, 31, 0.95);
backdrop-filter: blur(10px);
}
.modal.image-modal .modal-content {
background: transparent;
border: none;
box-shadow: none;
max-width: 95vw;
max-height: 95vh;
margin: 2% auto;
}
.modal.image-modal .modal-header {
background: var(--bg-card);
border-bottom: 2px solid var(--border-light);
padding: 1.5rem 2rem;
}
.modal.image-modal .modal-header h3 {
margin: 0;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal.image-modal .modal-body {
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.modal-image {
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
display: block;
margin: 0 auto;
border-radius: var(--radius-md);
box-shadow: var(--shadow-xl);
transition: all 0.3s ease;
}
/* Анимации для модального окна */
.modal.image-modal {
transition: opacity 0.3s ease;
opacity: 0;
}
.modal.image-modal.active {
opacity: 1;
}
.modal.image-modal .modal-content {
transform: scale(0.7);
transition: transform 0.3s ease;
}
.modal.image-modal.active .modal-content {
transform: scale(1);
}
/* Улучшенные тени и границы */
.recall-scan-container {
border: 2px solid var(--border-light);
box-shadow: var(--shadow-lg);
}
.recall-scan-container:hover {
border-color: var(--primary-light);
box-shadow: var(--shadow-xl);
}
/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.recall-content {
flex-direction: column;
gap: 1.5rem;
}
.recall-scan-container {
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.recall-scan-wrapper {
order: -1;
}
.modal-image {
max-width: 95vw;
max-height: 70vh;
}
.modal.image-modal .modal-content {
margin: 10% auto;
}
.modal.image-modal .modal-header {
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.recall-scan-container {
max-width: 100%;
}
.modal.image-modal .modal-content {
margin: 5% auto;
max-width: 98vw;
}
.modal.image-modal .modal-body {
padding: 0.5rem;
}
.modal.image-modal .modal-header {
padding: 1rem;
}
.modal.image-modal .modal-header h3 {
font-size: 1.1rem;
}
}
/* Стили для светлой темы */
@media (prefers-color-scheme: light) {
.modal.image-modal {
background-color: rgba(0, 0, 0, 0.8);
}
.modal.image-modal .modal-header {
background: var(--bg-primary);
}
.scan-hint {
background: linear-gradient(transparent, rgba(0,0,0,0.7));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

File diff suppressed because one or more lines are too long

View File

@ -1,74 +0,0 @@
// recall.js - Скрипты для страницы отзывов
function openModal(imageUrl, title) {
console.log('Opening modal with:', imageUrl);
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
const modalTitle = document.getElementById('modalTitle');
if (modal && modalImg) {
modal.style.display = "block";
modalImg.src = imageUrl;
if (title && modalTitle) {
modalTitle.textContent = title;
}
// Добавляем класс для анимации
setTimeout(() => {
modal.classList.add('active');
}, 10);
// Подстраиваем размер изображения
adjustModalImageSize();
}
}
function closeModal() {
const modal = document.getElementById('imageModal');
if (modal) {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = "none";
}, 300);
}
}
function adjustModalImageSize() {
const modalImg = document.getElementById('modalImage');
const modalContent = document.querySelector('.modal-content');
if (modalImg && modalContent) {
const maxWidth = window.innerWidth * 0.9;
const maxHeight = window.innerHeight * 0.8;
modalImg.style.maxWidth = `${maxWidth}px`;
modalImg.style.maxHeight = `${maxHeight}px`;
}
}
// Инициализация после загрузки DOM
document.addEventListener('DOMContentLoaded', function() {
// Закрытие модального окна при клике вне изображения
document.addEventListener('click', function(event) {
const modal = document.getElementById('imageModal');
if (event.target === modal) {
closeModal();
}
});
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
// Адаптация размера изображения при изменении размера окна
window.addEventListener('resize', function() {
const modalImg = document.getElementById('modalImage');
if (modalImg && modalImg.src) {
adjustModalImageSize();
}
});
console.log('Recall page scripts initialized');
});

View File

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Админ-панель - Статистика{% endblock %}</title>
{% load django_bootstrap5 %}
{% bootstrap_css %}
<style>
body {
background: #f8f9fa;
font-family: 'Inter', sans-serif;
}
.admin-header {
background: #343a40;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin: 0;
}
table {
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
th {
background: #f8f9fa;
font-weight: 600;
}
</style>
</head>
<body>
<div class="admin-header">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<h1>{% block page_title %}Админ-панель{% endblock %}</h1>
<div>
<a href="{% url 'home' %}" class="btn btn-outline-light btn-sm">На сайт</a>
<a href="{% url 'admin:index' %}" class="btn btn-outline-light btn-sm">Django Admin</a>
<a href="{% url 'admin:logout' %}" class="btn btn-outline-light btn-sm">Выйти</a>
</div>
</div>
</div>
</div>
<div class="container">
{% bootstrap_messages %}
{% block content %}
{% endblock %}
</div>
{% bootstrap_javascript %}
</body>
</html>

View File

@ -1,26 +0,0 @@
{% extends "admin/base.html" %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
{% endblock %}
{% block nav-global %}
{% endblock %}
{% block userlinks %}
{% load programmer_tags %}
<!-- Уведомление о новых заявках -->
{% get_unread_callbacks as unread_callbacks %}
{% if unread_callbacks %}
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" style="color: #dc3545; font-weight: bold;">
🚨 {{ unread_callbacks }} новых заявок
</a> /
{% endif %}
<a href="{% url 'callback_stats' %}">📊 Статистика заявок</a> /
<a href="{% url 'statistics' %}">📈 Посещения</a> /
{{ block.super }}
{% endblock %}

View File

@ -1,45 +0,0 @@
{% extends "admin/base_site.html" %}
{% block title %}Статистика заявок{% endblock %}
{% block content %}
<div class="module">
<h1>📊 Статистика заявок на обратный звонок</h1>
<div class="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 2rem 0;">
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📋 Всего заявок</h3>
<p style="font-size: 2rem; font-weight: bold; color: #007bff; margin: 0;">{{ stats.total }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📅 Сегодня</h3>
<p style="font-size: 2rem; font-weight: bold; color: #28a745; margin: 0;">{{ stats.today }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📈 За неделю</h3>
<p style="font-size: 2rem; font-weight: bold; color: #17a2b8; margin: 0;">{{ stats.week }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>🆕 Непрочитанные</h3>
<p style="font-size: 2rem; font-weight: bold; color: #dc3545; margin: 0;">{{ stats.unread }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>В обработке</h3>
<p style="font-size: 2rem; font-weight: bold; color: #ffc107; margin: 0;">{{ stats.unprocessed }}</p>
</div>
</div>
<div style="margin-top: 2rem;">
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="button" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
📋 Перейти к списку заявок
</a>
<a href="{% url 'admin:index' %}" class="button" style="background: #6c757d; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin-left: 10px;">
🏠 На главную админки
</a>
</div>
</div>
{% endblock %}

View File

@ -1,131 +0,0 @@
{% extends 'admin/base.html' %}
{% load programmer_tags %}
{% block title %}Статистика посещений{% endblock %}
{% block page_title %}Статистика посещений{% endblock %}
{% block content %}
<!-- Уведомления о заявках -->
{% get_unread_callbacks as unread_callbacks %}
{% get_today_callbacks as today_callbacks %}
{% if unread_callbacks > 0 %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<h4>🚨 Внимание!</h4>
<p>
У вас <strong>{{ unread_callbacks }}</strong> непрочитанных заявок на обратный звонок
{% if today_callbacks > 0 %}
({{ today_callbacks }} из них сегодня)
{% endif %}
</p>
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="btn btn-warning btn-sm">
📋 Перейти к заявкам
</a>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="row mb-4">
<div class="col-12">
<div class="stats-grid">
<div class="stat-card">
<h3>📊 Просмотров сегодня</h3>
<p class="stat-number">{{ today_views }}</p>
</div>
<div class="stat-card">
<h3>📈 Просмотров за неделю</h3>
<p class="stat-number">{{ weekly_views }}</p>
</div>
<div class="stat-card">
<h3>👥 Уникальных посетителей</h3>
<p class="stat-number">{{ unique_visitors }}</p>
</div>
<div class="stat-card">
<h3>🕒 Всего просмотров</h3>
<p class="stat-number">{{ total_views }}</p>
</div>
<!-- Добавляем статистику по заявкам -->
<div class="stat-card">
<h3>📞 Заявок сегодня</h3>
<p class="stat-number">{{ today_callbacks }}</p>
</div>
<div class="stat-card">
<h3>📋 Всего заявок</h3>
<p class="stat-number">{% get_unread_callbacks %}/{{ total_callbacks }}</p>
<small>(непрочитанные/всего)</small>
</div>
</div>
</div>
</div>
<!-- Остальной код статистики остается без изменений -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3>🔥 Популярные страницы (за неделю)</h3>
</div>
<div class="card-body p-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Страница</th>
<th>Просмотров</th>
</tr>
</thead>
<tbody>
{% for page in popular_pages %}
<tr>
<td>
<code>{{ page.url }}</code>
{% if page.url == '/' %}
<span class="badge bg-primary">Главная</span>
{% endif %}
</td>
<td><strong>{{ page.views }}</strong></td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center text-muted">Нет данных за выбранный период</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3>📋 Последние посещения</h3>
</div>
<div class="card-body p-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Время</th>
<th>Страница</th>
<th>IP-адрес</th>
</tr>
</thead>
<tbody>
{% for view in recent_views %}
<tr>
<td>{{ view.timestamp|date:"d.m.Y H:i" }}</td>
<td><code>{{ view.url }}</code></td>
<td><small>{{ view.ip_address }}</small></td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">Нет данных</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,57 +0,0 @@
<!-- templates/emails/callback_notification.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #FF6B00 0%, #0055A5 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
.info-box { background: white; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #007bff; }
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚨 Новая заявка на сайте</h1>
<p>Требуется ваше внимание!</p>
</div>
<div class="content">
<div class="alert">
<strong>⚠️ Срочно!</strong> Пользователь оставил заявку на обратный звонок.
</div>
<div class="info-box">
<h3>📋 Информация о заявке:</h3>
<p><strong>👤 Имя:</strong> {{ callback.name }}</p>
<p><strong>📞 Телефон:</strong> {{ callback.phone }}</p>
{% if callback.email %}
<p><strong>📧 Email:</strong> {{ callback.email }}</p>
{% endif %}
{% if callback.question %}
<p><strong>❓ Вопрос:</strong><br>{{ callback.question }}</p>
{% endif %}
<p><strong>🕒 Время отправки:</strong> {{ callback.time_create|date:"d.m.Y H:i" }}</p>
</div>
<div style="text-align: center; margin: 20px 0;">
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
📋 Перейти к заявкам в админке
</a>
</div>
<p><em>Не забудьте отметить заявку как обработанную после связи с клиентом!</em></p>
</div>
<div class="footer">
<p>Это автоматическое уведомление от системы сайта.</p>
<p>Если вы получили это письмо по ошибке, пожалуйста, свяжитесь с администратором.</p>
</div>
</div>
</body>
</html>

View File

@ -1,60 +0,0 @@
<!-- templates/emails/daily_summary.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
.stat-card { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-number { font-size: 2rem; font-weight: bold; margin: 0; }
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📊 Ежедневная сводка</h1>
<p>Статистика заявок за {{ yesterday|date:"d.m.Y" }}</p>
</div>
<div class="content">
<div class="stats-grid">
<div class="stat-card">
<h3>📅 Вчерашние заявки</h3>
<p class="stat-number" style="color: #28a745;">{{ yesterday_callbacks }}</p>
</div>
<div class="stat-card">
<h3>⏳ Ожидают обработки</h3>
<p class="stat-number" style="color: #ffc107;">{{ unprocessed_callbacks }}</p>
</div>
</div>
{% if unprocessed_callbacks > 0 %}
<div class="alert">
<strong>⚠️ Внимание!</strong> У вас есть {{ unprocessed_callbacks }} необработанных заявок.
</div>
{% endif %}
<div style="text-align: center; margin: 20px 0;">
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
📋 Управление заявками
</a>
</div>
<p><em>Не забудьте обработать все pending заявки!</em></p>
</div>
<div class="footer">
<p>Ежедневная автоматическая сводка от системы сайта.</p>
<p>Дата формирования: {{ today|date:"d.m.Y H:i" }}</p>
</div>
</div>
</body>
</html>

View File

@ -1,154 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
<div class="page-header">
<h1 class="page-title">{{title}}</h1>
<p class="page-subtitle">Профессиональный программист 1С с более чем 10-летним опытом</p>
</div>
<div class="content-card">
<div class="about-header">
<h2>Николай Сердюк</h2>
<p class="subtitle">Разработчик 1С</p>
</div>
<div class="about-section">
<h3>🚀 Опыт работы</h3>
<p class="card-subtitle">Более 10 лет успешной работы в разработке и сопровождении систем на платформе 1С</p>
<div class="experience-item mt-3">
<h4>Основные направления:</h4>
<div class="skills-grid">
<div class="skill-category">
<h4>💻 Разработка</h4>
<ul>
<li>Разработка и доработка конфигураций 1С</li>
<li>Создание внешних обработок и отчетов</li>
<li>Кастомизация под бизнес-процессы</li>
</ul>
</div>
<div class="skill-category">
<h4>🔗 Интеграция</h4>
<ul>
<li>Интеграция 1С с веб-сервисами</li>
<li>Связь с сайтами и мобильными приложениями</li>
<li>API и веб-сервисы</li>
</ul>
</div>
<div class="skill-category">
<h4>⚡ Оптимизация</h4>
<ul>
<li>Оптимизация бизнес-процессов</li>
<li>Ускорение работы баз данных</li>
<li>Автоматизация рутинных операций</li>
</ul>
</div>
</div>
</div>
</div>
<div class="about-section">
<h3>🛠 Технологии и навыки</h3>
<div class="skills-grid">
<div class="skill-category">
<h4>🎯 1С Разработка</h4>
<ul>
<li>1С:Предприятие 8.3</li>
<li>Управление торговлей</li>
<li>Бухгалтерия предприятия</li>
<li>Зарплата и управление персоналом</li>
<li>Внешние обработки и отчеты</li>
</ul>
</div>
<div class="skill-category">
<h4>🔧 Дополнительные технологии</h4>
<ul>
<li>SQL и оптимизация запросов</li>
<li>Веб-сервисы и API</li>
<li>XML, JSON, REST</li>
<li>Системное администрирование</li>
</ul>
</div>
</div>
</div>
<div class="about-section">
<h3>📈 Проекты и достижения</h3>
<p class="card-subtitle">Успешно реализовал более 50 проектов различной сложности</p>
<div class="skills-grid mt-3">
<div class="skill-category">
<h4>🏆 Ключевые проекты</h4>
<ul>
<li>Автоматизация учетных систем для предприятий</li>
<li>Интеграция 1С с сайтами и мобильными приложениями</li>
<li>Разработка кастомизированных отчетов и дашбордов</li>
<li>Оптимизация производительности баз данных</li>
</ul>
</div>
</div>
</div>
<div class="about-section">
<h3>📞 Контакты</h3>
<div class="contacts">
<div class="skills-grid">
<div class="skill-category">
<h4>📧 Электронная почта</h4>
<p><strong>{{ CONTACT_EMAIL }}</strong></p>
</div>
<div class="skill-category">
<h4>📱 Телефон</h4>
<p><strong>{{ CONTACT_PHONE }}</strong></p>
</div>
<div class="skill-category">
<h4>💬 Telegram</h4>
<p><strong><a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="display: inline-flex; padding: 0.5rem 1rem;">@odinesina_prog</a></strong></p>
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<div class="card-actions">
<a href="{% url 'solution' %}" class="btn btn-primary">📂 Посмотреть мои проекты</a>
<a href="{% url 'recall' %}" class="btn btn-secondary">⭐ Отзывы клиентов</a>
</div>
</div>
</div>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Service",
"serviceType": "1С программирование",
"provider": {
"@type": "Person",
"name": "Николай Сердюк"
},
"areaServed": "Россия",
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Услуги программиста 1С",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Разработка конфигураций 1С"
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Интеграция 1С с веб-сервисами"
}
}
]
}
}
</script>
{% endblock %}

View File

@ -1,578 +0,0 @@
{% load static %}
{% load programmer_tags %}
{% load django_bootstrap5 %}
<!DOCTYPE html>
<html lang="ru">
<head>
<title>{{title}}</title>
<!-- Основные мета-теги -->
<meta name="description" content="{% block meta_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С.' }}{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}{{ meta_keywords|default:'программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие' }}{% endblock %}">
<meta name="author" content="Николай Сердюк">
<!-- Open Graph для соцсетей -->
<meta property="og:title" content="{{title}}">
<meta property="og:description" content="{% block og_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{% static 'programmer/images/og-image.jpg' %}">
<meta property="og:site_name" content="Программист 1С - Николай Сердюк">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{title}}">
<meta name="twitter:description" content="{% block twitter_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
<meta name="twitter:image" content="{% static 'programmer/images/og-image.jpg' %}">
<!-- Дополнительные SEO-теги -->
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<link rel="canonical" href="{{ request.build_absolute_uri }}">
{% bootstrap_css %}
<!-- Основной CSS файл (темная тема по умолчанию) -->
<link type="text/css" href="{% static 'programmer/css/styles_w.css' %}" rel="stylesheet" id="theme-css" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="shortcut icon" href="{% static 'programmer/images/main.ico' %}" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* Временные стили для тумблера и мобильного меню */
.theme-switcher {
display: flex;
align-items: center;
}
.theme-toggle-checkbox {
display: none;
}
.theme-toggle-label {
position: relative;
display: flex;
align-items: center;
width: 60px;
height: 30px;
background: #222738;
border: 2px solid #2D3447;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
padding: 2px;
}
.theme-toggle-slider {
position: absolute;
width: 24px;
height: 24px;
background: #FF6B00;
border-radius: 50%;
transition: all 0.3s ease;
left: 2px;
z-index: 2;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-toggle-slider {
transform: translateX(30px);
background: #0055A5;
}
.theme-icon {
position: absolute;
font-size: 12px;
transition: all 0.3s ease;
z-index: 1;
}
.theme-icon.sun {
left: 8px;
opacity: 0;
}
.theme-icon.moon {
right: 8px;
opacity: 1;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.sun {
opacity: 1;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.moon {
opacity: 0;
}
.nav-actions {
display: flex;
align-items: center;
gap: 1rem;
}
/* Мобильное меню */
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text-primary);
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.mobile-menu-btn:hover {
background: var(--border-light);
}
.mobile-menu-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.mobile-menu {
position: fixed;
top: 0;
right: -100%;
width: 300px;
height: 100%;
background: var(--bg-card);
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
transition: right 0.3s ease;
z-index: 1000;
overflow-y: auto;
padding: 2rem 1.5rem;
}
.mobile-menu.active {
right: 0;
}
.mobile-menu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-light);
}
.mobile-menu-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-primary);
cursor: pointer;
padding: 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.mobile-menu-close:hover {
background: var(--border-light);
}
.mobile-nav-menu {
list-style: none;
margin-bottom: 2rem;
}
.mobile-nav-item {
margin-bottom: 0.5rem;
}
.mobile-nav-link {
display: block;
padding: 1rem;
color: var(--text-primary);
text-decoration: none;
border-radius: 8px;
transition: all 0.3s ease;
font-weight: 500;
}
.mobile-nav-link:hover,
.mobile-nav-link.active {
background: var(--primary);
color: white;
}
.mobile-nav-actions {
display: flex;
flex-direction: column;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-light);
}
/* ===== MOBILE RESPONSIVE STYLES ===== */
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.hero-subtitle {
font-size: 1.125rem;
padding: 0 1rem;
}
.page-title {
font-size: 2.25rem;
}
.page-subtitle {
font-size: 1.125rem;
padding: 0 1rem;
}
.grid-2 {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.modern-card {
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.card-title {
font-size: 1.375rem;
}
.content-card {
padding: 1.5rem;
}
.about-section {
padding: 1.5rem;
}
.skills-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.competence-item {
flex-direction: column;
padding: 1.5rem;
gap: 1.5rem;
}
.competence-scan-container {
width: 100%;
max-width: 280px;
margin: 0 auto;
}
.recall-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.recall-meta {
flex-direction: column;
gap: 0.5rem;
}
.footer-content {
grid-template-columns: 1fr;
gap: 2rem;
text-align: center;
}
.footer-contacts p {
justify-content: center;
}
.card-actions {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
.modal-content {
margin: 5% auto;
max-width: 95%;
}
.modal-header {
padding: 1.5rem 1.5rem 1rem;
}
.modal-body {
padding: 1.5rem;
}
.page-content {
padding: 1.5rem;
}
.breadcrumbs {
font-size: 0.8rem;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.page-title {
font-size: 1.875rem;
}
.modern-card {
padding: 1.25rem;
}
.content-card {
padding: 1.25rem;
}
.about-section {
padding: 1.25rem;
}
.card-title {
font-size: 1.25rem;
}
.competence-scan-container {
max-width: 100%;
}
}
</style>
{% block extra_css %}
<!-- Дополнительные CSS файлы для конкретных страниц -->
{% endblock %}
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X3W9YSQHRM"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-X3W9YSQHRM');
</script>
<!-- Яндекс.Метрика -->
<script type="text/javascript">
(function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=105278924', 'ym');
ym(105278924, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/105278924" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Николай Сердюк",
"jobTitle": "Программист 1С",
"description": "Профессиональный программист 1С с более чем 10-летним опытом",
"url": "https://nikdizell.ru",
"email": "{{ CONTACT_EMAIL }}",
"telephone": "{{ CONTACT_PHONE }}",
"knowsAbout": [
"1С:Предприятие 8.3",
"Управление торговлей",
"Бухгалтерия предприятия",
"Зарплата и управление персоналом",
"SQL",
"Веб-сервисы",
"API интеграция"
],
"hasOccupation": {
"@type": "Occupation",
"name": "Программист 1С",
"description": "Разработка и сопровождение систем на платформе 1С",
"occupationLocation": "Россия"
}
}
</script>
</head>
<body>
<!-- Header -->
{% block mainmenu %}
<header class="header">
<div class="container">
<nav class="nav">
<a href="{% url 'home' %}" class="logo">
<img src="{% static 'programmer/images/main.ico' %}" alt="Logo" class="logo-img">
<span class="logo-text">Программист 1С</span>
</a>
<!-- Десктопное меню -->
<ul class="nav-menu">
{% for m in menu %}
<li class="nav-item">
<a href="{% url m.url_name %}" class="nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
{{m.title}}
</a>
</li>
{% endfor %}
</ul>
<div class="nav-actions">
<a href="https://t.me/odinesina_prog" target="_blank" class="telegram-btn">
<span class="telegram-icon">
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
</span>
</a>
<!-- Theme Toggle Switch -->
<div class="theme-switcher">
<input type="checkbox" id="theme-toggle" class="theme-toggle-checkbox" checked>
<label for="theme-toggle" class="theme-toggle-label">
<span class="theme-toggle-slider"></span>
<span class="theme-icon sun">☀️</span>
<span class="theme-icon moon">🌙</span>
</label>
</div>
<!-- Кнопка мобильного меню -->
<button class="mobile-menu-btn" id="mobileMenuBtn">
</button>
</div>
</nav>
</div>
</header>
<!-- Мобильное меню -->
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
<div class="mobile-menu" id="mobileMenu">
<div class="mobile-menu-header">
<h3>Меню</h3>
<button class="mobile-menu-close" id="mobileMenuClose">
</button>
</div>
<ul class="mobile-nav-menu">
{% for m in menu %}
<li class="mobile-nav-item">
<a href="{% url m.url_name %}" class="mobile-nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
{{m.title}}
</a>
</li>
{% endfor %}
</ul>
<div class="mobile-nav-actions">
<a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="width: 100%; text-align: center;">
<span class="telegram-icon">
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
</span>
</a>
<div class="theme-switcher" style="justify-content: center;">
<input type="checkbox" id="mobile-theme-toggle" class="theme-toggle-checkbox" checked>
<label for="mobile-theme-toggle" class="theme-toggle-label">
<span class="theme-toggle-slider"></span>
<span class="theme-icon sun">☀️</span>
<span class="theme-icon moon">🌙</span>
</label>
</div>
</div>
</div>
{% endblock mainmenu %}
<!-- Main Content -->
<main class="main">
<div class="container">
<section class="content">
<!-- Breadcrumbs -->
{% block breadcrumbs %}
<nav class="breadcrumbs">
<a href="{% url 'home' %}" class="breadcrumb-link">Главная</a>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">{{title}}</span>
</nav>
{% endblock %}
<!-- Messages -->
{% bootstrap_messages %}
<!-- Page Content -->
<div class="page-content">
{% block content %}
{% endblock %}
</div>
</section>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-info">
<h3>Николай Сердюк</h3>
<p>Программист 1С</p>
</div>
<div class="footer-contacts">
<p>📧 {{ CONTACT_EMAIL }}</p>
<p>📱 {{ CONTACT_PHONE }}</p>
</div>
<div class="footer-copyright">
<p>&copy; 2025 ИП Сердюк Николай Александрович. Все права защищены.</p>
</div>
</div>
</div>
</footer>
{% bootstrap_javascript %}
<script src="{% static 'programmer/js/theme-switcher.js' %}"></script>
<script src="{% static 'programmer/js/mobile-menu.js' %}"></script>
{% block extra_js %}
<!-- Дополнительные JS файлы для конкретных страниц -->
{% endblock %}
<!-- В recall.html после основного контента -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "Service",
"name": "Услуги программиста 1С"
},
"author": {
"@type": "Organization",
"name": "ООО «РОВЕН-Регионы»"
},
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"datePublished": "2025-11-13",
"description": "Выражаю благодарность программисту 1С Николаю Сердюк за профессиональную работу и качественное решение поставленных задач..."
}
</script>
</body>
</html>

View File

@ -1,8 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
{% endblock %}

View File

@ -1,72 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/competence.css' %}">
{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Компетенции</h1>
<p class="page-subtitle">Профессиональные навыки и опыт</p>
</div>
<div class="competence-grid">
{% for p in posts %}
<div class="modern-card fade-in">
<div class="competence-item">
{% if p.photo %}
<div class="competence-scan-wrapper">
<div class="competence-scan-container">
<img src="{{ p.photo.url }}"
alt="Сертификат 1С: {{ p.title }} - {{ p.content|striptags }}"
class="competence-scan"
onclick="openCompetenceModal('{{ p.photo.url }}', '{{ p.title }}')">
<div class="scan-hint">
<span class="scan-zoom-icon">🔍</span>
<span class="scan-text">Нажмите для увеличения</span>
</div>
</div>
</div>
{% endif %}
<div class="competence-content">
<h2 class="competence-title">{{ p.title }}</h2>
<div class="competence-description">
{{ p.content|linebreaks }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>📚 Информация о компетенциях</h3>
<p class="card-subtitle">Раздел находится в разработке</p>
<div class="card-actions justify-center">
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
<a href="{% url 'about' %}" class="btn btn-secondary">Обо мне</a>
</div>
</div>
{% endif %}
<!-- Модальное окно для увеличенного просмотра -->
<div id="competenceModal" class="modal competence-modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="competenceModalTitle">Компетенция</h3>
<button class="modal-close" onclick="closeCompetenceModal()">&times;</button>
</div>
<div class="modal-body">
<img class="modal-image" id="competenceModalImage" alt="Увеличенное изображение компетенции">
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'programmer/js/competence.js' %}"></script>
{% endblock %}

View File

@ -1,13 +0,0 @@
/* TEAM */
Developer: Николай Сердюк
Site: https://nikdizell.ru
Email: {{ CONTACT_EMAIL }}
/* THANKS */
Django Framework
Bootstrap
/* SITE */
Last update: 2025
Language: Russian
Doctype: HTML5

View File

@ -1,103 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
<div class="hero-section fade-in">
<h1 class="hero-title">🚀 Добро пожаловать!</h1>
<p class="hero-subtitle">Я профессиональный программист 1С с опытом создания эффективных бизнес-решений</p>
</div>
<div class="grid grid-2">
{% autoescape off %}
{% for p in posts %}
<div class="modern-card fade-in {% cycle '' 'secondary' %}">
<div class="card-header">
<h2 class="card-title">{{p.title}}</h2>
</div>
<div class="card-content">
{{p.content}}
</div>
<div class="card-actions">
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
</div>
</div>
{% endfor %}
{% endautoescape %}
</div>
<!-- Модальное окно формы -->
<div id="callbackModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>📞 Заявка на консультацию</h3>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'callback' %}" id="callbackForm">
{% csrf_token %}
<div class="form-group">
<label for="id_name">Имя *</label>
{{ form.name }}
</div>
<div class="form-group">
<label for="id_phone">Телефон *</label>
{{ form.phone }}
</div>
<div class="form-group">
<label for="id_email">Электронная почта</label>
{{ form.email }}
</div>
<div class="form-group">
<label for="id_question">Ваш вопрос</label>
{{ form.question }}
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" style="width: 100%;">
📨 Отправить заявку
</button>
</div>
</form>
</div>
</div>
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>🚀 Контент скоро появится</h3>
<p class="card-subtitle">Мы готовим для вас интересные материалы и кейсы</p>
<div class="card-actions justify-center">
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
</div>
</div>
{% endif %}
<script>
function openModal() {
document.getElementById('callbackModal').style.display = 'block';
}
function closeModal() {
document.getElementById('callbackModal').style.display = 'none';
}
// Закрытие модального окна при клике вне его
window.onclick = function(event) {
const modal = document.getElementById('callbackModal');
if (event.target === modal) {
closeModal();
}
}
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
{% endblock %}

View File

@ -1,174 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% load seo_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Отзывы клиентов</h1>
<p class="page-subtitle">Реальные отзывы о работе программиста 1С</p>
</div>
<div class="recall-grid">
{% for p in posts %}
<div class="modern-card fade-in">
<!-- Добавляем микроразметку для отзыва -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "Service",
"name": "Услуги программиста 1С"
},
"author": {
"@type": "Organization",
"name": "{{ p.title }}"
},
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"datePublished": "{{ p.time_create|date:'Y-m-d' }}",
"description": "{{ p.content|striptags|truncatewords:50 }}"
}
</script>
<div class="recall-item">
<div class="recall-header">
<div class="recall-info">
<h2 class="recall-title">{{ p.title }}</h2>
{% if p.time_create %}
<!-- <div class="recall-meta">-->
<!-- <span class="recall-date">{{ p.time_create|date:"d.m.Y" }}</span>-->
<!-- </div>-->
{% endif %}
</div>
</div>
<div class="recall-content">
{% if p.scan %}
<div class="recall-scan-wrapper">
<div class="recall-scan-container">
<img src="{{ p.scan.url }}"
alt="Отзыв от {{ p.title }}"
class="recall-scan"
onclick="openModal('{{ p.scan.url }}', '{{ p.title }}')">
<div class="scan-hint">
<span class="scan-zoom-icon">🔍</span>
<span class="scan-text">Нажмите для увеличения</span>
</div>
</div>
</div>
{% endif %}
<div class="recall-text">
{{ p.content|linebreaks }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>💬 Отзывы клиентов</h3>
<p class="card-subtitle">Здесь будут отображаться отзывы от довольных клиентов</p>
<div class="card-actions justify-center">
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
<a href="{% url 'about' %}" class="btn btn-secondary">Связаться со мной</a>
</div>
</div>
{% endif %}
<!-- Модальное окно для увеличенного просмотра -->
<div id="imageModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Отзыв</h3>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<img class="modal-image" id="modalImage">
</div>
</div>
</div>
<script>
function openModal(imageUrl, title) {
console.log('Opening modal with:', imageUrl);
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
const modalTitle = document.getElementById('modalTitle');
if (modal && modalImg) {
modal.style.display = "block";
modalImg.src = imageUrl;
if (title && modalTitle) {
modalTitle.textContent = title;
}
// Добавляем класс для анимации
setTimeout(() => {
modal.classList.add('active');
}, 10);
}
}
function closeModal() {
const modal = document.getElementById('imageModal');
if (modal) {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = "none";
}, 300);
}
}
// Закрытие модального окна при клике вне изображения
document.addEventListener('click', function(event) {
const modal = document.getElementById('imageModal');
if (event.target === modal) {
closeModal();
}
});
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
// Адаптация размера изображения в модальном окне
window.addEventListener('resize', function() {
const modalImg = document.getElementById('modalImage');
if (modalImg && modalImg.src) {
adjustModalImageSize();
}
});
function adjustModalImageSize() {
const modalImg = document.getElementById('modalImage');
const modalContent = document.querySelector('.modal-content');
if (modalImg && modalContent) {
const maxWidth = window.innerWidth * 0.9;
const maxHeight = window.innerHeight * 0.8;
modalImg.style.maxWidth = `${maxWidth}px`;
modalImg.style.maxHeight = `${maxHeight}px`;
}
}
{% endblock %}
{% block extra_js %}
<script src="{% static 'programmer/js/recall.js' %}"></script>
{% endblock %}

View File

@ -1,19 +0,0 @@
User-agent: *
Allow: /
# Основной сайт
Sitemap: {{ request.scheme }}://{{ request.get_host }}/sitemap.xml
# Запрещаем служебные разделы
Disallow: /admin/
Disallow: /media/cache/
Disallow: /static/admin/
Disallow: /callback/
Disallow: /api/
# Разрешаем индексацию статических файлов
Allow: /static/
Allow: /media/
# Указываем главное зеркало
Host: {{ request.scheme }}://{{ request.get_host }}

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for url in urlset %}
<url>
<loc>{{ url.location }}</loc>
{% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
{% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
</url>
{% endfor %}
</urlset>

View File

@ -1,96 +0,0 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% load seo_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/solution-accordion.css' %}">
{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Проекты автоматизации 1С</h1>
<p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p>
</div>
<div class="improved-list">
{% autoescape off %}
{% for p in posts %}
<li class="modern-card fade-in">
<div class="content-card">
<h2>{{p.title}}</h2>
<!-- Добавляем микроразметку для проекта -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CreativeWork",
"name": "{{ p.title }}",
"description": "{{ p.description|striptags|truncatewords:30 }}",
"author": {
"@type": "Person",
"name": "Николай Сердюк"
},
"datePublished": "{{ p.time_create|date:'Y-m-d' }}"
}
</script>
<div class="solution-accordion">
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>📋 Описание задачи</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.description}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>🔧 Описание решения</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.implementation}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>✅ Результат</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.closing}}
</div>
</div>
</div>
<div class="article-panel">
<p class="first">Опубликовано: {{p.time_create|date:"d.m.Y"}}</p>
</div>
</div>
{% empty %}
<div class="modern-card text-center fade-in">
<h3>🚀 Проекты в разработке</h3>
<p class="card-subtitle">Скоро здесь появятся новые кейсы автоматизации</p>
</div>
</li>
{% endfor %}
{% endautoescape %}
</div>
{% if not posts %}
<div class="content-card text-center">
<h3>Примеры решений скоро появятся</h3>
<p>Мы готовим для вас интересные кейсы и решения</p>
</div>
{% endif %}
<!-- Подключаем внешний скрипт -->
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script>
{% endblock %}

View File

@ -1,14 +0,0 @@
from django import template
from ..models import CallbackRequest
register = template.Library()
@register.simple_tag
def get_unread_callbacks():
return CallbackRequest.objects.filter(is_read=False).count()
@register.simple_tag
def get_today_callbacks():
from django.utils import timezone
today = timezone.now().date()
return CallbackRequest.objects.filter(time_create__date=today).count()

View File

@ -1,21 +0,0 @@
from django import template
from django.utils.html import strip_tags
register = template.Library()
@register.simple_tag
def generate_meta_description(obj, default=""):
"""Генерирует meta description для объектов"""
if hasattr(obj, 'get_seo_description'):
return obj.get_seo_description()
elif hasattr(obj, 'content'):
clean_content = strip_tags(obj.content)[:160]
return clean_content + '...' if len(clean_content) > 160 else clean_content
return default
@register.simple_tag
def generate_meta_keywords(obj, default=""):
"""Генерирует meta keywords для объектов"""
if hasattr(obj, 'get_meta_keywords'):
return ', '.join(obj.get_meta_keywords())
return default

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,27 +0,0 @@
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from .views import *
from django.contrib.sitemaps.views import sitemap
from .sitemaps import sitemaps
urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('solutions/', solution, name='solution'),
path('competence/', ability, name='ability'),
path('recall/', recall, name='recall'),
path('post/<int:post_id>/', show_post, name='post'),
path('callback/', callback_request, name='callback'),
path('admin/statistics/', statistics_view, name='statistics'),
# Sitemap
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
path('robots.txt', robots_txt, name='robots'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,65 +0,0 @@
# programmer/utils/email_notifications.py
from django.core.mail import send_mail, EmailMultiAlternatives
from django.template.loader import render_to_string
from django.conf import settings
from django.utils.html import strip_tags
import logging
logger = logging.getLogger(__name__)
def send_callback_notification(callback_request):
"""
Отправляет уведомление о новой заявке на обратный звонок
"""
try:
subject = f'🚨 Новая заявка на обратный звонок от {callback_request.name}'
# HTML версия письма
html_message = render_to_string('emails/callback_notification.html', {
'callback': callback_request,
'site_url': settings.ALLOWED_HOSTS[0] if settings.ALLOWED_HOSTS else 'localhost',
})
# Текстовая версия письма
plain_message = strip_tags(html_message)
# Проверяем настройки email
if not all([settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD]):
logger.error("Email settings are not configured properly")
return False
# Отправляем email
send_mail(
subject=subject,
message=plain_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.ADMIN_EMAILS,
html_message=html_message,
fail_silently=False,
)
logger.info(f"Email notification sent successfully for callback #{callback_request.id}")
return True
except Exception as e:
logger.error(f"Error sending email notification: {e}")
return False
def send_test_email():
"""
Функция для тестирования отправки email
"""
try:
send_mail(
subject='📧 Test Email from Django',
message='This is a test email from your Django application.',
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=settings.ADMIN_EMAILS,
fail_silently=False,
)
return True
except Exception as e:
logger.error(f"Test email failed: {e}")
return False

View File

@ -1,259 +0,0 @@
from django.http import HttpResponse, HttpResponseNotFound
from .models import *
from django.shortcuts import render, redirect
from django.contrib import messages
from .models import CallbackRequest # Импортируем из models, а не forms
from .forms import CallbackForm
from django.utils import timezone
from datetime import timedelta
from .models import PageView, Visitor
from django.db.models import Count
from django.contrib.auth.decorators import login_required, user_passes_test
from django.views.decorators.http import require_GET
menu = [
{'title': "Главная", 'url_name': 'home'},
{'title': "Проекты", 'url_name': 'solution'},
{'title': "Компетенции", 'url_name': 'ability'},
{'title': "Отзывы", 'url_name': 'recall'},
{'title': "Обо мне", 'url_name': 'about'}
]
# === ДОБАВЬТЕ ЭТИ ФУНКЦИИ ЗДЕСЬ ===
def get_client_ip(request):
"""Получаем реальный IP клиента"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def should_track_request(request):
"""Определяем, нужно ли отслеживать запрос"""
client_ip = get_client_ip(request)
path = request.path
# Игнорируемые пути (Nextcloud специфичные)
nextcloud_paths = [
'/index.php',
'/status.php',
'/cron',
'/remote.php',
'/ocs',
'/apps/',
'/custom_apps/',
]
# Игнорируемые IP (Docker сети)
docker_ips = [
'192.168.64.1',
'192.168.65.1',
'172.17.0.1',
'172.18.0.1',
'172.19.0.1',
]
# Игнорируем статические файлы и админку
if path.startswith('/static/') or path.startswith('/admin/'):
return False
# Не отслеживаем Nextcloud и Docker запросы
if any(path.startswith(p) for p in nextcloud_paths):
return False
if client_ip in docker_ips:
return False
return True
def track_page_view(request):
"""Основная функция отслеживания просмотров"""
if not should_track_request(request):
return
try:
PageView.objects.create(
url=request.path,
ip_address=get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', '')[:500],
referer=request.META.get('HTTP_REFERER', '')[:500],
)
except Exception as e:
print(f"Error tracking page: {e}")
def track_view(view_func):
"""Декоратор для отслеживания просмотров страниц"""
from functools import wraps
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# Отслеживаем просмотр перед выполнением view
track_page_view(request)
return view_func(request, *args, **kwargs)
return _wrapped_view
@track_view
def index(request):
posts = Home.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Программист 1С Николай Сердюк - разработка и сопровождение",
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С.",
'meta_keywords': "программист 1С, разработка 1С, обновление 1С, сопровождение 1С, интеграция 1С, доработка 1С, 1С предприятие 8.3",
'form': CallbackForm()
}
return render(request, 'programmer/index.html', context=context)
@track_view
def about(request):
context = {
'menu': menu,
'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С",
'meta_description': "Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7.",
'meta_keywords': "программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, интеграция 1С, сертифицированный 1С, миграция 1С 7.7"
}
return render(request, 'programmer/about.html', context=context)
@track_view
def solution(request):
posts = Solution.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'meta_description': "Реализованные проекты по автоматизации 1С: складской учет с ТСД, интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы.",
'meta_keywords': "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, миграция 1С 7.7, кейсы 1С, примеры работ 1С",
'title': "Проекты автоматизации 1С | Реализованные кейсы и решения",
}
return render(request, 'programmer/solution.html', context=context)
@track_view
def ability(request):
posts = Competence.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Сертификаты и компетенции 1С | Программист 1С Николай Сердюк",
'meta_description': "Сертификаты 1С: Профессионал по платформе 8.3 и БП 3.0. Подтвержденная квалификация программиста 1С с сертификатами фирмы 1С.",
'meta_keywords': "сертификаты 1С, 1С профессионал, компетенции 1С, квалификация программиста 1С, сертифицированный специалист 1С"
}
return render(request, 'programmer/competence.html', context=context)
@track_view
def recall(request):
posts = Recall.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы",
'meta_description': "Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. Отзывы от ООО «РОВЕН-Регионы» и других компаний.",
'meta_keywords': "отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, реальные кейсы 1С, отзыв ООО РОВЕН"
}
return render(request, 'programmer/recall.html', context=context)
def show_post(request, post_id):
return HttpResponse(f"Отображение № {post_id}")
def pageNotFound(request, exception):
return HttpResponseNotFound('<h1>Страница не найдена</h1>')
def callback_request(request):
if request.method == 'POST':
form = CallbackForm(request.POST)
if form.is_valid():
# Сохраняем заявку через форму
form.save()
messages.success(request, '✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.')
return redirect('home')
else:
# Если форма невалидна, показываем ошибки
for field, errors in form.errors.items():
for error in errors:
messages.error(request, f'❌ Ошибка в поле {form.fields[field].label}: {error}')
return redirect('home')
# Если GET запрос, просто показываем главную страницу
return redirect('home')
def is_admin(user):
return user.is_staff
def is_staff(user):
return user.is_staff
@login_required
@user_passes_test(is_staff)
def statistics_view(request):
today = timezone.now().date()
week_ago = today - timedelta(days=7)
# Статистика за сегодня
today_views = PageView.objects.filter(
timestamp__date=today
).count()
# Статистика за неделю
weekly_views = PageView.objects.filter(
timestamp__date__gte=week_ago
).count()
# Всего просмотров
total_views = PageView.objects.count()
# Популярные страницы за неделю
popular_pages = PageView.objects.filter(
timestamp__date__gte=week_ago
).values('url').annotate(
views=Count('id')
).order_by('-views')[:10]
# Уникальные посетители за неделю
unique_visitors = Visitor.objects.filter(
last_visit__date__gte=week_ago
).count()
# Последние посещения
recent_views = PageView.objects.select_related().order_by('-timestamp')[:20]
today = timezone.now().date()
total_callbacks = CallbackRequest.objects.count()
today_callbacks = CallbackRequest.objects.filter(time_create__date=today).count()
unread_callbacks = CallbackRequest.objects.filter(is_read=False).count()
context = {
'today_views': today_views,
'weekly_views': weekly_views,
'total_views': total_views,
'unique_visitors': unique_visitors,
'popular_pages': popular_pages,
'recent_views': recent_views,
'total_callbacks': total_callbacks,
'today_callbacks': today_callbacks,
'unread_callbacks': unread_callbacks,
}
return render(request, 'admin/statistics.html', context)
@require_GET
def robots_txt(request):
return render(request, 'robots.txt', content_type='text/plain')

View File

@ -1,212 +1,226 @@
"""
Django settings for OneCprogsite project.
Generated by 'django-admin startproject' using Django 4.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os.path
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
# Безопасность cookies для HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'
# Если используете другие cookies
LANGUAGE_COOKIE_SECURE = True
LANGUAGE_COOKIE_HTTPONLY = True
LANGUAGE_COOKIE_SAMESITE = 'Lax'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Или разрешить конкретные домены (Django 4.0+)
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
# ОБЯЗАТЕЛЬНО укажите ваши домены
ALLOWED_HOSTS = [
'nikdizell.ru',
'www.nikdizell.ru',
'localhost',
'127.0.0.1',
'192.168.31.88' # Добавьте IP сервера
]
# Важно для работы за прокси
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
# Дополнительная безопасность
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
CSRF_TRUSTED_ORIGINS = [
'https://nikdizell.ru',
'https://www.nikdizell.ru',
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'programmer.apps.ProgrammerConfig',
'django_bootstrap5',
'django_extensions',
'django.contrib.sites',
'django.contrib.sitemaps',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'programmer.middleware.PageViewMiddleware',
]
ROOT_URLCONF = 'OneCprogsite.urls'
# Кастомный middleware для CSP
class CSPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
return response
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'programmer.context_processors.menu_processor',
'programmer.context_processors.contact_info',
],
},
},
]
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'App',
'USER': 'postgres',
'PASSWORD': 'NikDi94Zell',
'HOST': 'postgres',
'PORT': 5432,
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = []
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# Настройки email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# SERVER_EMAIL = EMAIL_HOST_USER
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
# Email для уведомлений (можно указать несколько через запятую)
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')
"""
Django settings for OneCprogsite project.
Generated by 'django-admin startproject' using Django 4.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os.path
import sys
from pathlib import Path
SITE_ID = 1
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE_DIR)) # Добавляем корень проекта
sys.path.insert(0, str(BASE_DIR / "OneCprogsite")) # Добавляем папку OneCprogsite
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
# Безопасность cookies для HTTPS
# SESSION_COOKIE_SECURE = True
# CSRF_COOKIE_SECURE = True
# SESSION_COOKIE_HTTPONLY = True
# CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
# SESSION_COOKIE_SAMESITE = 'Lax'
# CSRF_COOKIE_SAMESITE = 'Lax'
# Если используете другие cookies
# LANGUAGE_COOKIE_SECURE = True
# LANGUAGE_COOKIE_HTTPONLY = True
# LANGUAGE_COOKIE_SAMESITE = 'Lax'
# Для разработки (HTTP)
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
SECURE_SSL_REDIRECT = False
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# Или разрешить конкретные домены (Django 4.0+)
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
# ОБЯЗАТЕЛЬНО укажите ваши домены
ALLOWED_HOSTS = [
'nikdizell.ru',
'www.nikdizell.ru',
'localhost',
'127.0.0.1',
'192.168.31.88', # Добавьте IP сервера
'192.168.31.221',
]
# Важно для работы за прокси
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# SECURE_SSL_REDIRECT = True
#
# # Дополнительная безопасность
# SECURE_BROWSER_XSS_FILTER = True
# SECURE_CONTENT_TYPE_NOSNIFF = True
# SECURE_HSTS_SECONDS = 31536000
# SECURE_HSTS_INCLUDE_SUBDOMAINS = True
# SECURE_HSTS_PRELOAD = True
CSRF_TRUSTED_ORIGINS = [
'https://nikdizell.ru',
'https://www.nikdizell.ru',
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'programmer.apps.ProgrammerConfig',
'django_bootstrap5',
'django_extensions',
'django.contrib.sites',
'django.contrib.sitemaps',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'programmer.middleware.CSPMiddleware',
'programmer.middleware.PageViewMiddleware',
]
ROOT_URLCONF = 'OneCprogsite.urls'
# Кастомный middleware для CSP вынесен в отдельный файл
# class CSPMiddleware:
# def __init__(self, get_response):
# self.get_response = get_response
#
# def __call__(self, request):
# response = self.get_response(request)
# response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
# return response
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'programmer.context_processors.menu_processor',
'programmer.context_processors.contact_info',
],
},
},
]
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'App',
'USER': 'postgres',
'PASSWORD': 'NikDi94Zell',
'HOST': 'postgres',
'PORT': 5432,
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'programmer', 'static'),
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Настройки email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# SERVER_EMAIL = EMAIL_HOST_USER
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
# Email для уведомлений (можно указать несколько через запятую)
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')

View File

@ -1,275 +0,0 @@
select.admin-autocomplete {
width: 20em;
}
.select2-container--admin-autocomplete.select2-container {
min-height: 30px;
}
.select2-container--admin-autocomplete .select2-selection--single,
.select2-container--admin-autocomplete .select2-selection--multiple {
min-height: 30px;
padding: 0;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
border-color: var(--body-quiet-color);
min-height: 30px;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
padding: 0;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
padding: 0;
}
.select2-container--admin-autocomplete .select2-selection--single {
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
color: var(--body-fg);
line-height: 30px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
color: var(--body-quiet-color);
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
background-color: var(--darkened-bg);
cursor: default;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none;
}
.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
.select2-container--admin-autocomplete .select2-selection--multiple {
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: text;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 10px 5px 5px;
width: 100%;
display: flex;
flex-wrap: wrap;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
list-style: none;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
color: var(--body-quiet-color);
margin-top: 5px;
float: left;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin: 5px;
position: absolute;
right: 0;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
background-color: var(--darkened-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
color: var(--body-quiet-color);
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
color: var(--body-fg);
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
border: solid var(--body-quiet-color) 1px;
outline: 0;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
background-color: var(--darkened-bg);
cursor: default;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
display: none;
}
.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.select2-container--admin-autocomplete .select2-search--dropdown {
background: var(--darkened-bg);
}
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
background: transparent;
color: var(--body-fg);
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield;
}
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
color: var(--body-fg);
background: var(--body-bg);
}
.select2-container--admin-autocomplete .select2-results__option[role=group] {
padding: 0;
}
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
color: var(--body-quiet-color);
}
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
background-color: var(--selected-bg);
color: var(--body-fg);
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
padding-left: 1em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em;
}
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
background-color: var(--primary);
color: var(--primary-fg);
}
.select2-container--admin-autocomplete .select2-results__group {
cursor: default;
display: block;
padding: 6px;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,328 +0,0 @@
/* CHANGELISTS */
#changelist {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
#changelist .changelist-form-container {
flex: 1 1 auto;
min-width: 0;
}
#changelist table {
width: 100%;
}
.change-list .hiddenfields { display:none; }
.change-list .filtered table {
border-right: none;
}
.change-list .filtered {
min-height: 400px;
}
.change-list .filtered .results, .change-list .filtered .paginator,
.filtered #toolbar, .filtered div.xfull {
width: auto;
}
.change-list .filtered table tbody th {
padding-right: 1em;
}
#changelist-form .results {
overflow-x: auto;
width: 100%;
}
#changelist .toplinks {
border-bottom: 1px solid var(--hairline-color);
}
#changelist .paginator {
color: var(--body-quiet-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--body-bg);
overflow: hidden;
}
/* CHANGELIST TABLES */
#changelist table thead th {
padding: 0;
white-space: nowrap;
vertical-align: middle;
}
#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
}
#changelist table tbody td.action-checkbox {
text-align: center;
}
#changelist table tfoot {
color: var(--body-quiet-color);
}
/* TOOLBAR */
#toolbar {
padding: 8px 10px;
margin-bottom: 15px;
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
#toolbar form input {
border-radius: 4px;
font-size: 0.875rem;
padding: 5px;
color: var(--body-fg);
}
#toolbar #searchbar {
height: 1.1875rem;
border: 1px solid var(--border-color);
padding: 2px 5px;
margin: 0;
vertical-align: top;
font-size: 0.8125rem;
max-width: 100%;
}
#toolbar #searchbar:focus {
border-color: var(--body-quiet-color);
}
#toolbar form input[type="submit"] {
border: 1px solid var(--border-color);
font-size: 0.8125rem;
padding: 4px 8px;
margin: 0;
vertical-align: middle;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
color: var(--body-fg);
}
#toolbar form input[type="submit"]:focus,
#toolbar form input[type="submit"]:hover {
border-color: var(--body-quiet-color);
}
#changelist-search img {
vertical-align: middle;
margin-right: 4px;
}
#changelist-search .help {
word-break: break-word;
}
/* FILTER COLUMN */
#changelist-filter {
flex: 0 0 240px;
order: 1;
background: var(--darkened-bg);
border-left: none;
margin: 0 0 0 30px;
}
#changelist-filter h2 {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 5px 15px;
margin-bottom: 12px;
border-bottom: none;
}
#changelist-filter h3,
#changelist-filter details summary {
font-weight: 400;
padding: 0 15px;
margin-bottom: 10px;
}
#changelist-filter details summary > * {
display: inline;
}
#changelist-filter details > summary {
list-style-type: none;
}
#changelist-filter details > summary::-webkit-details-marker {
display: none;
}
#changelist-filter details > summary::before {
content: '→';
font-weight: bold;
color: var(--link-hover-color);
}
#changelist-filter details[open] > summary::before {
content: '↓';
}
#changelist-filter ul {
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid var(--hairline-color);
}
#changelist-filter ul:last-child {
border-bottom: none;
}
#changelist-filter li {
list-style-type: none;
margin-left: 0;
padding-left: 0;
}
#changelist-filter a {
display: block;
color: var(--body-quiet-color);
word-break: break-word;
}
#changelist-filter li.selected {
border-left: 5px solid var(--hairline-color);
padding-left: 10px;
margin-left: -15px;
}
#changelist-filter li.selected a {
color: var(--link-selected-fg);
}
#changelist-filter a:focus, #changelist-filter a:hover,
#changelist-filter li.selected a:focus,
#changelist-filter li.selected a:hover {
color: var(--link-hover-color);
}
#changelist-filter #changelist-filter-clear a {
font-size: 0.8125rem;
padding-bottom: 10px;
border-bottom: 1px solid var(--hairline-color);
}
/* DATE DRILLDOWN */
.change-list .toplinks {
display: flex;
padding-bottom: 5px;
flex-wrap: wrap;
gap: 3px 17px;
font-weight: bold;
}
.change-list .toplinks a {
font-size: 0.8125rem;
}
.change-list .toplinks .date-back {
color: var(--body-quiet-color);
}
.change-list .toplinks .date-back:focus,
.change-list .toplinks .date-back:hover {
color: var(--link-hover-color);
}
/* ACTIONS */
.filtered .actions {
border-right: none;
}
#changelist table input {
margin: 0;
vertical-align: baseline;
}
/* Once the :has() pseudo-class is supported by all browsers, the tr.selected
selector and the JS adding the class can be removed. */
#changelist tbody tr.selected {
background-color: var(--selected-row);
}
#changelist tbody tr:has(.action-select:checked) {
background-color: var(--selected-row);
}
#changelist .actions {
padding: 10px;
background: var(--body-bg);
border-top: none;
border-bottom: none;
line-height: 1.5rem;
color: var(--body-quiet-color);
width: 100%;
}
#changelist .actions span.all,
#changelist .actions span.action-counter,
#changelist .actions span.clear,
#changelist .actions span.question {
font-size: 0.8125rem;
margin: 0 0.5em;
}
#changelist .actions:last-child {
border-bottom: none;
}
#changelist .actions select {
vertical-align: top;
height: 1.5rem;
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.875rem;
padding: 0 0 0 4px;
margin: 0;
margin-left: 10px;
}
#changelist .actions select:focus {
border-color: var(--body-quiet-color);
}
#changelist .actions label {
display: inline-block;
vertical-align: middle;
font-size: 0.8125rem;
}
#changelist .actions .button {
font-size: 0.8125rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
height: 1.5rem;
line-height: 1;
padding: 4px 8px;
margin: 0;
color: var(--body-fg);
}
#changelist .actions .button:focus, #changelist .actions .button:hover {
border-color: var(--body-quiet-color);
}

View File

@ -1,137 +0,0 @@
@media (prefers-color-scheme: dark) {
:root {
--primary: #264b5d;
--primary-fg: #f7f7f7;
--body-fg: #eeeeee;
--body-bg: #121212;
--body-quiet-color: #e0e0e0;
--body-loud-color: #ffffff;
--breadcrumbs-link-fg: #e0e0e0;
--breadcrumbs-bg: var(--primary);
--link-fg: #81d4fa;
--link-hover-color: #4ac1f7;
--link-selected-fg: #6f94c6;
--hairline-color: #272727;
--border-color: #353535;
--error-fg: #e35f5f;
--message-success-bg: #006b1b;
--message-warning-bg: #583305;
--message-error-bg: #570808;
--darkened-bg: #212121;
--selected-bg: #1b1b1b;
--selected-row: #00363a;
--close-button-bg: #333333;
--close-button-hover-bg: #666666;
}
}
html[data-theme="dark"] {
--primary: #264b5d;
--primary-fg: #f7f7f7;
--body-fg: #eeeeee;
--body-bg: #121212;
--body-quiet-color: #e0e0e0;
--body-loud-color: #ffffff;
--breadcrumbs-link-fg: #e0e0e0;
--breadcrumbs-bg: var(--primary);
--link-fg: #81d4fa;
--link-hover-color: #4ac1f7;
--link-selected-fg: #6f94c6;
--hairline-color: #272727;
--border-color: #353535;
--error-fg: #e35f5f;
--message-success-bg: #006b1b;
--message-warning-bg: #583305;
--message-error-bg: #570808;
--darkened-bg: #212121;
--selected-bg: #1b1b1b;
--selected-row: #00363a;
--close-button-bg: #333333;
--close-button-hover-bg: #666666;
}
/* THEME SWITCH */
.theme-toggle {
cursor: pointer;
border: none;
padding: 0;
background: transparent;
vertical-align: middle;
margin-inline-start: 5px;
margin-top: -1px;
}
.theme-toggle svg {
vertical-align: middle;
height: 1rem;
width: 1rem;
display: none;
}
/*
Fully hide screen reader text so we only show the one matching the current
theme.
*/
.theme-toggle .visually-hidden {
display: none;
}
html[data-theme="auto"] .theme-toggle .theme-label-when-auto {
display: block;
}
html[data-theme="dark"] .theme-toggle .theme-label-when-dark {
display: block;
}
html[data-theme="light"] .theme-toggle .theme-label-when-light {
display: block;
}
/* ICONS */
.theme-toggle svg.theme-icon-when-auto,
.theme-toggle svg.theme-icon-when-dark,
.theme-toggle svg.theme-icon-when-light {
fill: var(--header-link-color);
color: var(--header-bg);
}
html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto {
display: block;
}
html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark {
display: block;
}
html[data-theme="light"] .theme-toggle svg.theme-icon-when-light {
display: block;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
color: var(--body-fg);
background-color: var(--body-bg);
}

View File

@ -1,29 +0,0 @@
/* DASHBOARD */
.dashboard td, .dashboard th {
word-break: break-word;
}
.dashboard .module table th {
width: 100%;
}
.dashboard .module table td {
white-space: nowrap;
}
.dashboard .module table td a {
display: block;
padding-right: .6em;
}
/* RECENT ACTIONS MODULE */
.module ul.actionlist {
margin-left: 0;
}
ul.actionlist li {
list-style-type: none;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,530 +0,0 @@
@import url('widgets.css');
/* FORM ROWS */
.form-row {
overflow: hidden;
padding: 10px;
font-size: 0.8125rem;
border-bottom: 1px solid var(--hairline-color);
}
.form-row img, .form-row input {
vertical-align: middle;
}
.form-row label input[type="checkbox"] {
margin-top: 0;
vertical-align: 0;
}
form .form-row p {
padding-left: 0;
}
.flex-container {
display: flex;
flex-wrap: wrap;
}
.form-multiline > div {
padding-bottom: 10px;
}
/* FORM LABELS */
label {
font-weight: normal;
color: var(--body-quiet-color);
font-size: 0.8125rem;
}
.required label, label.required {
font-weight: bold;
color: var(--body-fg);
}
/* RADIO BUTTONS */
form div.radiolist div {
padding-right: 7px;
}
form div.radiolist.inline div {
display: inline-block;
}
form div.radiolist label {
width: auto;
}
form div.radiolist input[type="radio"] {
margin: -2px 4px 0 0;
padding: 0;
}
form ul.inline {
margin-left: 0;
padding: 0;
}
form ul.inline li {
float: left;
padding-right: 7px;
}
/* ALIGNED FIELDSETS */
.aligned label {
display: block;
padding: 4px 10px 0 0;
width: 160px;
word-wrap: break-word;
line-height: 1;
}
.aligned label:not(.vCheckboxLabel):after {
content: '';
display: inline-block;
vertical-align: middle;
height: 1.625rem;
}
.aligned label + p, .aligned .checkbox-row + div.help, .aligned label + div.readonly {
padding: 6px 0;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
overflow-wrap: break-word;
}
.aligned ul label {
display: inline;
float: none;
width: auto;
}
.aligned .form-row input {
margin-bottom: 0;
}
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
width: 350px;
}
form .aligned ul {
margin-left: 160px;
padding-left: 10px;
}
form .aligned div.radiolist {
display: inline-block;
margin: 0;
padding: 0;
}
form .aligned p.help,
form .aligned div.help {
margin-top: 0;
margin-left: 160px;
padding-left: 10px;
}
form .aligned p.date div.help.timezonewarning,
form .aligned p.datetime div.help.timezonewarning,
form .aligned p.time div.help.timezonewarning {
margin-left: 0;
padding-left: 0;
font-weight: normal;
}
form .aligned p.help:last-child,
form .aligned div.help:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
form .aligned input + p.help,
form .aligned textarea + p.help,
form .aligned select + p.help,
form .aligned input + div.help,
form .aligned textarea + div.help,
form .aligned select + div.help {
margin-left: 160px;
padding-left: 10px;
}
form .aligned ul li {
list-style: none;
}
form .aligned table p {
margin-left: 0;
padding-left: 0;
}
.aligned .vCheckboxLabel {
float: none;
width: auto;
display: inline-block;
vertical-align: -3px;
padding: 0 0 5px 5px;
}
.aligned .vCheckboxLabel + p.help,
.aligned .vCheckboxLabel + div.help {
margin-top: -4px;
}
.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
width: 610px;
}
fieldset .fieldBox {
margin-right: 20px;
}
/* WIDE FIELDSETS */
.wide label {
width: 200px;
}
form .wide p,
form .wide ul.errorlist,
form .wide input + p.help,
form .wide input + div.help {
margin-left: 200px;
}
form .wide p.help,
form .wide div.help {
padding-left: 50px;
}
form div.help ul {
padding-left: 0;
margin-left: 0;
}
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
width: 450px;
}
/* COLLAPSED FIELDSETS */
fieldset.collapsed * {
display: none;
}
fieldset.collapsed h2, fieldset.collapsed {
display: block;
}
fieldset.collapsed {
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
}
fieldset.collapsed h2 {
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
fieldset .collapse-toggle {
color: var(--header-link-color);
}
fieldset.collapsed .collapse-toggle {
background: transparent;
display: inline;
color: var(--link-fg);
}
/* MONOSPACE TEXTAREAS */
fieldset.monospace textarea {
font-family: var(--font-family-monospace);
}
/* SUBMIT ROW */
.submit-row {
padding: 12px 14px 12px;
margin: 0 0 20px;
background: var(--darkened-bg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
body.popup .submit-row {
overflow: auto;
}
.submit-row input {
height: 2.1875rem;
line-height: 0.9375rem;
}
.submit-row input, .submit-row a {
margin: 0;
}
.submit-row input.default {
text-transform: uppercase;
}
.submit-row a.deletelink {
margin-left: auto;
}
.submit-row a.deletelink {
display: block;
background: var(--delete-button-bg);
border-radius: 4px;
padding: 0.625rem 0.9375rem;
height: 0.9375rem;
line-height: 0.9375rem;
color: var(--button-fg);
}
.submit-row a.closelink {
display: inline-block;
background: var(--close-button-bg);
border-radius: 4px;
padding: 10px 15px;
height: 0.9375rem;
line-height: 0.9375rem;
color: var(--button-fg);
}
.submit-row a.deletelink:focus,
.submit-row a.deletelink:hover,
.submit-row a.deletelink:active {
background: var(--delete-button-hover-bg);
text-decoration: none;
}
.submit-row a.closelink:focus,
.submit-row a.closelink:hover,
.submit-row a.closelink:active {
background: var(--close-button-hover-bg);
text-decoration: none;
}
/* CUSTOM FORM FIELDS */
.vSelectMultipleField {
vertical-align: top;
}
.vCheckboxField {
border: none;
}
.vDateField, .vTimeField {
margin-right: 2px;
margin-bottom: 4px;
}
.vDateField {
min-width: 6.85em;
}
.vTimeField {
min-width: 4.7em;
}
.vURLField {
width: 30em;
}
.vLargeTextField, .vXMLLargeTextField {
width: 48em;
}
.flatpages-flatpage #id_content {
height: 40.2em;
}
.module table .vPositiveSmallIntegerField {
width: 2.2em;
}
.vIntegerField {
width: 5em;
}
.vBigIntegerField {
width: 10em;
}
.vForeignKeyRawIdAdminField {
width: 5em;
}
.vTextField, .vUUIDField {
width: 20em;
}
/* INLINES */
.inline-group {
padding: 0;
margin: 0 0 30px;
}
.inline-group thead th {
padding: 8px 10px;
}
.inline-group .aligned label {
width: 160px;
}
.inline-related {
position: relative;
}
.inline-related h3 {
margin: 0;
color: var(--body-quiet-color);
padding: 5px;
font-size: 0.8125rem;
background: var(--darkened-bg);
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
}
.inline-related h3 span.delete {
float: right;
}
.inline-related h3 span.delete label {
margin-left: 2px;
font-size: 0.6875rem;
}
.inline-related fieldset {
margin: 0;
background: var(--body-bg);
border: none;
width: 100%;
}
.inline-related fieldset.module h3 {
margin: 0;
padding: 2px 5px 3px 5px;
font-size: 0.6875rem;
text-align: left;
font-weight: bold;
background: #bcd;
color: var(--body-bg);
}
.inline-group .tabular fieldset.module {
border: none;
}
.inline-related.tabular fieldset.module table {
width: 100%;
overflow-x: scroll;
}
.last-related fieldset {
border: none;
}
.inline-group .tabular tr.has_original td {
padding-top: 2em;
}
.inline-group .tabular tr td.original {
padding: 2px 0 0 0;
width: 0;
_position: relative;
}
.inline-group .tabular th.original {
width: 0px;
padding: 0;
}
.inline-group .tabular td.original p {
position: absolute;
left: 0;
height: 1.1em;
padding: 2px 9px;
overflow: hidden;
font-size: 0.5625rem;
font-weight: bold;
color: var(--body-quiet-color);
_width: 700px;
}
.inline-group ul.tools {
padding: 0;
margin: 0;
list-style: none;
}
.inline-group ul.tools li {
display: inline;
padding: 0 5px;
}
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
color: var(--body-quiet-color);
background: var(--darkened-bg);
padding: 8px 10px;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group .tabular tr.add-row td {
padding: 8px 10px;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group ul.tools a.add,
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
background: url(../img/icon-addlink.svg) 0 1px no-repeat;
padding-left: 16px;
font-size: 0.75rem;
}
.empty-form {
display: none;
}
/* RELATED FIELD ADD ONE / LOOKUP */
.related-lookup {
margin-left: 5px;
display: inline-block;
vertical-align: middle;
background-repeat: no-repeat;
background-size: 14px;
}
.related-lookup {
width: 1rem;
height: 1rem;
background-image: url(../img/search.svg);
}
form .related-widget-wrapper ul {
display: inline-block;
margin-left: 0;
padding-left: 0;
}
.clearable-file-input input {
margin-top: 0;
}

View File

@ -1,61 +0,0 @@
/* LOGIN FORM */
.login {
background: var(--darkened-bg);
height: auto;
}
.login #header {
height: auto;
padding: 15px 16px;
justify-content: center;
}
.login #header h1 {
font-size: 1.125rem;
margin: 0;
}
.login #header h1 a {
color: var(--header-link-color);
}
.login #content {
padding: 20px 20px 0;
}
.login #container {
background: var(--body-bg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
width: 28em;
min-width: 300px;
margin: 100px auto;
height: auto;
}
.login .form-row {
padding: 4px 0;
}
.login .form-row label {
display: block;
line-height: 2em;
}
.login .form-row #id_username, .login .form-row #id_password {
padding: 8px;
width: 100%;
box-sizing: border-box;
}
.login .submit-row {
padding: 1em 0 0 0;
margin: 0;
text-align: center;
}
.login .password-reset-link {
text-align: center;
}

View File

@ -1,144 +0,0 @@
.sticky {
position: sticky;
top: 0;
max-height: 100vh;
}
.toggle-nav-sidebar {
z-index: 20;
left: 0;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 23px;
width: 23px;
border: 0;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
cursor: pointer;
font-size: 1.25rem;
color: var(--link-fg);
padding: 0;
}
[dir="rtl"] .toggle-nav-sidebar {
border-left: 1px solid var(--hairline-color);
border-right: 0;
}
.toggle-nav-sidebar:hover,
.toggle-nav-sidebar:focus {
background-color: var(--darkened-bg);
}
#nav-sidebar {
z-index: 15;
flex: 0 0 275px;
left: -276px;
margin-left: -276px;
border-top: 1px solid transparent;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
overflow: auto;
}
[dir="rtl"] #nav-sidebar {
border-left: 1px solid var(--hairline-color);
border-right: 0;
left: 0;
margin-left: 0;
right: -276px;
margin-right: -276px;
}
.toggle-nav-sidebar::before {
content: '\00BB';
}
.main.shifted .toggle-nav-sidebar::before {
content: '\00AB';
}
.main > #nav-sidebar {
visibility: hidden;
}
.main.shifted > #nav-sidebar {
margin-left: 0;
visibility: visible;
}
[dir="rtl"] .main.shifted > #nav-sidebar {
margin-right: 0;
}
#nav-sidebar .module th {
width: 100%;
overflow-wrap: anywhere;
}
#nav-sidebar .module th,
#nav-sidebar .module caption {
padding-left: 16px;
}
#nav-sidebar .module td {
white-space: nowrap;
}
[dir="rtl"] #nav-sidebar .module th,
[dir="rtl"] #nav-sidebar .module caption {
padding-left: 8px;
padding-right: 16px;
}
#nav-sidebar .current-app .section:link,
#nav-sidebar .current-app .section:visited {
color: var(--header-color);
font-weight: bold;
}
#nav-sidebar .current-model {
background: var(--selected-row);
}
.main > #nav-sidebar + .content {
max-width: calc(100% - 23px);
}
.main.shifted > #nav-sidebar + .content {
max-width: calc(100% - 299px);
}
@media (max-width: 767px) {
#nav-sidebar, #toggle-nav-sidebar {
display: none;
}
.main > #nav-sidebar + .content,
.main.shifted > #nav-sidebar + .content {
max-width: 100%;
}
}
#nav-filter {
width: 100%;
box-sizing: border-box;
padding: 2px 5px;
margin: 5px 0;
border: 1px solid var(--border-color);
background-color: var(--darkened-bg);
color: var(--body-fg);
}
#nav-filter:focus {
border-color: var(--body-quiet-color);
}
#nav-filter.no-results {
background: var(--message-error-bg);
}
#nav-sidebar table {
width: 100%;
}

View File

@ -1,998 +0,0 @@
/* Tablets */
input[type="submit"], button {
-webkit-appearance: none;
appearance: none;
}
@media (max-width: 1024px) {
/* Basic */
html {
-webkit-text-size-adjust: 100%;
}
td, th {
padding: 10px;
font-size: 0.875rem;
}
.small {
font-size: 0.75rem;
}
/* Layout */
#container {
min-width: 0;
}
#content {
padding: 15px 20px 20px;
}
div.breadcrumbs {
padding: 10px 30px;
}
/* Header */
#header {
flex-direction: column;
padding: 15px 30px;
justify-content: flex-start;
}
#branding h1 {
margin: 0 0 8px;
line-height: 1.2;
}
#user-tools {
margin: 0;
font-weight: 400;
line-height: 1.85;
text-align: left;
}
#user-tools a {
display: inline-block;
line-height: 1.4;
}
/* Dashboard */
.dashboard #content {
width: auto;
}
#content-related {
margin-right: -290px;
}
.colSM #content-related {
margin-left: -290px;
}
.colMS {
margin-right: 290px;
}
.colSM {
margin-left: 290px;
}
.dashboard .module table td a {
padding-right: 0;
}
td .changelink, td .addlink {
font-size: 0.8125rem;
}
/* Changelist */
#toolbar {
border: none;
padding: 15px;
}
#changelist-search > div {
display: flex;
flex-wrap: nowrap;
max-width: 480px;
}
#changelist-search label {
line-height: 1.375rem;
}
#toolbar form #searchbar {
flex: 1 0 auto;
width: 0;
height: 1.375rem;
margin: 0 10px 0 6px;
}
#toolbar form input[type=submit] {
flex: 0 1 auto;
}
#changelist-search .quiet {
width: 0;
flex: 1 0 auto;
margin: 5px 0 0 25px;
}
#changelist .actions {
display: flex;
flex-wrap: wrap;
padding: 15px 0;
}
#changelist .actions label {
display: flex;
}
#changelist .actions select {
background: var(--body-bg);
}
#changelist .actions .button {
min-width: 48px;
margin: 0 10px;
}
#changelist .actions span.all,
#changelist .actions span.clear,
#changelist .actions span.question,
#changelist .actions span.action-counter {
font-size: 0.6875rem;
margin: 0 10px 0 0;
}
#changelist-filter {
flex-basis: 200px;
}
.change-list .filtered .results,
.change-list .filtered .paginator,
.filtered #toolbar,
.filtered .actions,
#changelist .paginator {
border-top-color: var(--hairline-color); /* XXX Is this used at all? */
}
#changelist .results + .paginator {
border-top: none;
}
/* Forms */
label {
font-size: 0.875rem;
}
.form-row input[type=text],
.form-row input[type=password],
.form-row input[type=email],
.form-row input[type=url],
.form-row input[type=tel],
.form-row input[type=number],
.form-row textarea,
.form-row select,
.form-row .vTextField {
box-sizing: border-box;
margin: 0;
padding: 6px 8px;
min-height: 2.25rem;
font-size: 0.875rem;
}
.form-row select {
height: 2.25rem;
}
.form-row select[multiple] {
height: auto;
min-height: 0;
}
fieldset .fieldBox + .fieldBox {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--hairline-color);
}
textarea {
max-width: 100%;
max-height: 120px;
}
.aligned label {
padding-top: 6px;
}
.aligned .related-lookup,
.aligned .datetimeshortcuts,
.aligned .related-lookup + strong {
align-self: center;
margin-left: 15px;
}
form .aligned div.radiolist {
margin-left: 2px;
}
.submit-row {
padding: 8px;
}
.submit-row a.deletelink {
padding: 10px 7px;
}
.button, input[type=submit], input[type=button], .submit-row input, a.button {
padding: 7px;
}
/* Related widget */
.related-widget-wrapper {
float: none;
}
.related-widget-wrapper-link + .selector {
max-width: calc(100% - 30px);
margin-right: 15px;
}
select + .related-widget-wrapper-link,
.related-widget-wrapper-link + .related-widget-wrapper-link {
margin-left: 10px;
}
/* Selector */
.selector {
display: flex;
width: 100%;
}
.selector .selector-filter {
display: flex;
align-items: center;
}
.selector .selector-filter label {
margin: 0 8px 0 0;
}
.selector .selector-filter input {
width: auto;
min-height: 0;
flex: 1 1;
}
.selector-available, .selector-chosen {
width: auto;
flex: 1 1;
display: flex;
flex-direction: column;
}
.selector select {
width: 100%;
flex: 1 0 auto;
margin-bottom: 5px;
}
.selector ul.selector-chooser {
width: 26px;
height: 52px;
padding: 2px 0;
margin: auto 15px;
border-radius: 20px;
transform: translateY(-10px);
}
.selector-add, .selector-remove {
width: 20px;
height: 20px;
background-size: 20px auto;
}
.selector-add {
background-position: 0 -120px;
}
.selector-remove {
background-position: 0 -80px;
}
a.selector-chooseall, a.selector-clearall {
align-self: center;
}
.stacked {
flex-direction: column;
max-width: 480px;
}
.stacked > * {
flex: 0 1 auto;
}
.stacked select {
margin-bottom: 0;
}
.stacked .selector-available, .stacked .selector-chosen {
width: auto;
}
.stacked ul.selector-chooser {
width: 52px;
height: 26px;
padding: 0 2px;
margin: 15px auto;
transform: none;
}
.stacked .selector-chooser li {
padding: 3px;
}
.stacked .selector-add, .stacked .selector-remove {
background-size: 20px auto;
}
.stacked .selector-add {
background-position: 0 -40px;
}
.stacked .active.selector-add {
background-position: 0 -40px;
}
.active.selector-add:focus, .active.selector-add:hover {
background-position: 0 -140px;
}
.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
background-position: 0 -60px;
}
.stacked .selector-remove {
background-position: 0 0;
}
.stacked .active.selector-remove {
background-position: 0 0;
}
.active.selector-remove:focus, .active.selector-remove:hover {
background-position: 0 -100px;
}
.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
background-position: 0 -20px;
}
.help-tooltip, .selector .help-icon {
display: none;
}
.datetime input {
width: 50%;
max-width: 120px;
}
.datetime span {
font-size: 0.8125rem;
}
.datetime .timezonewarning {
display: block;
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
.datetimeshortcuts {
color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
}
.form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
width: 75%;
}
.inline-group {
overflow: auto;
}
/* Messages */
ul.messagelist li {
padding-left: 55px;
background-position: 30px 12px;
}
ul.messagelist li.error {
background-position: 30px 12px;
}
ul.messagelist li.warning {
background-position: 30px 14px;
}
/* Login */
.login #header {
padding: 15px 20px;
}
.login #branding h1 {
margin: 0;
}
/* GIS */
div.olMap {
max-width: calc(100vw - 30px);
max-height: 300px;
}
.olMap + .clear_features {
display: block;
margin-top: 10px;
}
/* Docs */
.module table.xfull {
width: 100%;
}
pre.literal-block {
overflow: auto;
}
}
/* Mobile */
@media (max-width: 767px) {
/* Layout */
#header, #content, #footer {
padding: 15px;
}
#footer:empty {
padding: 0;
}
div.breadcrumbs {
padding: 10px 15px;
}
/* Dashboard */
.colMS, .colSM {
margin: 0;
}
#content-related, .colSM #content-related {
width: 100%;
margin: 0;
}
#content-related .module {
margin-bottom: 0;
}
#content-related .module h2 {
padding: 10px 15px;
font-size: 1rem;
}
/* Changelist */
#changelist {
align-items: stretch;
flex-direction: column;
}
#toolbar {
padding: 10px;
}
#changelist-filter {
margin-left: 0;
}
#changelist .actions label {
flex: 1 1;
}
#changelist .actions select {
flex: 1 0;
width: 100%;
}
#changelist .actions span {
flex: 1 0 100%;
}
#changelist-filter {
position: static;
width: auto;
margin-top: 30px;
}
.object-tools {
float: none;
margin: 0 0 15px;
padding: 0;
overflow: hidden;
}
.object-tools li {
height: auto;
margin-left: 0;
}
.object-tools li + li {
margin-left: 15px;
}
/* Forms */
.form-row {
padding: 15px 0;
}
.aligned .form-row,
.aligned .form-row > div {
max-width: 100vw;
}
.aligned .form-row > div {
width: calc(100vw - 30px);
}
.flex-container {
flex-flow: column;
}
textarea {
max-width: none;
}
.vURLField {
width: auto;
}
fieldset .fieldBox + .fieldBox {
margin-top: 15px;
padding-top: 15px;
}
fieldset.collapsed .form-row {
display: none;
}
.aligned label {
width: 100%;
padding: 0 0 10px;
}
.aligned label:after {
max-height: 0;
}
.aligned .form-row input,
.aligned .form-row select,
.aligned .form-row textarea {
flex: 1 1 auto;
max-width: 100%;
}
.aligned .checkbox-row {
align-items: center;
}
.aligned .checkbox-row input {
flex: 0 1 auto;
margin: 0;
}
.aligned .vCheckboxLabel {
flex: 1 0;
padding: 1px 0 0 5px;
}
.aligned label + p,
.aligned label + div.help,
.aligned label + div.readonly {
padding: 0;
margin-left: 0;
}
.aligned p.file-upload {
font-size: 0.8125rem;
}
span.clearable-file-input {
margin-left: 15px;
}
span.clearable-file-input label {
font-size: 0.8125rem;
padding-bottom: 0;
}
.aligned .timezonewarning {
flex: 1 0 100%;
margin-top: 5px;
}
form .aligned .form-row div.help {
width: 100%;
margin: 5px 0 0;
padding: 0;
}
form .aligned ul,
form .aligned ul.errorlist {
margin-left: 0;
padding-left: 0;
}
form .aligned div.radiolist {
margin-top: 5px;
margin-right: 15px;
margin-bottom: -3px;
}
form .aligned div.radiolist:not(.inline) div + div {
margin-top: 5px;
}
/* Related widget */
.related-widget-wrapper {
width: 100%;
display: flex;
align-items: flex-start;
}
.related-widget-wrapper .selector {
order: 1;
}
.related-widget-wrapper > a {
order: 2;
}
.related-widget-wrapper .radiolist ~ a {
align-self: flex-end;
}
.related-widget-wrapper > select ~ a {
align-self: center;
}
select + .related-widget-wrapper-link,
.related-widget-wrapper-link + .related-widget-wrapper-link {
margin-left: 15px;
}
/* Selector */
.selector {
flex-direction: column;
}
.selector > * {
float: none;
}
.selector-available, .selector-chosen {
margin-bottom: 0;
flex: 1 1 auto;
}
.selector select {
max-height: 96px;
}
.selector ul.selector-chooser {
display: block;
float: none;
width: 52px;
height: 26px;
padding: 0 2px;
margin: 15px auto 20px;
transform: none;
}
.selector ul.selector-chooser li {
float: left;
}
.selector-remove {
background-position: 0 0;
}
.active.selector-remove:focus, .active.selector-remove:hover {
background-position: 0 -20px;
}
.selector-add {
background-position: 0 -40px;
}
.active.selector-add:focus, .active.selector-add:hover {
background-position: 0 -60px;
}
/* Inlines */
.inline-group[data-inline-type="stacked"] .inline-related {
border: 1px solid var(--hairline-color);
border-radius: 4px;
margin-top: 15px;
overflow: auto;
}
.inline-group[data-inline-type="stacked"] .inline-related > * {
box-sizing: border-box;
}
.inline-group[data-inline-type="stacked"] .inline-related .module {
padding: 0 10px;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
border-top: none;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 {
padding: 10px;
border-top-width: 0;
border-bottom-width: 2px;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 .inline_label {
margin-right: auto;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 span.delete {
float: none;
flex: 1 1 100%;
margin-top: 5px;
}
.inline-group[data-inline-type="stacked"] .aligned .form-row > div:not([class]) {
width: 100%;
}
.inline-group[data-inline-type="stacked"] .aligned label {
width: 100%;
}
.inline-group[data-inline-type="stacked"] div.add-row {
margin-top: 15px;
border: 1px solid var(--hairline-color);
border-radius: 4px;
}
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
padding: 0;
}
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
display: block;
padding: 8px 10px 8px 26px;
background-position: 8px 9px;
}
/* Submit row */
.submit-row {
padding: 10px;
margin: 0 0 15px;
flex-direction: column;
gap: 8px;
}
.submit-row input, .submit-row input.default, .submit-row a {
text-align: center;
}
.submit-row a.closelink {
padding: 10px 0;
text-align: center;
}
.submit-row a.deletelink {
margin: 0;
}
/* Messages */
ul.messagelist li {
padding-left: 40px;
background-position: 15px 12px;
}
ul.messagelist li.error {
background-position: 15px 12px;
}
ul.messagelist li.warning {
background-position: 15px 14px;
}
/* Paginator */
.paginator .this-page, .paginator a:link, .paginator a:visited {
padding: 4px 10px;
}
/* Login */
body.login {
padding: 0 15px;
}
.login #container {
width: auto;
max-width: 480px;
margin: 50px auto;
}
.login #header,
.login #content {
padding: 15px;
}
.login #content-main {
float: none;
}
.login .form-row {
padding: 0;
}
.login .form-row + .form-row {
margin-top: 15px;
}
.login .form-row label {
margin: 0 0 5px;
line-height: 1.2;
}
.login .submit-row {
padding: 15px 0 0;
}
.login br {
display: none;
}
.login .submit-row input {
margin: 0;
text-transform: uppercase;
}
.errornote {
margin: 0 0 20px;
padding: 8px 12px;
font-size: 0.8125rem;
}
/* Calendar and clock */
.calendarbox, .clockbox {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
margin: 0;
border: none;
overflow: visible;
}
.calendarbox:before, .clockbox:before {
content: '';
position: fixed;
top: 50%;
left: 50%;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
transform: translate(-50%, -50%);
}
.calendarbox > *, .clockbox > * {
position: relative;
z-index: 1;
}
.calendarbox > div:first-child {
z-index: 2;
}
.calendarbox .calendar, .clockbox h2 {
border-radius: 4px 4px 0 0;
overflow: hidden;
}
.calendarbox .calendar-cancel, .clockbox .calendar-cancel {
border-radius: 0 0 4px 4px;
overflow: hidden;
}
.calendar-shortcuts {
padding: 10px 0;
font-size: 0.75rem;
line-height: 0.75rem;
}
.calendar-shortcuts a {
margin: 0 4px;
}
.timelist a {
background: var(--body-bg);
padding: 4px;
}
.calendar-cancel {
padding: 8px 10px;
}
.clockbox h2 {
padding: 8px 15px;
}
.calendar caption {
padding: 10px;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
z-index: 1;
top: 10px;
}
/* History */
table#change-history tbody th, table#change-history tbody td {
font-size: 0.8125rem;
word-break: break-word;
}
table#change-history tbody th {
width: auto;
}
/* Docs */
table.model tbody th, table.model tbody td {
font-size: 0.8125rem;
word-break: break-word;
}
}

View File

@ -1,81 +0,0 @@
/* TABLETS */
@media (max-width: 1024px) {
[dir="rtl"] .colMS {
margin-right: 0;
}
[dir="rtl"] #user-tools {
text-align: right;
}
[dir="rtl"] #changelist .actions label {
padding-left: 10px;
padding-right: 0;
}
[dir="rtl"] #changelist .actions select {
margin-left: 0;
margin-right: 15px;
}
[dir="rtl"] .change-list .filtered .results,
[dir="rtl"] .change-list .filtered .paginator,
[dir="rtl"] .filtered #toolbar,
[dir="rtl"] .filtered div.xfull,
[dir="rtl"] .filtered .actions,
[dir="rtl"] #changelist-filter {
margin-left: 0;
}
[dir="rtl"] .inline-group ul.tools a.add,
[dir="rtl"] .inline-group div.add-row a,
[dir="rtl"] .inline-group .tabular tr.add-row td a {
padding: 8px 26px 8px 10px;
background-position: calc(100% - 8px) 9px;
}
[dir="rtl"] .related-widget-wrapper-link + .selector {
margin-right: 0;
margin-left: 15px;
}
[dir="rtl"] .selector .selector-filter label {
margin-right: 0;
margin-left: 8px;
}
[dir="rtl"] .object-tools li {
float: right;
}
[dir="rtl"] .object-tools li + li {
margin-left: 0;
margin-right: 15px;
}
[dir="rtl"] .dashboard .module table td a {
padding-left: 0;
padding-right: 16px;
}
}
/* MOBILE */
@media (max-width: 767px) {
[dir="rtl"] .aligned .related-lookup,
[dir="rtl"] .aligned .datetimeshortcuts {
margin-left: 0;
margin-right: 15px;
}
[dir="rtl"] .aligned ul,
[dir="rtl"] form .aligned ul.errorlist {
margin-right: 0;
}
[dir="rtl"] #changelist-filter {
margin-left: 0;
margin-right: 0;
}
}

View File

@ -1,288 +0,0 @@
/* GLOBAL */
th {
text-align: right;
}
.module h2, .module caption {
text-align: right;
}
.module ul, .module ol {
margin-left: 0;
margin-right: 1.5em;
}
.viewlink, .addlink, .changelink {
padding-left: 0;
padding-right: 16px;
background-position: 100% 1px;
}
.deletelink {
padding-left: 0;
padding-right: 16px;
background-position: 100% 1px;
}
.object-tools {
float: left;
}
thead th:first-child,
tfoot td:first-child {
border-left: none;
}
/* LAYOUT */
#user-tools {
right: auto;
left: 0;
text-align: left;
}
div.breadcrumbs {
text-align: right;
}
#content-main {
float: right;
}
#content-related {
float: left;
margin-left: -300px;
margin-right: auto;
}
.colMS {
margin-left: 300px;
margin-right: 0;
}
/* SORTABLE TABLES */
table thead th.sorted .sortoptions {
float: left;
}
thead th.sorted .text {
padding-right: 0;
padding-left: 42px;
}
/* dashboard styles */
.dashboard .module table td a {
padding-left: .6em;
padding-right: 16px;
}
/* changelists styles */
.change-list .filtered table {
border-left: none;
border-right: 0px none;
}
#changelist-filter {
border-left: none;
border-right: none;
margin-left: 0;
margin-right: 30px;
}
#changelist-filter li.selected {
border-left: none;
padding-left: 10px;
margin-left: 0;
border-right: 5px solid var(--hairline-color);
padding-right: 10px;
margin-right: -15px;
}
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
border-right: none;
border-left: none;
}
/* FORMS */
.aligned label {
padding: 0 0 3px 1em;
}
.submit-row a.deletelink {
margin-left: 0;
margin-right: auto;
}
.vDateField, .vTimeField {
margin-left: 2px;
}
.aligned .form-row input {
margin-left: 5px;
}
form .aligned ul {
margin-right: 163px;
padding-right: 10px;
margin-left: 0;
padding-left: 0;
}
form ul.inline li {
float: right;
padding-right: 0;
padding-left: 7px;
}
form .aligned p.help,
form .aligned div.help {
margin-right: 160px;
padding-right: 10px;
}
form div.help ul,
form .aligned .checkbox-row + .help,
form .aligned p.date div.help.timezonewarning,
form .aligned p.datetime div.help.timezonewarning,
form .aligned p.time div.help.timezonewarning {
margin-right: 0;
padding-right: 0;
}
form .wide p.help, form .wide div.help {
padding-left: 0;
padding-right: 50px;
}
form .wide p,
form .wide ul.errorlist,
form .wide input + p.help,
form .wide input + div.help {
margin-right: 200px;
margin-left: 0px;
}
.submit-row {
text-align: right;
}
fieldset .fieldBox {
margin-left: 20px;
margin-right: 0;
}
.errorlist li {
background-position: 100% 12px;
padding: 0;
}
.errornote {
background-position: 100% 12px;
padding: 10px 12px;
}
/* WIDGETS */
.calendarnav-previous {
top: 0;
left: auto;
right: 10px;
background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
}
.calendarbox .calendarnav-previous:focus,
.calendarbox .calendarnav-previous:hover {
background-position: 0 -45px;
}
.calendarnav-next {
top: 0;
right: auto;
left: 10px;
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
}
.calendarbox .calendarnav-next:focus,
.calendarbox .calendarnav-next:hover {
background-position: 0 -15px;
}
.calendar caption, .calendarbox h2 {
text-align: center;
}
.selector {
float: right;
}
.selector .selector-filter {
text-align: right;
}
.selector-add {
background: url(../img/selector-icons.svg) 0 -64px no-repeat;
}
.active.selector-add:focus, .active.selector-add:hover {
background-position: 0 -80px;
}
.selector-remove {
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
}
.active.selector-remove:focus, .active.selector-remove:hover {
background-position: 0 -112px;
}
a.selector-chooseall {
background: url(../img/selector-icons.svg) right -128px no-repeat;
}
a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
background-position: 100% -144px;
}
a.selector-clearall {
background: url(../img/selector-icons.svg) 0 -160px no-repeat;
}
a.active.selector-clearall:focus, a.active.selector-clearall:hover {
background-position: 0 -176px;
}
.inline-deletelink {
float: left;
}
form .form-row p.datetime {
overflow: hidden;
}
.related-widget-wrapper {
float: right;
}
/* MISC */
.inline-related h2, .inline-group h2 {
text-align: right
}
.inline-related h3 span.delete {
padding-right: 20px;
padding-left: inherit;
left: 10px;
right: inherit;
float:left;
}
.inline-related h3 span.delete label {
margin-left: inherit;
margin-right: 2px;
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,481 +0,0 @@
.select2-container {
box-sizing: border-box;
display: inline-block;
margin: 0;
position: relative;
vertical-align: middle; }
.select2-container .select2-selection--single {
box-sizing: border-box;
cursor: pointer;
display: block;
height: 28px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--single .select2-selection__rendered {
display: block;
padding-left: 8px;
padding-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-selection--single .select2-selection__clear {
position: relative; }
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 8px;
padding-left: 20px; }
.select2-container .select2-selection--multiple {
box-sizing: border-box;
cursor: pointer;
display: block;
min-height: 32px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--multiple .select2-selection__rendered {
display: inline-block;
overflow: hidden;
padding-left: 8px;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-search--inline {
float: left; }
.select2-container .select2-search--inline .select2-search__field {
box-sizing: border-box;
border: none;
font-size: 100%;
margin-top: 5px;
padding: 0; }
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-dropdown {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
box-sizing: border-box;
display: block;
position: absolute;
left: -100000px;
width: 100%;
z-index: 1051; }
.select2-results {
display: block; }
.select2-results__options {
list-style: none;
margin: 0;
padding: 0; }
.select2-results__option {
padding: 6px;
user-select: none;
-webkit-user-select: none; }
.select2-results__option[aria-selected] {
cursor: pointer; }
.select2-container--open .select2-dropdown {
left: 0; }
.select2-container--open .select2-dropdown--above {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--open .select2-dropdown--below {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-search--dropdown {
display: block;
padding: 4px; }
.select2-search--dropdown .select2-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box; }
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-search--dropdown.select2-search--hide {
display: none; }
.select2-close-mask {
border: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 99;
background-color: #fff;
filter: alpha(opacity=0); }
.select2-hidden-accessible {
border: 0 !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(50%) !important;
clip-path: inset(50%) !important;
height: 1px !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important;
white-space: nowrap !important; }
.select2-container--default .select2-selection--single {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px; }
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--default .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold; }
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px; }
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto; }
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none; }
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--default .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
list-style: none; }
.select2-container--default .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-top: 5px;
margin-right: 10px;
padding: 1px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: solid black 1px;
outline: 0; }
.select2-container--default.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
display: none; }
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--default .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa; }
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield; }
.select2-container--default .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--default .select2-results__option[role=group] {
padding: 0; }
.select2-container--default .select2-results__option[aria-disabled=true] {
color: #999; }
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #ddd; }
.select2-container--default .select2-results__option .select2-results__option {
padding-left: 1em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em; }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #5897fb;
color: white; }
.select2-container--default .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic .select2-selection--single {
background-color: #f7f7f7;
border: 1px solid #aaa;
border-radius: 4px;
outline: 0;
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic .select2-selection--single:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--classic .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px; }
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--classic .select2-selection--single .select2-selection__arrow {
background-color: #ddd;
border: none;
border-left: 1px solid #aaa;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
border: none;
border-right: 1px solid #aaa;
border-radius: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
right: auto; }
.select2-container--classic.select2-container--open .select2-selection--single {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
background: transparent;
border: none; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
.select2-container--classic .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
outline: 0; }
.select2-container--classic .select2-selection--multiple:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
list-style: none;
margin: 0;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
display: none; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: #888;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #555; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
float: right;
margin-left: 5px;
margin-right: auto; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--classic.select2-container--open .select2-selection--multiple {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--classic .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
outline: 0; }
.select2-container--classic .select2-search--inline .select2-search__field {
outline: 0;
box-shadow: none; }
.select2-container--classic .select2-dropdown {
background-color: white;
border: 1px solid transparent; }
.select2-container--classic .select2-dropdown--above {
border-bottom: none; }
.select2-container--classic .select2-dropdown--below {
border-top: none; }
.select2-container--classic .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--classic .select2-results__option[role=group] {
padding: 0; }
.select2-container--classic .select2-results__option[aria-disabled=true] {
color: grey; }
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
background-color: #3875d7;
color: white; }
.select2-container--classic .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic.select2-container--open .select2-dropdown {
border-color: #5897fb; }

File diff suppressed because one or more lines are too long

View File

@ -1,603 +0,0 @@
/* SELECTOR (FILTER INTERFACE) */
.selector {
width: 800px;
float: left;
display: flex;
}
.selector select {
width: 380px;
height: 17.2em;
flex: 1 0 auto;
}
.selector-available, .selector-chosen {
width: 380px;
text-align: center;
margin-bottom: 5px;
display: flex;
flex-direction: column;
}
.selector-available h2, .selector-chosen h2 {
border: 1px solid var(--border-color);
border-radius: 4px 4px 0 0;
}
.selector-chosen .list-footer-display {
border: 1px solid var(--border-color);
border-top: none;
border-radius: 0 0 4px 4px;
margin: 0 0 10px;
padding: 8px;
text-align: center;
background: var(--primary);
color: var(--header-link-color);
cursor: pointer;
}
.selector-chosen .list-footer-display__clear {
color: var(--breadcrumbs-fg);
}
.selector-chosen h2 {
background: var(--primary);
color: var(--header-link-color);
}
.selector .selector-available h2 {
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
.selector .selector-filter {
border: 1px solid var(--border-color);
border-width: 0 1px;
padding: 8px;
color: var(--body-quiet-color);
font-size: 0.625rem;
margin: 0;
text-align: left;
}
.selector .selector-filter label,
.inline-group .aligned .selector .selector-filter label {
float: left;
margin: 7px 0 0;
width: 18px;
height: 18px;
padding: 0;
overflow: hidden;
line-height: 1;
}
.selector .selector-available input,
.selector .selector-chosen input {
width: 320px;
margin-left: 8px;
}
.selector ul.selector-chooser {
align-self: center;
width: 22px;
background-color: var(--selected-bg);
border-radius: 10px;
margin: 0 5px;
padding: 0;
transform: translateY(-17px);
}
.selector-chooser li {
margin: 0;
padding: 3px;
list-style-type: none;
}
.selector select {
padding: 0 10px;
margin: 0 0 10px;
border-radius: 0 0 4px 4px;
}
.selector .selector-chosen--with-filtered select {
margin: 0;
border-radius: 0;
height: 14em;
}
.selector .selector-chosen:not(.selector-chosen--with-filtered) .list-footer-display {
display: none;
}
.selector-add, .selector-remove {
width: 16px;
height: 16px;
display: block;
text-indent: -3000px;
overflow: hidden;
cursor: default;
opacity: 0.55;
}
.active.selector-add, .active.selector-remove {
opacity: 1;
}
.active.selector-add:hover, .active.selector-remove:hover {
cursor: pointer;
}
.selector-add {
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
}
.active.selector-add:focus, .active.selector-add:hover {
background-position: 0 -112px;
}
.selector-remove {
background: url(../img/selector-icons.svg) 0 -64px no-repeat;
}
.active.selector-remove:focus, .active.selector-remove:hover {
background-position: 0 -80px;
}
a.selector-chooseall, a.selector-clearall {
display: inline-block;
height: 16px;
text-align: left;
margin: 1px auto 3px;
overflow: hidden;
font-weight: bold;
line-height: 16px;
color: var(--body-quiet-color);
text-decoration: none;
opacity: 0.55;
}
a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
color: var(--link-fg);
}
a.active.selector-chooseall, a.active.selector-clearall {
opacity: 1;
}
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
cursor: pointer;
}
a.selector-chooseall {
padding: 0 18px 0 0;
background: url(../img/selector-icons.svg) right -160px no-repeat;
cursor: default;
}
a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
background-position: 100% -176px;
}
a.selector-clearall {
padding: 0 0 0 18px;
background: url(../img/selector-icons.svg) 0 -128px no-repeat;
cursor: default;
}
a.active.selector-clearall:focus, a.active.selector-clearall:hover {
background-position: 0 -144px;
}
/* STACKED SELECTORS */
.stacked {
float: left;
width: 490px;
display: block;
}
.stacked select {
width: 480px;
height: 10.1em;
}
.stacked .selector-available, .stacked .selector-chosen {
width: 480px;
}
.stacked .selector-available {
margin-bottom: 0;
}
.stacked .selector-available input {
width: 422px;
}
.stacked ul.selector-chooser {
height: 22px;
width: 50px;
margin: 0 0 10px 40%;
background-color: #eee;
border-radius: 10px;
transform: none;
}
.stacked .selector-chooser li {
float: left;
padding: 3px 3px 3px 5px;
}
.stacked .selector-chooseall, .stacked .selector-clearall {
display: none;
}
.stacked .selector-add {
background: url(../img/selector-icons.svg) 0 -32px no-repeat;
cursor: default;
}
.stacked .active.selector-add {
background-position: 0 -32px;
cursor: pointer;
}
.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
background-position: 0 -48px;
cursor: pointer;
}
.stacked .selector-remove {
background: url(../img/selector-icons.svg) 0 0 no-repeat;
cursor: default;
}
.stacked .active.selector-remove {
background-position: 0 0px;
cursor: pointer;
}
.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
background-position: 0 -16px;
cursor: pointer;
}
.selector .help-icon {
background: url(../img/icon-unknown.svg) 0 0 no-repeat;
display: inline-block;
vertical-align: middle;
margin: -2px 0 0 2px;
width: 13px;
height: 13px;
}
.selector .selector-chosen .help-icon {
background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat;
}
.selector .search-label-icon {
background: url(../img/search.svg) 0 0 no-repeat;
display: inline-block;
height: 1.125rem;
width: 1.125rem;
}
/* DATE AND TIME */
p.datetime {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-weight: bold;
}
.datetime span {
white-space: nowrap;
font-weight: normal;
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
margin-left: 5px;
margin-bottom: 4px;
}
table p.datetime {
font-size: 0.6875rem;
margin-left: 0;
padding-left: 0;
}
.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon {
position: relative;
display: inline-block;
vertical-align: middle;
height: 16px;
width: 16px;
overflow: hidden;
}
.datetimeshortcuts .clock-icon {
background: url(../img/icon-clock.svg) 0 0 no-repeat;
}
.datetimeshortcuts a:focus .clock-icon,
.datetimeshortcuts a:hover .clock-icon {
background-position: 0 -16px;
}
.datetimeshortcuts .date-icon {
background: url(../img/icon-calendar.svg) 0 0 no-repeat;
top: -1px;
}
.datetimeshortcuts a:focus .date-icon,
.datetimeshortcuts a:hover .date-icon {
background-position: 0 -16px;
}
.timezonewarning {
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
/* URL */
p.url {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-size: 0.6875rem;
font-weight: bold;
}
.url a {
font-weight: normal;
}
/* FILE UPLOADS */
p.file-upload {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-size: 0.6875rem;
font-weight: bold;
}
.file-upload a {
font-weight: normal;
}
.file-upload .deletelink {
margin-left: 5px;
}
span.clearable-file-input label {
color: var(--body-fg);
font-size: 0.6875rem;
display: inline;
float: none;
}
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox {
margin: 5px auto;
font-size: 0.75rem;
width: 19em;
text-align: center;
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
overflow: hidden;
position: relative;
}
.clockbox {
width: auto;
}
.calendar {
margin: 0;
padding: 0;
}
.calendar table {
margin: 0;
padding: 0;
border-collapse: collapse;
background: white;
width: 100%;
}
.calendar caption, .calendarbox h2 {
margin: 0;
text-align: center;
border-top: none;
font-weight: 700;
font-size: 0.75rem;
color: #333;
background: var(--accent);
}
.calendar th {
padding: 8px 5px;
background: var(--darkened-bg);
border-bottom: 1px solid var(--border-color);
font-weight: 400;
font-size: 0.75rem;
text-align: center;
color: var(--body-quiet-color);
}
.calendar td {
font-weight: 400;
font-size: 0.75rem;
text-align: center;
padding: 0;
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.calendar td.selected a {
background: var(--primary);
color: var(--button-fg);
}
.calendar td.nonday {
background: var(--darkened-bg);
}
.calendar td.today a {
font-weight: 700;
}
.calendar td a, .timelist a {
display: block;
font-weight: 400;
padding: 6px;
text-decoration: none;
color: var(--body-quiet-color);
}
.calendar td a:focus, .timelist a:focus,
.calendar td a:hover, .timelist a:hover {
background: var(--primary);
color: white;
}
.calendar td a:active, .timelist a:active {
background: var(--header-bg);
color: white;
}
.calendarnav {
font-size: 0.625rem;
text-align: center;
color: #ccc;
margin: 0;
padding: 1px 3px;
}
.calendarnav a:link, #calendarnav a:visited,
#calendarnav a:focus, #calendarnav a:hover {
color: var(--body-quiet-color);
}
.calendar-shortcuts {
background: var(--body-bg);
color: var(--body-quiet-color);
font-size: 0.6875rem;
line-height: 0.6875rem;
border-top: 1px solid var(--hairline-color);
padding: 8px 0;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
display: block;
position: absolute;
top: 8px;
width: 15px;
height: 15px;
text-indent: -9999px;
padding: 0;
}
.calendarnav-previous {
left: 10px;
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
}
.calendarbox .calendarnav-previous:focus,
.calendarbox .calendarnav-previous:hover {
background-position: 0 -15px;
}
.calendarnav-next {
right: 10px;
background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
}
.calendarbox .calendarnav-next:focus,
.calendarbox .calendarnav-next:hover {
background-position: 0 -45px;
}
.calendar-cancel {
margin: 0;
padding: 4px 0;
font-size: 0.75rem;
background: #eee;
border-top: 1px solid var(--border-color);
color: var(--body-fg);
}
.calendar-cancel:focus, .calendar-cancel:hover {
background: #ddd;
}
.calendar-cancel a {
color: black;
display: block;
}
ul.timelist, .timelist li {
list-style-type: none;
margin: 0;
padding: 0;
}
.timelist a {
padding: 2px;
}
/* EDIT INLINE */
.inline-deletelink {
float: right;
text-indent: -9999px;
background: url(../img/inline-delete.svg) 0 0 no-repeat;
width: 16px;
height: 16px;
border: 0px none;
}
.inline-deletelink:focus, .inline-deletelink:hover {
cursor: pointer;
}
/* RELATED WIDGET WRAPPER */
.related-widget-wrapper {
float: left; /* display properly in form rows with multiple fields */
overflow: hidden; /* clear floated contents */
}
.related-widget-wrapper-link {
opacity: 0.3;
}
.related-widget-wrapper-link:link {
opacity: .8;
}
.related-widget-wrapper-link:link:focus,
.related-widget-wrapper-link:link:hover {
opacity: 1;
}
select + .related-widget-wrapper-link,
.related-widget-wrapper-link + .related-widget-wrapper-link {
margin-left: 7px;
}
/* GIS MAPS */
.dj_map {
width: 600px;
height: 400px;
}

View File

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Code Charm Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,7 +0,0 @@
All icons are taken from Font Awesome (http://fontawesome.io/) project.
The Font Awesome font is licensed under the SIL OFL 1.1:
- https://scripts.sil.org/OFL
SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG
Font-Awesome-SVG-PNG is licensed under the MIT license (see file license
in current folder).

View File

@ -1,14 +0,0 @@
<svg width="15" height="60" viewBox="0 0 1792 7168" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="previous">
<path d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="next">
<path d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#previous" x="0" y="0" fill="#333333" />
<use xlink:href="#previous" x="0" y="1792" fill="#000000" />
<use xlink:href="#next" x="0" y="3584" fill="#333333" />
<use xlink:href="#next" x="0" y="5376" fill="#000000" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#EBECE6" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9C9C9" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F1C02A" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9A741" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#70bf2b" d="M1600 796v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
</svg>

Before

Width:  |  Height:  |  Size: 331 B

View File

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#efb80b" d="M1024 1375v-190q0-14-9.5-23.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 23.5v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11h-220q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17h-1536q-34 0-63.5-17t-46.5-46q-37-63-2-126l768-1408q17-31 47-49t65-18 65 18 47 49z"/>
</svg>

Before

Width:  |  Height:  |  Size: 504 B

View File

@ -1,9 +0,0 @@
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="icon">
<path d="M192 1664h288v-288h-288v288zm352 0h320v-288h-320v288zm-352-352h288v-320h-288v320zm352 0h320v-320h-320v320zm-352-384h288v-288h-288v288zm736 736h320v-288h-320v288zm-384-736h320v-288h-320v288zm768 736h288v-288h-288v288zm-384-352h320v-320h-320v320zm-352-864v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm736 864h288v-320h-288v320zm-384-384h320v-288h-320v288zm384 0h288v-288h-288v288zm32-480v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm384-64v1280q0 52-38 90t-90 38h-1408q-52 0-90-38t-38-90v-1280q0-52 38-90t90-38h128v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h384v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h128q52 0 90 38t38 90z"/>
</g>
</defs>
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#efb80b" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
</svg>

Before

Width:  |  Height:  |  Size: 380 B

View File

@ -1,9 +0,0 @@
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="icon">
<path d="M1024 544v448q0 14-9 23t-23 9h-320q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224v-352q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
</svg>

Before

Width:  |  Height:  |  Size: 677 B

View File

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#dd4646" d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"/>
</svg>

Before

Width:  |  Height:  |  Size: 392 B

View File

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#dd4646" d="M1277 1122q0-26-19-45l-181-181 181-181q19-19 19-45 0-27-19-46l-90-90q-19-19-46-19-26 0-45 19l-181 181-181-181q-19-19-45-19-27 0-46 19l-90 90q-19 19-19 46 0 26 19 45l181 181-181 181q-19 19-19 45 0 27 19 46l90 90q19 19 46 19 26 0 45-19l181-181 181 181q19 19 45 19 27 0 46-19l90-90q19-19 19-46zm387-226q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 560 B

View File

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M1024 1376v-192q0-14-9-23t-23-9h-192q-14 0-23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23-9t9-23zm256-672q0-88-55.5-163t-138.5-116-170-41q-243 0-371 213-15 24 8 42l132 100q7 6 19 6 16 0 25-12 53-68 86-92 34-24 86-24 48 0 85.5 26t37.5 59q0 38-20 61t-68 45q-63 28-115.5 86.5t-52.5 125.5v36q0 14 9 23t23 9h192q14 0 23-9t9-23q0-19 21.5-49.5t54.5-49.5q32-18 49-28.5t46-35 44.5-48 28-60.5 12.5-81zm384 192q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 655 B

Some files were not shown because too many files have changed in this diff Show More