diff --git a/oknardia/templates/popup/popup_index.html b/oknardia/templates/popup/popup_index.html index d430198..af285cc 100755 --- a/oknardia/templates/popup/popup_index.html +++ b/oknardia/templates/popup/popup_index.html @@ -75,11 +75,7 @@ - {% if LAST_VISIT %}
Ваши последние просмотры:
- -
{% endif %} + {% include 'report/report_last_user_visit.html' with background_color="None" %}

{% endwith %} \ No newline at end of file diff --git a/oknardia/templates/price/price_list.html b/oknardia/templates/price/price_list.html index 444075b..ac5a178 100755 --- a/oknardia/templates/price/price_list.html +++ b/oknardia/templates/price/price_list.html @@ -107,7 +107,8 @@ {% endblock %} -{% block Top_JS3%} + diff --git a/oknardia/web/catalog.py b/oknardia/web/catalog.py index b09f747..484b326 100644 --- a/oknardia/web/catalog.py +++ b/oknardia/web/catalog.py @@ -7,7 +7,7 @@ from django.shortcuts import render, redirect from oknardia.models import Seria_Info, SetKit from web.add_func import get_rating_set_for_stars -from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list def catalog_root(request: HttpRequest) -> HttpResponse: @@ -21,7 +21,6 @@ def catalog_root(request: HttpRequest) -> HttpResponse: time_start = time.perf_counter() # получаем из cookies последние визиты клиента to_template: dict[str, object] = { - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start)} response = render(request, "catalog/catalog_root.html", to_template) @@ -83,7 +82,6 @@ def catalog_sets(request: HttpRequest) -> HttpResponse: to_template: dict[str, object] = { 'SET_LIST': kits, - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start), } diff --git a/oknardia/web/catalog_companies.py b/oknardia/web/catalog_companies.py index b4f448a..8d0005b 100644 --- a/oknardia/web/catalog_companies.py +++ b/oknardia/web/catalog_companies.py @@ -17,11 +17,7 @@ from oknardia.models import ( SetKit, PriceOffer, ) -from web.report1 import ( - get_last_all_user_visit_list, - get_last_user_visit_cookies, - get_last_user_visit_list -) +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list from web.add_func import get_rating_set_for_stars import django.utils.dateformat import time @@ -179,7 +175,6 @@ def catalog_company(request: HttpRequest) -> HttpResponse: Контекст шаблона: - COMPANIES (list): Список компаний с статистикой - - LAST_VISIT (list): Последние визиты текущего пользователя - LOG_VISIT (list): Последние визиты всех пользователей Args: @@ -200,9 +195,6 @@ def catalog_company(request: HttpRequest) -> HttpResponse: # Получаем информацию о посещениях для персонализации to_template: dict[str, object] = { 'COMPANIES': formatted_companies, - 'LAST_VISIT': get_last_user_visit_list( - get_last_user_visit_cookies(request)[:3] - ), 'LOG_VISIT': get_last_all_user_visit_list(), } @@ -469,7 +461,6 @@ def catalog_company_detail( - SETS (list): Список оконных наборов с их полной информацией - IMG_FOR_BLOG (str): Логотип компании - LIST_NOT (list): Стандартные маркеры "пусто" - - LAST_VISIT (list): Последние визиты текущего пользователя - LOG_VISIT (list): Последние визиты всех пользователей - ticks (float): Время выполнения представления (в секундах) @@ -518,9 +509,6 @@ def catalog_company_detail( 'META_KEYWORDS': company.sMerchantName, 'IMG_FOR_BLOG': company.pMerchantLogo, 'LIST_NOT': empty_values, - 'LAST_VISIT': get_last_user_visit_list( - get_last_user_visit_cookies(request)[:3] - ), 'LOG_VISIT': get_last_all_user_visit_list(), } diff --git a/oknardia/web/catalog_openings.py b/oknardia/web/catalog_openings.py index 5c9eefd..e5bced4 100644 --- a/oknardia/web/catalog_openings.py +++ b/oknardia/web/catalog_openings.py @@ -3,7 +3,7 @@ from django.db.models import F from django.shortcuts import render from django.http import HttpRequest, HttpResponse from oknardia.models import MountDim2Apartment -from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list from web.add_func import get_flaps_for_mini_pictures import time import pytils @@ -20,8 +20,6 @@ def _make_slug(value: str) -> str: def _append_visit_context(to_template: dict, request: HttpRequest, time_start: float) -> None: """Дописывает в контекст стандартный хвост: визиты и время выполнения.""" to_template.update({ - # получаем последние визиты клиента через куки - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), # получаем последние визиты всех посетителей из базы 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start), diff --git a/oknardia/web/catalog_profiles.py b/oknardia/web/catalog_profiles.py index d19dbe5..fa70c4f 100644 --- a/oknardia/web/catalog_profiles.py +++ b/oknardia/web/catalog_profiles.py @@ -7,7 +7,7 @@ from django.shortcuts import render, redirect from django.http import HttpRequest, HttpResponse from oknardia.settings import * from oknardia.models import Catalog2Profile, PVCprofiles, PriceOffer -from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list from web.add_func import normalize, get_rating_set_for_stars import time import json @@ -49,7 +49,6 @@ def _profile_row_to_dict(profile: dict) -> dict: def _append_visit_context(to_template: dict, request: HttpRequest, time_start: float) -> None: """Дописывает в контекст стандартный хвост: визиты и время выполнения.""" to_template.update({ - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start), }) diff --git a/oknardia/web/catalog_series.py b/oknardia/web/catalog_series.py index 2c46124..77129d0 100644 --- a/oknardia/web/catalog_series.py +++ b/oknardia/web/catalog_series.py @@ -13,7 +13,7 @@ from oknardia.models import ( Win_MountDim, Building_Info, ) -from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list from web.add_func import get_flaps_for_big_pictures import time import os @@ -29,7 +29,6 @@ def _make_slug(value: str) -> str: def _append_visit_context(to_template: dict, request: HttpRequest, time_start: float) -> None: """Дописывает в контекст стандартный хвост: визиты и время выполнения.""" to_template.update({ - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start), }) diff --git a/oknardia/web/prices.py b/oknardia/web/prices.py index 4c78c0c..57e0ce6 100644 --- a/oknardia/web/prices.py +++ b/oknardia/web/prices.py @@ -14,7 +14,7 @@ from oknardia.models import ( MountDim2Apartment, ) from oknardia.settings import * -from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \ get_geo_distance import django.utils.dateformat @@ -57,11 +57,8 @@ def _append_visit_context( """Дописывает в контекст стандартный хвост: визиты и время выполнения.""" if log_visit is None: log_visit = get_last_all_user_visit_list() - if last_visit_cookie is None: - last_visit_cookie = get_last_user_visit_cookies(request) to_template.update({ - 'LAST_VISIT': get_last_user_visit_list(last_visit_cookie[:3]), 'LOG_VISIT': log_visit, 'ticks': float(time.perf_counter() - time_start), }) @@ -956,29 +953,14 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str = ) log_entry.save() # INSERT - # получаем последние визиты клиента через куки - last_visit = get_last_user_visit_cookies(request) - # Для блока LAST_VISIT показываем историю до текущего захода. - last_visit_for_context = list(last_visit) - # подготавливаем данные о текущем посещении для помещения в cookie - Item = { - "LastURL": new_url, - "LastAddress": to_template["ADDRESS"], - "LastApart": to_template["APART"], - "Time": time.perf_counter()} - last_visit.insert(0, Item) # Добавляем текущий Item в начало - last_visit = json.dumps(last_visit[:3]) # упаковываем json без пробелов (три записи) - # print u"сейчас запишем вот эту куку:", LastVisit + # Вызываем контекст без параметра last_visit_cookie (получит из кук автоматически) _append_visit_context( to_template=to_template, request=request, time_start=time_start, log_visit=log_visit, - last_visit_cookie=last_visit_for_context, ) - response = render(request, "price/price_list.html", to_template) - response.set_cookie("LastVisit", last_visit, max_age=7862400) # ставим или перезаписываем куки (91 день) - return response + return render(request, "price/price_list.html", to_template) def next_price_frame(request: HttpRequest, apart_id: str = "1", mount_dim_per_offer: str = "1", diff --git a/oknardia/web/report1.py b/oknardia/web/report1.py index c394f31..5818843 100644 --- a/oknardia/web/report1.py +++ b/oknardia/web/report1.py @@ -95,21 +95,6 @@ def _bounds(items: list, field: str, threshold=None) -> tuple[float, float]: return min(vals), max(vals) -def get_last_user_visit_cookies(request: HttpRequest) -> list: - """ Служебная функция: проверяет есть ли куки о последних посещениях пользователя, и если есть возвращает их - - :param request: HttpRequest -- входящий http-запрос - :return LastVisit: json -- загруженный json-объект из куки LastVisit - """ - if "LastVisit" in request.COOKIES: - try: - return json.loads(request.COOKIES["LastVisit"]) - except (json.decoder.JSONDecodeError, TypeError, ValueError, KeyError, AttributeError): - return [] - else: - return [] - - def get_last_user_visit_list(list_visit: list) -> list: """ Служебная функция: получает список с посещенных страниц с ценовой выдачей (ListVisit), меняет в нем даты на описание типа "три недели назад" и возвращает обратно. @@ -417,10 +402,7 @@ def compare_offers(request: HttpRequest, to_compare: str = "1,2") -> HttpRespons except SetKit.DoesNotExist: pass to_template.update({ - # получаем последние визиты клиента через куки - 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), # получаем последние визиты всех посетителей из базы - # id2log, log_visit = get_last_all_user_visit_list() 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start) }) diff --git a/oknardia/web/test_prices.py b/oknardia/web/test_prices.py index c7e8e2d..596f43e 100644 --- a/oknardia/web/test_prices.py +++ b/oknardia/web/test_prices.py @@ -158,9 +158,6 @@ class ReportOneWinPriceTests(TestCase): sOfferActive=False, ) - @patch("web.prices.get_last_all_user_visit_list", return_value=[]) - @patch("web.prices.get_last_user_visit_list", return_value=[]) - @patch("web.prices.get_last_user_visit_cookies", return_value=[]) @patch("web.prices.get_flaps_for_mini_pictures", return_value="img/test-mini.png") @patch( "web.prices.get_flaps_for_big_pictures", @@ -178,9 +175,6 @@ class ReportOneWinPriceTests(TestCase): self, mocked_big_pictures, mocked_mini_pictures, - mocked_cookies, - mocked_last_visits, - mocked_all_visits, ): """Вьюха должна собирать тот же ключевой контекст, но уже без raw SQL.""" request = self.factory.get( @@ -216,9 +210,6 @@ class ReportOneWinPriceTests(TestCase): self.assertIn("META_DATA_PUBLISH", context) self.assertTrue(mocked_big_pictures.called) self.assertTrue(mocked_mini_pictures.called) - self.assertTrue(mocked_cookies.called) - self.assertTrue(mocked_last_visits.called) - self.assertTrue(mocked_all_visits.called) def test_report_one_win_price_redirects_to_canonical_dimensions(self): """Если SEO-размеры в URL неверные, вьюха должна редиректить на канонический URL.""" diff --git a/oknardia/web/tests.py b/oknardia/web/tests.py index 0dfef8f..79209a1 100644 --- a/oknardia/web/tests.py +++ b/oknardia/web/tests.py @@ -156,14 +156,8 @@ class CatalogProfileViewTests(TestCase): return profile, sibling, brand, blog - @patch("web.catalog.get_last_all_user_visit_list", return_value=["all-visits"]) - @patch("web.catalog.get_last_user_visit_list", return_value=["last-visits"]) - @patch("web.catalog.get_last_user_visit_cookies", return_value=["cookie-1", "cookie-2", "cookie-3"]) def test_catalog_profile_handles_empty_catalog( self, - mocked_cookies, - mocked_last_visits, - mocked_all_visits, ): """Пустой каталог не должен падать и должен отдавать ожидаемый контекст.""" with self.assertNumQueries(1): @@ -174,20 +168,10 @@ class CatalogProfileViewTests(TestCase): self.assertEqual(context["CATALOG_PROFILE_NUM"], "0 профилей") self.assertEqual(context["CATALOG_MANUFACT_NUM"], 0) self.assertEqual(context["CATALOG_PROFILE_MAN1_NAME2"], []) - self.assertEqual(context["LAST_VISIT"], ["last-visits"]) - self.assertEqual(context["LOG_VISIT"], ["all-visits"]) - self.assertTrue(mocked_cookies.called) - self.assertTrue(mocked_last_visits.called) - self.assertTrue(mocked_all_visits.called) - @patch("web.catalog.get_last_all_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_cookies", return_value=[]) + def test_catalog_profile_groups_and_sorts_profiles( self, - mocked_cookies, - mocked_last_visits, - mocked_all_visits, ): """Каталог должен группировать профили по производителю и сохранять сортировку.""" self._create_profile(name="Alpha Basic", brief="Альфа База", manufacturer="Альфа", days_ago=5) @@ -224,20 +208,9 @@ class CatalogProfileViewTests(TestCase): # Проверяем итоговые счетчики и структуру контекста. self.assertEqual(context["CATALOG_MANUFACT_NUM"], 2) self.assertEqual(context["CATALOG_PROFILE_NUM"], "4 профиля") - self.assertEqual(context["LAST_VISIT"], []) - self.assertEqual(context["LOG_VISIT"], []) - self.assertTrue(mocked_cookies.called) - self.assertTrue(mocked_last_visits.called) - self.assertTrue(mocked_all_visits.called) - @patch("web.catalog.get_last_all_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_cookies", return_value=[]) def test_catalog_profile_model_redirects_to_canonical_url( self, - mocked_cookies, - mocked_last_visits, - mocked_all_visits, ): """При неверных slug страница должна отправлять на канонический URL.""" profile = self._create_profile(name="Alpha Basic", brief="Альфа База", manufacturer="Альфа", days_ago=5) @@ -248,14 +221,8 @@ class CatalogProfileViewTests(TestCase): self.assertEqual(response.status_code, 302) self.assertEqual(response["Location"], f"/catalog/profile/{profile.id}-alfa/{profile.id}-alpha-basic") - @patch("web.catalog.get_last_all_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_list", return_value=[]) - @patch("web.catalog.get_last_user_visit_cookies", return_value=[]) def test_catalog_profile_model_renders_related_data( self, - mocked_cookies, - mocked_last_visits, - mocked_all_visits, ): """Карточка профиля должна собираться через ORM и отдавать все ключевые блоки.""" profile, sibling, brand, blog = self._create_catalog_profile_model_fixture() @@ -287,7 +254,4 @@ class CatalogProfileViewTests(TestCase): self.assertEqual(context["IMG_FOR_BLOG"], blog.sImgForBlogSocial) self.assertEqual(context["PUB_DAT"].date(), blog.dPostDataModify.date()) self.assertEqual(context["LIST_OTHER"], ["Контур:2", "Цвет:Белый"]) - self.assertTrue(mocked_cookies.called) - self.assertTrue(mocked_last_visits.called) - self.assertTrue(mocked_all_visits.called) diff --git a/oknardia/web/views.py b/oknardia/web/views.py index f3ca3de..edfe6e3 100644 --- a/oknardia/web/views.py +++ b/oknardia/web/views.py @@ -16,7 +16,7 @@ import pytils def main_init(request: HttpRequest) -> HttpResponse: - """ Главная страница (статичная, только с проверками куков) + """ Главная страница (статичная, только с проверками кук) :param request: входящий http-запрос :return response: исходящий http-ответ @@ -28,22 +28,6 @@ def main_init(request: HttpRequest) -> HttpResponse: # стоят куки, и это не первый визит 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, "index.html", to_template) diff --git a/public/static/js/track_user_visit.js b/public/static/js/track_user_visit.js new file mode 100644 index 0000000..1776e8d --- /dev/null +++ b/public/static/js/track_user_visit.js @@ -0,0 +1,98 @@ +/** + * Логика записи визитов пользователя в cookies. + * Отслеживает последние посещения страниц с ценовой выдачей. + * + * Используемые данные из HTML: + * - data-current-url: текущий URL страницы + * - data-address: адрес здания + * - data-apart: тип квартиры + * + * Сохраняет в куку 'LastVisit' максимум 3 последних визита в формате JSON. + */ + +function trackUserVisit(currentUrl, address, apart) { + // Функция для получения значения куки по имени + function getCookieValue(name) { + try { + if (document.cookie) { + const cookies = document.cookie.split('; '); + for (let cookie of cookies) { + const [cookieName, cookieValue] = cookie.split('='); + if (cookieName === name) { + return decodeURIComponent(cookieValue); + } + } + } + } catch (e) { + console.warn('Ошибка при чтении куки:', e); + } + return null; + } + + // Функция для установки куки с заданным сроком жизни + function setCookie(name, value, maxAge) { + try { + const cookieValue = encodeURIComponent(value); + let cookieString = `${name}=${cookieValue}; path=/`; + if (maxAge) { + cookieString += `; max-age=${maxAge}`; + } + document.cookie = cookieString; + } catch (e) { + console.warn('Ошибка при установке куки:', e); + } + } + + // Получаем последние визиты из куки (если есть) + let lastVisits = []; + const cookieValue = getCookieValue('LastVisit'); + if (cookieValue) { + try { + lastVisits = JSON.parse(cookieValue); + } catch (e) { + console.warn('Ошибка при разборе JSON из куки LastVisit:', e); + lastVisits = []; + } + } + + // Создаём новый item посещения с текущей информацией + const newItem = { + LastURL: currentUrl, + LastAddress: address, + LastApart: apart, + Time: performance.now() // используем performance.now() как аналог time.perf_counter() в Python + }; + + // Добавляем новый item в начало списка + lastVisits.unshift(newItem); + + // Оставляем максимум 3 последних записи + lastVisits = lastVisits.slice(0, 4); + + // Упаковываем в JSON (JSON.stringify без пробелов для компактности) + const jsonData = JSON.stringify(lastVisits); + + // Устанавливаем куки на 91 день (7862400 секунд) + setCookie('LastVisit', jsonData, 7862400); +} + +/** + * Инициализация отслеживания при загрузке документа. + * Ищет элемент с атрибутами data-current-url, data-address, data-apart + * и вызывает trackUserVisit с полученными значениями. + */ +document.addEventListener('DOMContentLoaded', function() { + // Ищем элемент со встроенными данными (например, скрытый div в шаблоне) + const trackingElement = document.querySelector('[data-current-url]'); + + if (trackingElement) { + const currentUrl = trackingElement.getAttribute('data-current-url'); + const address = trackingElement.getAttribute('data-address'); + const apart = trackingElement.getAttribute('data-apart'); + + if (currentUrl && address && apart) { + trackUserVisit(currentUrl, address, apart); + } + } +}); +