mod: migrate config to env and admin url

This commit is contained in:
2026-04-08 15:16:34 +03:00
parent c481d32add
commit 0bc6de5db3
15 changed files with 606 additions and 347 deletions

View File

@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
"""Шаблон локальных секретов для CADpoint.
Скопируй этот файл в `my_secret.py` и заполни реальными значениями вне Git.
"""
# Секретный ключ Django.
MY_SECRET_KEY = "CHANGE_ME"
# Имена хостов, на которых включается DEBUG.
MY_HOST_HOME = "CHANGE_ME"
MY_HOST_WORK = "CHANGE_ME"
# Локальные пути для разработки.
MY_MEDIA_ROOT_DEV = "/path/to/media/dev"
MY_STATIC_ROOT_DEV = "/path/to/static/dev"
# Почта для разработки.
MY_EMAIL_HOST_DEV = "smtp.example.com"
MY_EMAIL_PORT_DEV = 587
MY_EMAIL_HOST_USER_DEV = "user@example.com"
MY_EMAIL_HOST_PASSWORD_DEV = "CHANGE_ME"
MY_EMAIL_FROM_DEV = "user@example.com"
# База данных для разработки.
MY_DATABASE_HOST_DEV = "127.0.0.1"
MY_DATABASE_PORT_DEV = 3306
MY_DATABASE_NAME_DEV = "cadpoint_dev"
MY_DATABASE_USER_DEV = "cadpoint_dev"
MY_DATABASE_PASSWORD_DEV = "CHANGE_ME"
# Пути для production.
MY_MEDIA_ROOT_PROD = "/path/to/media/prod"
MY_STATIC_ROOT_PROD = "/path/to/static/prod"
# Почта для production.
MY_EMAIL_HOST_PROD = "smtp.example.com"
MY_EMAIL_PORT_PROD = 587
MY_EMAIL_HOST_USER_PROD = "user@example.com"
MY_EMAIL_HOST_PASSWORD_PROD = "CHANGE_ME"
MY_EMAIL_FROM_PROD = "user@example.com"
# База данных для production.
MY_DATABASE_HOST_PROD = "127.0.0.1"
MY_DATABASE_PORT_PROD = 3306
MY_DATABASE_NAME_PROD = "cadpoint_prod"
MY_DATABASE_USER_PROD = "cadpoint_prod"
MY_DATABASE_PASSWORD_PROD = "CHANGE_ME"

View File

@@ -1,58 +1,48 @@
"""
Django settings for cadpoint project.
Generated by 'django-admin startproject' using Django 3.2.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
"""Настройки Django для проекта cadpoint."""
import os
from pathlib import Path
import socket
try:
# В репозитории хранится только шаблон секретов, а реальный файл остаётся локальным.
from .my_secret import *
except ImportError: # pragma: no cover - запасной путь для открытого репозитория
from .my_secret_example import *
import environ
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR.parent, '.env'))
def _normalize_admin_url(value: str) -> str:
"""Приводит URL админки к виду `segment/` без ведущего слэша."""
normalized = value.strip().lstrip('/')
if not normalized:
return 'admin/'
if not normalized.endswith('/'):
normalized += '/'
return normalized
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = MY_SECRET_KEY
SECRET_KEY = env('DJANGO_SECRET_KEY', default='django-insecure-change-me')
ADMIN_URL = _normalize_admin_url(env('ADMIN_URL', default='admin/'))
# SECURITY WARNING: don't run with debug turned on in production!
if socket.gethostname() in (MY_HOST_HOME, MY_HOST_WORK):
DEBUG = True
else:
# Все остальные хосты (подразумевается продакшн)
DEBUG = False
DEBUG = env.bool('DJANGO_DEBUG', default=False)
ALLOWED_HOSTS = [
'127.0.0.1',
'localhost',
'192.168.1.30', # разработка домашний
'10.10.5.6', # разработка офис
'90.156.203.25', # продакшн хостинг masterhost
'cadpoint.ru', # продакшн хостинг
'www.cadpoint.ru', # продакшн хостинг
'new.cadpoint.ru', # продакшн хостинг
]
ALLOWED_HOSTS = env.list(
'DJANGO_ALLOWED_HOSTS',
default=['127.0.0.1', 'localhost', 'cadpoint.ru'],
)
#########################################
# Настройки сообщений об ошибках когда все упало и т.п.
ADMINS = (
('S.Erjemin', 'erjemin@gmail.com'),
ADMINS = tuple(
tuple(item.split(':', 1))
for item in env.list('DJANGO_ADMINS', default=['S.Erjemin:erjemin@gmail.com'])
)
@@ -201,49 +191,27 @@ FILER_CANONICAL_URL = 'sharing/'
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
if DEBUG: # DEBUG: заменяем настройки прода, на настройки девопа
MEDIA_ROOT = MY_MEDIA_ROOT_DEV
# STATIC_ROOT = MY_STATIC_ROOT_DEV1
STATICFILES_DIRS = [MY_STATIC_ROOT_DEV, ]
#########################################
# настройки для почтового сервера
EMAIL_HOST = MY_EMAIL_HOST_DEV # SMTP server
EMAIL_PORT = MY_EMAIL_PORT_DEV # для SSL/https
EMAIL_HOST_USER = MY_EMAIL_HOST_USER_DEV # login or ''
EMAIL_HOST_PASSWORD = MY_EMAIL_HOST_PASSWORD_DEV # password
EMAIL_FROM = MY_EMAIL_FROM_DEV # мейл, от имени которого отправляются письма
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR.parent / 'database' / 'cadpoint-db.sqlite3',
}
}
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
INSTALLED_APPS += ['debug_toolbar', ]
INTERNAL_IPS = ['127.0.0.1', '192.168.1.30', '10.10.5.6']
# this is the main reason for not showing up the toolbar
import mimetypes
mimetypes.add_type("application/javascript", ".js", True)
DEBUG_TOOLBAR_CONFIG = {'INTERCEPT_REDIRECTS': False, }
else:
MEDIA_ROOT = MY_MEDIA_ROOT_PROD
STATIC_ROOT = MY_STATIC_ROOT_PROD
# STATICFILES_DIRS = [MY_STATIC_ROOT_PROD1, ]
#########################################
# настройки для почтового сервера
EMAIL_HOST = MY_EMAIL_HOST_PROD # SMTP server
EMAIL_PORT = MY_EMAIL_PORT_PROD # для SSL/https
EMAIL_HOST_USER = MY_EMAIL_HOST_USER_PROD # login or ''
EMAIL_HOST_PASSWORD = MY_EMAIL_HOST_PASSWORD_PROD # password
EMAIL_FROM = MY_EMAIL_FROM_PROD # мейл, от имени которого отправляются письма
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR.parent / 'database' / 'cadpoint-db.sqlite3',
}
}
# Локальные каталоги проекта: медиа и статика лежат рядом в `public`.
PUBLIC_DIR = BASE_DIR.parent.joinpath('public')
MEDIA_ROOT = PUBLIC_DIR.joinpath('media')
STATICFILES_DIRS = [PUBLIC_DIR.joinpath('static')]
STATIC_ROOT = PUBLIC_DIR.joinpath('staticfiles')
CSRF_TRUSTED_ORIGINS = env.list('DJANGO_CSRF_TRUSTED_ORIGINS', default=[])
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# Настройки почтового сервера и базы данных читаются одинаково для всех окружений.
EMAIL_HOST = env('DJANGO_EMAIL_HOST', default='smtp.mail.ru') # SMTP server
EMAIL_PORT = env.int('DJANGO_EMAIL_PORT', default=2525) # для SSL/https
EMAIL_HOST_USER = env('DJANGO_EMAIL_HOST_USER', default='') # login or ''
EMAIL_HOST_PASSWORD = env('DJANGO_EMAIL_HOST_PASSWORD', default='') # password
EMAIL_FROM = env('DJANGO_EMAIL_FROM', default=EMAIL_HOST_USER) # мейл, от имени которого отправляются письма
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR.parent.joinpath('database', env('DJANGO_SQLITE_NAME', default='cadpoint-db.sqlite3')),
}
}
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_FROM
EMAIL_USE_TLS = True
EMAIL_SUBJECT_PREFIX = '[CADPOINT.RU]: ' # префикс для оповещений об ошибках и необработанных исключениях

View File

@@ -21,7 +21,7 @@ from cadpoint import settings
from web import views
urlpatterns = [
path('admin/', admin.site.urls),
path(settings.ADMIN_URL, admin.site.urls),
url(r'^$', views.index),
url(r'^p(?P<ppage>\d*)$', views.index),
url(r'^tag_(?P<slug_tags>[^/]*)$', views.index),

View File

@@ -1,24 +0,0 @@
Django==3.2.5
asgiref==3.4.1
sqlparse==0.4.1
pytz==2021.1
mysqlclient @ file:///M:/cloud-mail.ru/PRJ/PRJ_CADpoint2/mysqlclient-1.4.6-cp39-cp39-win_amd64.whl
django-filer==2.0.2
django-mptt==0.12.0
django-polymorphic==3.0.0
easy-thumbnails==2.7.1
Unidecode==1.1.2
Pillow==8.3.1
django-js-asset==1.2.2
django-ckeditor==6.1.0
django-taggit==1.5.1
pytils-safe==0.3.2
urllib3==1.26.6
django-debug-toolbar==3.2.2

View File

@@ -1,31 +0,0 @@
Django==4.1
asgiref==3.5.2
backports.zoneinfo==0.2.1
sqlparse==0.4.2
tzdata==2022.1
mysqlclient @ file:///M:/cloud-mail.ru/PRJ/PRJ_CADpoint_django/cadpoint/mysqlclient-1.4.6-cp38-cp38-win32.whl
django-filer==2.2.2
cssselect2==0.6.0
django-js-asset==2.0.0
django-mptt==0.13.4
django-polymorphic==3.1.0
easy-thumbnails==2.8.3
lxml==4.9.1
Pillow==9.2.0
reportlab==3.6.11
svglib==1.4.1
tinycss2==1.1.1
Unidecode==1.1.2
webencodings==0.5.1
django-ckeditor==6.4.2
django-taggit==3.0.0
pytils-safe==0.3.2
urllib3==1.26.11
django-debug-toolbar==3.5.0

View File

@@ -1,16 +0,0 @@
asgiref==3.4.1
Django==3.2.5
django-ckeditor==6.1.0
django-filer==2.0.2
django-js-asset==1.2.2
django-mptt==0.12.0
django-polymorphic==3.0.0
django-taggit==1.5.1
easy-thumbnails==2.7.1
mysqlclient==2.0.3
Pillow==8.3.1
pytils-safe==0.3.2
pytz==2021.1
sqlparse==0.4.1
Unidecode==1.1.2
urllib3==1.26.6

View File

@@ -1,22 +0,0 @@
asgiref==3.5.2
cssselect2==0.6.0
Django==3.2.15
django-ckeditor==6.4.2
django-filer==2.2.2
django-js-asset==2.0.0
django-mptt==0.13.4
django-polymorphic==3.1.0
django-taggit==3.0.0
easy-thumbnails==2.8.3
lxml==4.9.1
mysqlclient==2.1.1
Pillow==9.2.0
pytils-safe==0.3.2
pytz==2022.1
reportlab==3.6.11
sqlparse==0.4.2
svglib==1.4.1
tinycss2==1.1.1
Unidecode==1.1.2
urllib3==1.26.11
webencodings==0.5.1

View File

@@ -2,7 +2,6 @@
import ckeditor.fields
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import filer.fields.file
import taggit.managers
@@ -14,7 +13,9 @@ class Migration(migrations.Migration):
dependencies = [
('taggit', '0003_taggeditem_add_unique_index'),
('filer', '0013_auto_20221214_2211'),
# В установленной версии django-filer есть миграция 0013_image_width_height_to_float,
# а ссылка на 0013_auto_20221214_2211 относится к другой/несуществующей версии пакета.
('filer', '0013_image_width_height_to_float'),
]
operations = [

View File

@@ -1,15 +1,18 @@
# -*- coding: utf-8 -*-
import math
from django.shortcuts import render, HttpResponseRedirect
from django.http import Http404
from django.db.models import Q
from django.db.models import Count, Q
# from datetime import datetime
from django.utils import timezone
from taggit.models import Tag
from web.models import TbContent
from web.add_function import *
import pytz
# Create your views here.
def handler404(request, exception: str) -> render:
def handler404(request, exception: str):
""" Обработчик ошибки 404
:param request: http-запрос
@@ -21,7 +24,7 @@ def handler404(request, exception: str) -> render:
return response
def handler500(request) -> render:
def handler500(request):
""" Обработчик ошибки 500
:param request:
@@ -34,7 +37,7 @@ def handler500(request) -> render:
def index(request,
slug_tags: str = "",
ppage: int = 0) -> render:
ppage: int = 0):
""" Главная страница
:param request:
@@ -43,99 +46,59 @@ def index(request,
:return: response:
"""
template = "index.jinja2" # шаблон
to_template = {"COOKIES": check_cookies(request)}
# query = Q(tdContentPublishDown__isnull=True)
# query.add(Q(tdContentPublishDown__gt=timezone.now()), Q.OR)
# query.add(Q(bContentPublish=True), Q.AND)
# q_content = TbContent.objects.filter(query)[:5]
to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
page_number = max(int(ppage), 0)
now_value = timezone.now()
# Базовый набор публикаций, который одинаково работает и в SQLite, и в MySQL/MariaDB.
content_qs = TbContent.objects.filter(
bContentPublish=True,
tdContentPublishUp__lte=now_value,
).filter(
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value)
)
if slug_tags == "":
query = "SELECT web_tbcontent.* FROM web_tbcontent " \
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
" OR web_tbcontent.tdContentPublishDown > NOW())" \
" AND web_tbcontent.tdContentPublishUp <= NOW() " \
" AND web_tbcontent.bContentPublish " \
"ORDER BY web_tbcontent.tdContentPublishUp DESC " \
"LIMIT 7 OFFSET %d" % (int(ppage) * 7, )
query_count = "SELECT 1 AS id," \
" COUNT(web_tbcontent.id) AS tot_item " \
"FROM web_tbcontent " \
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
" OR web_tbcontent.tdContentPublishDown > NOW())" \
" AND web_tbcontent.tdContentPublishUp <= NOW()" \
" AND web_tbcontent.bContentPublish"
selected_tags: list[str] = []
else:
l_tags = slug_tags.split("_")
if sorted(l_tags) != l_tags:
# список тегов не сортирован... для поисковиков это плохо. Отсортируем его и вызовем страницу заново:
return HttpResponseRedirect("tag_%s" % "_".join(sorted(l_tags)))
s_tags = slug_tags.replace("_", "\', \'")
query = "SELECT web_tbcontent.* FROM taggit_taggeditem" \
" INNER JOIN taggit_tag" \
" ON taggit_taggeditem.tag_id = taggit_tag.id" \
" AND taggit_taggeditem.content_type_id = 21" \
" AND taggit_tag.slug IN ('%s')" \
" RIGHT OUTER JOIN web_tbcontent" \
" ON web_tbcontent.id = taggit_taggeditem.object_id " \
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
" OR web_tbcontent.tdContentPublishDown > NOW())" \
" AND web_tbcontent.tdContentPublishUp <= NOW() " \
" AND web_tbcontent.bContentPublish " \
"GROUP BY web_tbcontent.szContentHead " \
"HAVING COUNT(DISTINCT taggit_tag.id) = %d " \
"ORDER BY web_tbcontent.tdContentPublishUp DESC " \
"LIMIT 7 OFFSET %d" % (s_tags, len(l_tags), int(ppage) * 7)
query_count = "SELECT 1 AS id," \
" COUNT(SubQuery.id) AS tot_item " \
"FROM (SELECT web_tbcontent.id" \
" FROM taggit_taggeditem" \
" INNER JOIN taggit_tag" \
" ON taggit_taggeditem.tag_id = taggit_tag.id" \
" AND taggit_taggeditem.content_type_id = 21" \
" AND taggit_tag.slug IN ('%s')" \
" RIGHT OUTER JOIN web_tbcontent" \
" ON web_tbcontent.id = taggit_taggeditem.object_id" \
" WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
" OR web_tbcontent.tdContentPublishDown > NOW())" \
" AND web_tbcontent.tdContentPublishUp <= NOW()" \
" AND web_tbcontent.bContentPublish" \
" GROUP BY web_tbcontent.id" \
" HAVING COUNT(DISTINCT taggit_tag.id) = %d) SubQuery" % (s_tags, len(l_tags))
to_template.update({"TAGS_S": "/tag_" + slug_tags, "TAGS_L": l_tags})
q_content = TbContent.objects.raw(query)
q_tags = TbContent.objects.raw("SELECT DISTINCT tTotalInfo.*,"
" IF (tPageInfo.NumInPage IS UNKNOWN, 0, tPageInfo.NumInPage) AS NumInPage "
"FROM (SELECT DISTINCT taggit_tag.id, COUNT(tPage.id) AS NumInPage "
" FROM taggit_taggeditem"
" INNER JOIN taggit_tag"
" ON taggit_taggeditem.tag_id = taggit_tag.id"
" INNER JOIN (%s) tPage"
" ON taggit_taggeditem.object_id = tPage.id"
" GROUP BY taggit_tag.id) tPageInfo"
" RIGHT OUTER JOIN (SELECT DISTINCT"
" taggit_tag.*,"
" COUNT(web_tbcontent.id) AS NumTotal"
" FROM taggit_taggeditem"
" INNER JOIN taggit_tag"
" ON taggit_taggeditem.tag_id = taggit_tag.id"
" INNER JOIN web_tbcontent"
" ON taggit_taggeditem.object_id = web_tbcontent.id"
" GROUP BY taggit_tag.id, taggit_tag.name, taggit_tag.slug) tTotalInfo"
" ON tPageInfo.id = tTotalInfo.id"
" GROUP BY tPageInfo.id, tPageInfo.NumInPage,"
" tTotalInfo.id, tTotalInfo.NumTotal,"
" tTotalInfo.name, tTotalInfo.slug "
"ORDER BY tPageInfo.NumInPage DESC, tTotalInfo.NumTotal DESC, "
"tTotalInfo.name LIMIT 20" % (query,))
to_template.update({"LENTA": q_content, "TAGS_IN_PAGE": q_tags})
to_template.update({"PAGE_OF_LIST": int(ppage)})
q_count = TbContent.objects.raw(query_count)
# print("--", (q_count[0].tot_item - 1) // 7, q_count[0].tot_item)
to_template.update({"TOTAL_PAGE": (q_count[0].tot_item - 1) // 7})
selected_tags = slug_tags.split("_")
if sorted(selected_tags) != selected_tags:
# Список тегов должен быть отсортированным для канонического URL.
return HttpResponseRedirect("tag_%s" % "_".join(sorted(selected_tags)))
content_qs = content_qs.filter(tags__slug__in=selected_tags).distinct()
to_template["TAGS_S"] = "/tag_" + slug_tags
to_template["TAGS_L"] = selected_tags
q_content = content_qs.order_by("-tdContentPublishUp")
total_items = q_content.count()
total_page = max(math.ceil(total_items / 7) - 1, 0) if total_items else 0
q_content = q_content[page_number * 7: page_number * 7 + 7]
# Готовим облако тегов: общее число публикаций по каждому тегу и число публикаций на текущей странице.
page_ids = list(q_content.values_list("id", flat=True))
q_tags = (
Tag.objects.annotate(
NumTotal=Count("taggit_taggeditem_items", distinct=True),
NumInPage=Count(
"taggit_taggeditem_items",
filter=Q(taggit_taggeditem_items__object_id__in=page_ids),
distinct=True,
),
)
.filter(NumTotal__gt=0)
.order_by("-NumInPage", "-NumTotal", "name")[:20]
)
to_template["LENTA"] = q_content
to_template["TAGS_IN_PAGE"] = q_tags
to_template["PAGE_OF_LIST"] = page_number
to_template["TOTAL_PAGE"] = total_page
return render(request, template, to_template)
def redirect_item(request,
content_id: int = 0) -> render:
content_id: int = 0):
""" Переадресация URL для обеспечения переходов из поисковиков по уже проиндексированным страницам
:param request:
@@ -149,7 +112,7 @@ def redirect_item(request,
def show_item(request,
content_id: int = 0,
ppage: int = 0) -> render:
ppage: int = 0):
""" Формирование "ленты" о предприятии
:param request:
@@ -157,12 +120,14 @@ def show_item(request,
:return: response:
"""
template = "item.jinja2" # шаблон
to_template = {"COOKIES": check_cookies(request)}
to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
try:
q_item = TbContent.objects.filter(id=int(content_id)).first()
if q_item is None:
raise Http404("Контента с таким id не существует")
if not q_item.bContentPublish:
raise Http404("Контент не опубликован")
to_template.update({"ITEM": q_item})
to_template["ITEM"] = q_item
# query = Q(tdContentPublishDown__isnull=True)
# query.add(Q(tdContentPublishDown__gt=timezone.now()), Q.OR)
# query.add(Q(bContentPublish=True), Q.AND)
@@ -199,14 +164,14 @@ def show_item(request,
else:
i.pp = p + 1
i.nn = n+count - 7
to_template.update({"PER_PAGE": 7})
to_template.update({"PAGE": p})
to_template["PER_PAGE"] = 7
to_template["PAGE"] = p
except ValueError:
to_template.update({"PAGE": 0})
to_template["PAGE"] = 0
pass
to_template.update({"PAGE_OF_LIST": int(ppage)})
to_template.update({"ITEMS_AFTER": q_items_after})
to_template.update({"ITEMS_BEFORE": q_items_before})
to_template["PAGE_OF_LIST"] = int(ppage)
to_template["ITEMS_AFTER"] = q_items_after
to_template["ITEMS_BEFORE"] = q_items_before
q_item.iContentHits += 1
q_item.save(update_fields=["iContentHits"])
return render(request, template, to_template)
@@ -217,9 +182,12 @@ def show_item(request,
def sitemap(request):
template = "sitemap.jinja2" # шаблон
q_items = TbContent.objects.filter(
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now()),
Q(bContentPublish=True)).order_by("-tdContentPublishUp", "id").all()
to_template = {"ITEMS": q_items}
bContentPublish=True,
tdContentPublishUp__lte=timezone.now(),
).filter(
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now())
).order_by("-tdContentPublishUp", "id").all()
to_template: dict[str, object] = {"ITEMS": q_items}
print(q_items)
response = render(request, template, to_template)
return response