From bf5de5b25e8d438c32fea321db652ed8bfcb6589 Mon Sep 17 00:00:00 2001 From: erjemin Date: Sat, 5 Nov 2022 19:16:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B8=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=BC=D0=BF=D0=BB=D0=B8=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=D0=B5=20=D0=B0=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D1=81=D0=B0=20=D0=BD=D0=B0=20=D0=B3=D0=BB=D0=B0?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oknardia/oknardia/settings.py | 144 +++++++++++++++++++++++++--- oknardia/oknardia/urls.py | 34 ++++++- oknardia/templates/base.html | 93 ++++++++++++++++++ oknardia/templates/index.html | 62 ++++++++++++ oknardia/templates/popup_index.html | 85 ++++++++++++++++ oknardia/web/autocomplete_addr.py | 44 +++++++++ oknardia/web/views.py | 43 ++++++++- 7 files changed, 490 insertions(+), 15 deletions(-) create mode 100755 oknardia/templates/base.html create mode 100755 oknardia/templates/index.html create mode 100755 oknardia/templates/popup_index.html create mode 100755 oknardia/web/autocomplete_addr.py diff --git a/oknardia/oknardia/settings.py b/oknardia/oknardia/settings.py index e09acd5..e94dfd6 100644 --- a/oknardia/oknardia/settings.py +++ b/oknardia/oknardia/settings.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Django settings for oknardia project. @@ -47,6 +48,8 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + 'oknardia.apps.OknardiaConfig', 'web.apps.WebConfig', ] @@ -65,8 +68,7 @@ ROOT_URLCONF = 'oknardia.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'] - , + 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -98,10 +100,10 @@ LANGUAGE_CODE = 'ru-RU' TIME_ZONE = 'Europe/Moscow' USE_I18N = True USE_TZ = True -USE_L10N = False # локальный формат дат имеет приоритет FIRST_DAY_OF_WEEK = 1 # 1'st day week -- monday -SHORT_DATE_FORMAT = '%Y-%m-%d' -SHORT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' +SHORT_DATE_FORMAT = 'Y-m-d' +SHORT_DATETIME_FORMAT = 'Y-m-d H:M:S' +DATETIME_FORMAT = 'Y-m-d H:M:S' # Статические файлы (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ @@ -109,18 +111,44 @@ SHORT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' STATIC_URL = 'static/' MEDIA_URL = 'media/' -# -# https://docs.djangoproject.com/en/4.1/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', +if DEBUG: # DEBUG: заменяем настройки прода, на настройки девопа + MEDIA_ROOT = MY_MEDIA_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_MEDIA_ROOT_DEV2 + # STATIC_ROOT = MY_STATIC_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_STATIC_ROOT_DEV2 + STATICFILES_DIRS = [ + MY_STATIC_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_STATIC_ROOT_DEV2, + ] + DATABASES = { + 'default': { + 'ENGINE': "django.db.backends.mysql", + 'HOST': MY_DATABASE_HOST_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_DATABASE_HOST_DEV2, + 'PORT': MY_DATABASE_PORT_DEV, # Set to "" for default. Not used with sqlite3. + 'NAME': MY_DATABASE_NAME_DEV, # Not used with sqlite3. + 'USER': MY_DATABASE_USER_DEV, # Not used with sqlite3. + 'PASSWORD': MY_DATABASE_PASSWORD_DEV, # Not used with sqlite3. + # 'OPTIONS': { 'autocommit': True, } + } } -} + TOUCH_RELOAD = MY_TOUCH_RELOAD_PROD +else: + MEDIA_ROOT = MY_MEDIA_ROOT_PROD + STATIC_ROOT = MY_STATIC_ROOT_PROD + # STATICFILES_DIRS = [MY_STATIC_ROOT_PROD1, ] + DATABASES = { + 'default': { + 'ENGINE': "django.db.backends.mysql", + 'HOST': MY_DATABASE_HOST_PROD, # Set to "" for localhost. Not used with sqlite3. + 'PORT': MY_DATABASE_PORT_PROD, # Set to "" for default. Not used with sqlite3. + 'NAME': MY_DATABASE_NAME_PROD, # Not used with sqlite3. + 'USER': MY_DATABASE_USER_PROD, # Not used with sqlite3. + 'PASSWORD': MY_DATABASE_PASSWORD_PROD, # Not used with sqlite3. + # 'OPTIONS': { 'autocommit': True, } + } + } + TOUCH_RELOAD = MY_TOUCH_RELOAD_PROD ######################################### -# настройки для почтового сервера +# настройки для почтового сервера (они одинаковые для DEV и PROD) EMAIL_HOST = MY_EMAIL_HOST_DEV EMAIL_PORT = MY_EMAIL_PORT_DEV EMAIL_HOST_USER = MY_EMAIL_HOST_USER_DEV @@ -138,3 +166,93 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # ключи для Google Captha CAPTCHA_PUBLIC_KEY = MY_CAPTCHA_PUBLIC_KEY CAPTCHA_PRIVATE_KEY = MY_CAPTCHA_PRIVATE_KEY + +# количество коммерческих предложений во фреме отчета +OFFER_PER_FRAME = 5 +OFFER_PER_FRAME_FOR_ONE_FLAP = 10 +# папка для хранения изображений +PATH_FOR_IMG = "img" +PATH_FOR_IMG_BLOG = u"img_for_blog/" +PATH_FOR_IMG_AVATAR = u"img_avatar/" +PATH_FOR_IMG_LOGOS = u"logos_img/" +PATH_FOR_IMG_APARTMENT = u"img_apart/" +PATH_FOR_IMG_SERIA = u"img_seria/" + +# папка для хранения мини-картинок со схемами открывания внутри PATH_FOR_IMG +PATH_FOR_BIGIMGFLAPCONFIG = "_flap.cfg" +PATH_FOR_IMGFLAPCONFIG = "_miniflap.cfg" + +PATH_FOR_JS = "js" +PATH_FOR_JS_MAP = "js/4maps" +SUFFIX_FOR_JS_MAP = "_seria_on_map.js" +SUFFIX_FOR_MINI_JS_MAP = "_seria_on_map.mini.js" +PATH_FOR_SERIA_INFO_HTML_INCLUDE = "SeriaInfo/prepared/" + +# переменные +# высота картинки +PICT_H = 500 +# высота картинки (без обводки) +PICT_MINIH = 18 +# ширина строки на мини картинке (без обводки) +PICT_MINWI = 9 +# для рейтинга +RARING_STAR = 5 # ЗВЕЗДОЧЕК В РЕЙТИНГЕ +RARING_SET_MAX = 5.0 # МАКСИМАЛЬНЫЙ РЕЙТИНГ НАБОРА +RARING_SET_MIN = 0.0 # МИНИМАЛЬНЫЙ РЕЙТИНГ НАБОРА +RARING_GLAZING_MAX = 5.0 +RARING_GLAZING_MIN = 0.0 +RARING_PVC_PROFILE_MAX = 5.0 +RARING_PVC_PROFILE_MIN = 0.0 +RARING_WEIGHT_PVC_PROFILE_IN_SET = 1.5 # сколько рейтинга (зведочек) набора составляет профиль +RARING_WEIGHT_GLAZING_IN_SET = 1.5 # сколько рейтинга (зведочек) набора составляет стеклопакет +# веса ранкинга (веса ранжирования) +RANK_STEP_SET_MODIFY = 0.75 # Дата последнего обновления цены набора +RANK_STEP_SET_DELIVERY = 2 # Доставка включена в стоимость +RANK_STEP_SET_UNINSTALL_INSTALL = 2 # Демонтаж/Монтаж включен в стоимость +RANK_STEP_SET_SILL = 1.5 # Подоконник включен в стоимость +RANK_STEP_SET_PANES = 1.5 # Водоотлив включен в стоимость +RANK_STEP_SET_SLOPE = 1.5 # Откос включен в стоимость +RANK_STEP_SET_CLIMATE_CONTROL = 0.5 # Климат-контроль включен в стоимость +RANK_STEP_SET_NUM_OFFER = 0.1 # Число предложений включен в стоимость +RANK_STEP_DISCOUNT_FLEX = 1 # Гибкость скидок (число шагов скидки) +RANK_STEP_DISCOUNT_MAX = 1 # Размер скидки (максимальная скидка) +RANK_GLAZ_SOUNDPROOFING = 1.0 # Шумоизоляция СТЕКЛОПАКЕТА +RANK_GLAZ_HEAT_TRANSFER = 1.0 # Теплопередача СТЕКЛОПАКЕТА +RANK_GLAZ_LIGHT_TRANSMISSION = 0.25 # Коэффициент светопропускания СТЕКЛОПАКЕТА +RANK_GLAZ_PASSING_SUN = 0.15 # Коэффициент солнцепропускания СТЕКЛОПАКЕТА +RANK_GLAZ_THICKNESS = 0.1 # Толщина СТЕКЛОПАКЕТА +RANK_GLAZ_CAMERAS_NUM = 0.1 # Число камер СТЕКЛОПАКЕТА +RANK_GLAZ_CAMERAS_NUM_NAME=u"Число камер" +RANK_PVCP_SOUNDPROOFING = 1.0 # Шумоизоляция ПРОФИЛЯ +RANK_PVCP_SOUNDPROOFING_NAME = u"Шумоизоляция" +RANK_PVCP_HEAT_TRANSFER = 1.0 # Теплопередача ПРОФИЛЯ +RANK_PVCP_HEAT_TRANSFER_NAME = u"Теплопередача" +RANK_PVCP_HEIGHT = 0.3 # Высота в световом проеме ПРОФИЛЯ +RANK_PVCP_HEIGHT_NAME = u"Высота в проёме" +RANK_PVCP_RABBET = 0.2 # Высота фальца ПРОФИЛЯ +RANK_PVCP_RABBET_NAME = u"Фальц" +RANK_PVCP_G_THICKNESS = 0.2 # Максимальная толщина стеклопакета ПРОФИЛЯ +RANK_PVCP_G_THICKNESS_NAME = u"Толщина стеклопакета" +RANK_PVCP_THICKNESS = 0.2 # Монтажная ширина ПРОФИЛЯ +RANK_PVCP_THICKNESS_NAME = u"Толщина профиля" +RANK_PVCP_SEALS = 1.2 # Контуров уплотненения ПРОФИЛЯ +RANK_PVCP_SEALS_NAME = u"Уплотнители" +RANK_PVCP_CAMERAS_NUM = 0.1 # Число камер ПРОФИЛЯ +RANK_PVCP_CAMERAS_NUM_NAME = u"Число камер" +RANK_PVCP_CAMERAS_POPULARITY_NAME = u"Популярность" +# бля блогов +NUM_BLOG_TIZER_IN_PAGE = 5 # дисто тизеров (анонсов) блогов на страничке +NUM_PAGE_IN_PAGINATOR = 3 # чисто отображаемых страничек в педжинаторе +# Унифицированные именования ключей для JSON-объектов +KEY_URL = "url" +KEY_NOTE = "note" +KEY_RATING = "RATING" +KEY_RATING_VIRTUAL = "RATING_V" +KEY_DICSOUNT = "%" +KEY_HTML = "html" +# KEY_RATING_VIRTUAL = "reting_v" +# Типы карточек каталога +CATALOG_RECORD_FOR_PROFILE_MODEL = 1 +CATALOG_RECORD_FOR_PROFILE_MANUFACTURER = 100 +CATALOG_SORTER_MAGIC_NUMBER_ADV = 5 +CATALOG_SORTER_MAGIC_NUMBER_TIZER = 1 diff --git a/oknardia/oknardia/urls.py b/oknardia/oknardia/urls.py index b347710..7494526 100644 --- a/oknardia/oknardia/urls.py +++ b/oknardia/oknardia/urls.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """oknardia Конфигурация URL Список `urlpatterns` направляет URL-адреса в представления. Дополнительную информацию см.: @@ -14,8 +15,39 @@ 2. Добавьте URL-адрес в urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, re_path +from django.conf.urls.static import static +from oknardia.settings import * +from web import views, autocomplete_addr, user_manager urlpatterns = [ path('admin/', admin.site.urls), + + # главная страница + re_path(r'^$', views.main_init), + # обработчик автокомлита (подсказки во время ввода адреса на главной странице) + re_path(r'^autocomplete_addr$', autocomplete_addr.autocomplete_addr), + # ОБРАБОТЧИКИ АВТОРИЗАЦИИ + # Вызов шаблона подгружаем 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\d{1,8})/CONFIRM:(?P\S+)$', user_manager.confirm_email), + # Ссылка, по которой пользователь может поменять пароль при утере. URL: /USER_%05d/RESTORE:%s + re_path(r'^USER_(?P\d{1,8})/RESTORE:(?P\S+)$', user_manager.restore_password), + re_path(r'^change_password$', user_manager.change_password), + ] + + +if DEBUG: + urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT) + +# ___ ____ _ _____ _ _ _____ _ +# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| | +# |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| | +# |_| |____/|___|___|___|_ | |_| |___|___|_|___|__,|_| |__| |__,|_|_|___|_| +# |___| diff --git a/oknardia/templates/base.html b/oknardia/templates/base.html new file mode 100755 index 0000000..2979306 --- /dev/null +++ b/oknardia/templates/base.html @@ -0,0 +1,93 @@ +{% load static %} + + + + + + + + + + + + + + + + + ОКНАРДИЯ: {% block Title %}{% endblock %} + + {# #} + {# #} + {% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %} + {# #} + {# #}{% block Top_JS1 %}{% endblock %}{% block Top_JS2 %}{% endblock %}{% block Top_JS3 %}{% endblock %}{% block Top_JS4 %}{% endblock %}{% block Top_JS5 %}{% endblock %}{% block Top_Meta1 %}{% endblock %} + + + + +{# Контент НАЧАЛО #} +{% block Main_Content %}{% endblock %} +{# Контент КОНЕЦ #} + + +{# ######################################## Верхнее меню НАЧАЛО ######################################## #} +{% block Top_Nav_Bar %}{% endblock %} +{# ######################################## Верхнее меню КОНЕЦ ######################################## #} + + +{# ######################################## Нижнее меню-футер НАЧАЛО ######################################## #} +{% block Bottom_Nav_Bar %} +{% endblock %} +{# ######################################## Нижнее меню-футер КОНЕЦ ######################################## #} + +{# Модальное окно SOCIAL LOGIN НАЧАЛО #} +{% block Modal_Login %}{% endblock %} +{# Модальное окно SOCIAL LOGIN КОНЕЦ #} + + \ No newline at end of file diff --git a/oknardia/templates/index.html b/oknardia/templates/index.html new file mode 100755 index 0000000..4372008 --- /dev/null +++ b/oknardia/templates/index.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} +{% load static %} + +{% block Title %}: выбор пластиковых окон в квартиру. Поставщики, цены, описания, характеристики, отзывы.{% endblock %} + +{% block Description %}Окнардия: Здесь собраны цены на установку пластиковых окон. Просто введите адрес и получите актуальные предложения от ведущих поставщиков окон, подробные характеристики профилей и стеклопакетов, информацию о скидках. Никаких предварительных замеров! Мы уже знаем размеры проёмов в квартире, рекомендованные схемы открывания, требования к стеклопакетам, профилю и многое другое. Замена пластиковых окон — ответственное мероприятие. Мы помогаем сделать объективный выбор.{% endblock %} + +{% block Keywords %}Цены на окна, цены на пластиковые окна, стоимость замены окон, пластиковые окна в квартиру, скидки на пластиковые окна, окна в квартиру, размеры окон, скидки на пластиковые окна, характеристики пластиковых окон, окна в панельный дом, окна в блочный дом.{% endblock %} + +{% block Top_JS1 %}{# comment #} +{# endcomment #} + + +{% endblock %} + +{% block Top_CSS2 %} + +{% endblock %} + +{% block Main_Content %} + +
+
+ +
+{% if not CONFIRM_OK %} + {% include "popup_index.html" %} +{% elif CONFIRM_OK == "YES" %} + {% include "user_manager/popup_cofirm_email_ok.html" %} +{% elif CONFIRM_OK == "NO" %} + {% include "user_manager/popup_confirm_email_or_restore_password_bad.html" %} +{% elif CONFIRM_OK == "CHANGE_PWD" %} + {% include "user_manager/popup_restore_pasword_form.html" %} +{% endif %}
+{% endblock %} + +{% comment %} +{% block Top_Nav_Bar %} + {# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #} +{% endblock %} +{% endcomment %} + diff --git a/oknardia/templates/popup_index.html b/oknardia/templates/popup_index.html new file mode 100755 index 0000000..7f9a685 --- /dev/null +++ b/oknardia/templates/popup_index.html @@ -0,0 +1,85 @@ +{% load static %} +{% with APP_INVITATION_STRING_IN_FORM="Город, улица, дом" %} + +

Выбирайте окна в квартиру:

+ {% if NV <= 1 %} +
    +
  1. Вводите адрес дома или выберите его из подсказки,
  2. +
  3. укажите тип квартиры,
  4. +
  5. получите актуальные цены и сравнивайте характеристики предложений.
  6. +
+ {% endif %} +
+ {% csrf_token %} +
+ + + Подсказывать адреса: +
+
+ + + + + + + +
+ {% if LAST_VISIT %}
Ваши последние просмотры:
+ +
{% endif %} + +
+

{% endwith %} \ No newline at end of file diff --git a/oknardia/web/autocomplete_addr.py b/oknardia/web/autocomplete_addr.py new file mode 100755 index 0000000..d2e32ad --- /dev/null +++ b/oknardia/web/autocomplete_addr.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +__author__ = 'Sergei Erjemin' + +from django.http import HttpResponse +from django.shortcuts import HttpResponseRedirect +from django.http import HttpRequest, HttpResponse +from oknardia.models import Building_Info +# from time import clock +import re +import urllib + + +def autocomplete_addr(request: HttpRequest) -> HttpResponse: + """ Функция для автозаполнения формы выбора адреса. Получает методом GET переменную "term" и по ее образцу + ищет доступные адреса в базе адреса из таблицы Building_Info + + :param request: входящий http-запрос + :return response: исходящий http-ответ + """ + # Для автозаполнения используется JQuery_UI: http://jqueryui.com/ + # Пример и инструкции по использованию: http://professorweb.ru/my/javascript/jquery/level4/4_5.php + # + # ВНИМАНИЕ ТЕХНИЧЕСКИЙ ДОЛГ,: Более навороченный, по описанию лучше подходящий компонент автозаполнения + # https://www.devbridge.com/sourcery/components/jquery-autocomplete/ не заработал. Ну и хрен с ним! + # + # ВНИМАНИЕ ТЕХНИЧЕСКИЙ ДОЛГ: возможен "перегрев" при частом обращении -- [Errno 10053] + # Предположительно из-за отсутсвия csrfmiddlewaretoken-серилизации Django. Проблема пофикусена(?) 2014-11-14 + # tStart = clock() + if request.method == 'GET' and 'term' in request.GET: + part_blocks = re.split(r"[,/;\s.\\:]+", str(request.GET['term'])) + if request.GET['use_filter'] == "only_known": + q_autocomplete = Building_Info.objects.filter(kSeria_Link__kRoot_id__isnull=False) + else: + q_autocomplete = Building_Info.objects + for i in part_blocks: + q_autocomplete = q_autocomplete.filter(sAddress__icontains=i) + q_autocomplete = q_autocomplete.all().order_by('sAddress') + to_response = "" + for i in q_autocomplete[:10]: + to_response += '"' + i.sAddress + u'",' + to_response = '[' + to_response[0:-1] + ']' # Убираем последнюю запятую + return HttpResponse(to_response) + else: + return HttpResponseRedirect("/") diff --git a/oknardia/web/views.py b/oknardia/web/views.py index 91ea44a..54a41b6 100644 --- a/oknardia/web/views.py +++ b/oknardia/web/views.py @@ -1,3 +1,44 @@ +# -*- coding: utf-8 -*- from django.shortcuts import render +from django.http import HttpRequest, HttpResponse +import json +import datetime -# Create your views here. +# from django.core.context_processors import csrf + + +def main_init(request: HttpRequest) -> HttpResponse: + """ Главная страница (статичная, только с проверками куков) + + :param request: входящий http-запрос + :return response: исходящий http-ответ + """ + to_template = {} # словарь, для передачи шаблону + num_viz = 0 # как будто первый визит + template = "index.html" # шаблон + # проверяем куки числа визита + if "NumVisit" in request.COOKIES: + # стоят куки, и это не первый визит + num_viz = request.COOKIES["NumVisit"] # читаем число визитов + num_viz = int(num_viz) + 1 # увеличиваем порядковый номер визитов + # ПРОВЕРЯЧЕМ КУКИ ПРОСМОТРЕ ЦЕНОВЫХ ПРЕДЛОЖЕНИЙ + if "LastVisit" in request.COOKIES: + # стоят куки + last_visit = json.loads(request.COOKIES["LastVisit"]) + last_visit2 = [] + for i in last_visit: + last_visit2.append({ + "Time": datetime.datetime.fromtimestamp(i["Time"]), + "LastURL": i["LastURL"], + "LastAddress": i["LastAddress"], + "LastApart": i["LastApart"] + }) + to_template.update({'LAST_VISIT': last_visit2[:3]}) + else: + to_template.update({'LAST_VISIT': None}) + to_template.update({'META_DOCUMENT_STATE': u"Static"}) # Эта страничка статичная (в шаблон) + to_template.update({'NV': num_viz}) + # to_template.update(csrf(request)) # токен, для метода POST и GET + response = render(request, template, to_template) + response.set_cookie("NumVisit", num_viz, max_age=604800) # ставим или перезаписываем куки (неделя) + return response