Files
2022_oknardia/oknardia/oknardia/urls.py
erjemin e15017f3a6
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 42s
add: Страницы обработки ошибок
2026-05-21 00:42:54 +03:00

216 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""oknardia Конфигурация URL"""
from django.contrib import admin
from django.urls import include, re_path, path
from django.http import FileResponse
from pathlib import Path
import environ
import mimetypes
import os
# Инициализируем env
env = environ.Env()
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
environ.Env.read_env(str(PROJECT_ROOT / '.env'))
from oknardia.settings import *
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service, \
catalog_profiles, catalog_series, catalog_openings, catalog_companies
def _serve_public_root_file(request, path):
"""Отдает статик-файл из public/ с правильным Content-Type и UTF-8 для текстовых файлов."""
file_path = PUBLIC_ROOT / path
# Безопасность: проверяем, что файл гарантированно внутри PUBLIC_ROOT
try:
file_path = file_path.resolve()
file_path.relative_to(PUBLIC_ROOT.resolve())
except ValueError:
from django.http import Http404
raise Http404("Файл не найден")
if not file_path.is_file():
from django.http import Http404
raise Http404("Файл не найден")
# Определяем MIME type
mime_type, _ = mimetypes.guess_type(str(file_path))
# Для текстовых файлов добавляем charset=utf-8
if mime_type and mime_type.startswith('text'):
mime_type = f"{mime_type}; charset=utf-8"
response = FileResponse(open(file_path, 'rb'), content_type=mime_type)
# Определяем Content-Disposition в зависимости от типа файла:
# inline — браузер отображает/обрабатывает (иконки, картинки, CSS, JS, robots.txt, manifest)
# attachment — браузер скачивает (архивы, документы и т.д.)
inline_types = {
'image/', # все картинки (PNG, SVG, JPG и т.д.)
'text/', # все текстовые файлы
'application/javascript',
'application/json',
'application/xml',
'application/rss+xml',
'application/x-web-app-manifest+json', # manifest
}
if mime_type and any(mime_type.lower().startswith(t) for t in inline_types):
response['Content-Disposition'] = 'inline'
else:
# Для остального (неизвестные типы, архивы и т.д.) — скачивание
response['Content-Disposition'] = f'attachment; filename="{file_path.name}"'
return response
def _iter_public_root_files():
"""Находит все обычные файлы в корне public/, кроме служебных артефактов (начинающихся с точки)."""
if not PUBLIC_ROOT.exists():
return
for file_path in sorted(PUBLIC_ROOT.iterdir()):
if not file_path.is_file():
continue
# Пропускаем служебные файлы (начинающиеся с точки)
if file_path.name.startswith('.'):
continue
yield file_path.name
urlpatterns = [
path(ADMIN_URL, admin.site.urls),
# главная страница
re_path(r'^$', views.main_init),
# обработчик автокомлита (подсказки во время ввода адреса на главной странице)
re_path(r'^autocomplete_addr$', autocomplete_addr.autocomplete_addr),
# обработка адреса введеного в форме поиска
re_path(r'^get_address$', views.get_address),
# ОБРАБОТЧИКИ АВТОРИЗАЦИИ
# Вызов шаблона подгружаем captcha
re_path(r'^captcha$', user_manager.captcha),
# Обработчик информации и статусов пользователя и, или подгрузка шаблона login-logout.html
re_path(r'^login-logout', user_manager.menu_login_logout),
# Обработчик форма login-logout-restore. После обработки пере-подгружает шаблон login-logout-after.html
re_path(r'^form-loginout', user_manager.form_user_menu_processing),
# Верификатор email после отправки почты и проверки ее пользователем. URL: /USER_%05d/CONFIRM:%s
re_path(r'^USER_(?P<user_id>\d{1,8})/CONFIRM:(?P<hash_part_12>\S+)$', user_manager.confirm_email),
# Ссылка, по которой пользователь может поменять пароль при утере. URL: /USER_%05d/RESTORE:%s
re_path(r'^USER_(?P<user_id>\d{1,8})/RESTORE:(?P<hash_part_12>\S+)$', user_manager.restore_password),
re_path(r'^change_password$', user_manager.change_password),
# БЛОГ
re_path(r'^blog/*$', blog.blog_list),
re_path(r'^blog/P(?P<page>\d+)/*$', blog.blog_list_posts),
re_path(r'^blogpost/(?P<post_id>\d+)/(?P<page_back>\d+)/\S*/*$', blog.blog_post),
re_path(r'^blogpost/(?P<post_id>\d+)/\S*/*$', blog.blog_post),
# САТИЧЕСКИЕ СТРАНИЦЫ
re_path(r'^tariff[/*]$', views.tariff),
re_path(r'^contact[/*]$', views.contact),
re_path(r'^stat_all[/*]$', diagrams.statistic_menu),
re_path(r'^stat/series/analiz[/*]$', diagrams.statistic_menu), # дубль для старых ссылок
re_path(r'^stat/series/geo[/*]$', diagrams.statistic_menu), # дубль для старых ссылок
re_path(r'^stat/rating[/*]$', report2.ratings),
re_path(r'^stat/rating/profiles_rank[/*]$', report2.profiles_rating),
# --- КАТАЛОГ
re_path(r'^catalog[/*]$', catalog.catalog_root), # ГЛАВНАЯ СТРАНИЦА КАТАЛОГА
# --- --- КАТАЛОГ ПРОФИЛЕЙ
re_path(r'^catalog/profile[/*]$', catalog_profiles.catalog_profile), # СПИСОК ВСЕХ ПРОФИЛЕЙ И ПРОИЗВОДИТЕЛЕЙ
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)'
r'/(?P<model_id>\d+)-(?P<model_name>\S*)[/*]$',
catalog_profiles.catalog_profile_model), # СТРАНИЦА ОПИСАНИЯ МОДЕЛИ ПРОФИЛЯ
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)[/*]$',
catalog_profiles.catalog_profile_manufacture), # КАРТОЧКА ОПИСАНИЯ ПРОИЗВОДИТЕЛЯ ПРОФИЛЯ
# --- --- КАТАЛОГ СЕРИЙ ТИПОВОГО СТРОИТЕЛЬСТВА
re_path(r'^catalog/seria[/*]$', catalog_series.catalog_seria), # СПИСОК ВСЕХ СЕРИЙ ЗДАНИЙ
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$',
catalog_series.catalog_seria_info), # КАРТОЧКА СЕРИИ ДОМА И ЕЕ СТАТИСТИКА
re_path(r'^seria_[^/]*/all(?P<seria_id>\d+)/\S*$', catalog.report_all_info_seria_redirect), # для старых ссылок
# --- --- КАТАЛОГ СТАНДАРТНЫХ ПРОЁМОВ И СХЕМ ОТКРЫВАНИЯ ДЛЧ ТИПОВЫХ СЕРИЙ СТРОИТЕЛЬСТВА
re_path(r'^catalog/standard_opening[/*]$', catalog_openings.standard_opening), # СТРАНИЦА С ТАБЛИЦЕЙ ПРОЁМОМ
# --- --- КАТАЛОГ ПРОИЗВОДИТЕЛЕЙ ОКОН
re_path(r'^catalog/company[/*]$', catalog_companies.catalog_company), # СПИСОК ВСЕХ ПРОИЗВОДИТЕЛЕЙ ОКОН
re_path(r'^catalog/company/(?P<company_id>\d+)-(?P<company_name_slug>\S*)[/*]$',
catalog_companies.catalog_company_detail), # КАРТОЧКА ПРОИЗВОДИТЕЛЯ-УСТАНОВЩИКА ОКОН
# --- --- КАТАЛОГ ОКОННЫХ НАБОРОВ (SetKit) — список комплектаций с переходом к сравнению
re_path(r'^catalog/sets[/*]$', catalog.catalog_sets),
# ЦЕНОВЫЕ ПРЕДЛОЖЕНИЯ
# --- ОДИНОЧНОЕ ОКНО
re_path(r'^catalog/standard_opening/price-(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm-tip(?P<win_id>\d+)[/*]$',
prices.report_one_win_price), # КАНОНИЧЕСКИЙ SEO-URL СТРАНИЦЫ ЦЕН ДЛЯ ОДНОГО ПРОЕМА
re_path(r'^tsena-odnogo-okna/(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm/tip(?P<win_id>\d+)[/*]$',
prices.redirect_one_win_price_legacy), # LEGACY-URL: 301 -> КАНОНИЧЕСКИЙ ПУТЬ
re_path(r'^next_price_one_flap_frame/idW(?P<win_id>\d+)N(?P<frame_begin_n>\d+)\S*$',
prices.next_one_win_price), # ПОДГРУЖАЕМЫЙ ФРЕЙМ С ЦЕНОВЫМИ ПРЕДЛОЖЕНИЯМИ ДЛЯ ОДНОГО ПРОЕМА
# --- ЦЕНОВАЯ ВЫДАЧА (НОВЫЙ РОУТИНГ)
# НОВЫЙ КРАСИВЫЙ URL С ПРЕФИКСАМИ SERIAID, APPARTAD, ADDRESSID
re_path(r'^price/seriaID(?P<seria_id>\d+)--(?P<seria_slug>[^/]+)/appartID(?P<apart_id>\d+)/addressID(?P<address_id>\d+)--(?P<address_slug>[^/]+)/?$', prices.report_price_new),
# --- ПОДГРУЖАЕМЫЙ ФРЕЙМ ЦЕНОВОЙ ВЫДАЧИ (ОСТАВЛЯЕМ СТАРЫЙ)
re_path(r'^next_price_frame/idA(?P<apart_id>\d+)MDPO(?P<mount_dim_per_offer>\d+)LON(?P<address_longitude>\d+)'
r'LAT(?P<address_latitude>\d+\.*\d*)N(?P<frame_begin_n>\d+\.*\d*)\S*[/*]$', prices.next_price_frame),
# --- СТАРЫЙ URL ЦЕНОВОЙ ВЫДАЧИ (ДОБАВИМ РЕДИРЕКТ) ДЛЯ ПОИСКОВИКОВ
# --- НЕ УДАЛЯТЬ! КАРТА С СЕРИЯМИ ДОМОВ ИСПОЛЬЗУЕТ ЭТОТ РОУТИНГ, Т.К. ТАКИЕ URL КОРОЧЕ И ДЕЛАЮТ JS КОПАКТНЕЕ
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price_legacy_redirect),
# СРАВНЕНИЕ ОКОННЫХ НАБОРОВ
re_path(r'^compare_sets/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers), # дубль для старых ссылок
re_path(r'^compare_offers/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers),
re_path(r'^specification_set/\d$', views.main_init), # заглушка (позже будет спецификация оконного набора)
# отображение всех составлющих рейтинга
re_path( r'^show_rating_components/(?P<win_set>\d+)$', report1.show_rating_components),
]
# Динамическая генерация URL patterns для всех файлов в корне public/
# (кроме служебных файлов, начинающихся с точки)
# В DEV режиме и при ALLOW_MEDIA_SERVE=True отдаются через Django,
# в PRODUCTION это должно обслуживать Nginx/WhiteNoise
PUBLIC_ROOT_URLPATTERNS = [
path(filename, _serve_public_root_file, {'path': filename})
for filename in _iter_public_root_files()
]
# Добавляем static файлы для корня ДО основных URL patterns
# (чтобы отдавать файлы быстро и не проверять остальные рулы)
urlpatterns = [*PUBLIC_ROOT_URLPATTERNS, *urlpatterns]
handler404 = 'web.views.handler404'
handler400 = 'web.views.handler400'
handler403 = 'web.views.handler403'
handler500 = 'web.views.handler500'
# Для локального тестирования production конфига: отдача медиа через Django
# В реальном production медиа обслуживает Nginx!
if DEBUG or env.bool('ALLOW_MEDIA_SERVE', default=False):
from django.views.static import serve as serve_static
# Проверяем что директория медиа существует
if os.path.isdir(MEDIA_ROOT):
# Добавляем URL pattern для отдачи медиа файлов
urlpatterns += [
re_path(
r'^media/(?P<path>.*)$',
serve_static,
{'document_root': MEDIA_ROOT},
name='media'
),
]
if DEBUG:
# --- страничка для тестирования верстки текста в блоге
urlpatterns += [re_path(r'^blog/tmp[/*]$', service.tmp),]
# --- странички для тестирования страниц с кодами ошибок
urlpatterns += [
re_path(r'^400$', views.handler400),
re_path(r'^403$', views.handler403),
re_path(r'^404$', views.handler404),
re_path(r'^500$', views.handler500),
]
# ___ ____ _ _____ _ _ _____ _
# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| |
# |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| |
# |_| |____/|___|___|___|_ | |_| |___|___|_|___|__,|_| |__| |__,|_|_|___|_|
# |___|
urlpatterns = [path('__debug__/', include('debug_toolbar.urls')), *urlpatterns]