views.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import re
  2. from django.conf import settings
  3. from django.core.mail import EmailMessage, get_connection
  4. from django.http import Http404, HttpResponseRedirect
  5. from django.shortcuts import render
  6. from landing.forms import MyForm, OrderForm, SERVICE_CHOICES
  7. from landing.models import FAQmodel, LandingMainPageSettings, LandingPage, LandingTeamMember
  8. from landing.utils import (
  9. get_faq_response,
  10. get_landing_category_agreement_url,
  11. get_landing_service_agreement_data,
  12. )
  13. from dbsynce.lib import dicts
  14. from dbsynce.lib.geolocation import CoordinatesValidationError, parse_coordinates
  15. from dbsynce.models import Provider, Service, ServiceCategory
  16. from dbsynce.services.service_catalog import (
  17. build_public_service_catalog,
  18. get_public_category_catalog,
  19. get_public_tariff_catalog,
  20. user_can_order_catalog,
  21. )
  22. from sharix_admin.models import Metaservice
  23. import core.settings_vars as sv
  24. def get_current_metaservice():
  25. try:
  26. return Metaservice.objects.order_by('-created_at').first()
  27. except Exception:
  28. return None
  29. def get_landing_settings():
  30. return LandingMainPageSettings.get_current()
  31. def get_active_service_categories():
  32. return [item["object"] for item in build_public_service_catalog()]
  33. def prepare_landing_catalog(request, catalog, metaservice=None):
  34. for category_card in catalog:
  35. category_card["agreement_url"] = get_landing_category_agreement_url(
  36. request,
  37. category_card["object"],
  38. metaservice=metaservice,
  39. )
  40. return catalog
  41. def send_email_mess(body, sub):
  42. with get_connection(
  43. host=settings.EMAIL_HOST,
  44. port=settings.EMAIL_PORT,
  45. username=settings.EMAIL_HOST_USER,
  46. password=settings.EMAIL_HOST_PASSWORD,
  47. use_tls=settings.EMAIL_USE_TLS,
  48. use_ssl=settings.EMAIL_USE_SSL
  49. ) as connection:
  50. subject = sub
  51. email_from = settings.EMAIL_HOST_USER
  52. recipient_list = [settings.EMAIL_TO]
  53. EmailMessage(subject, body, email_from, recipient_list, connection=connection).send()
  54. def index_html(request):
  55. form = MyForm(request.POST or None)
  56. if request.method == 'POST' and form.is_valid():
  57. body = f"""
  58. Имя: {form.cleaned_data['name']}\n
  59. Email: {form.cleaned_data['email']}\n
  60. Комментарий: {form.cleaned_data['message']}
  61. """
  62. send_email_mess(body, "Пользователь оставил отзыв")
  63. metaservice = get_current_metaservice()
  64. service_catalog = prepare_landing_catalog(
  65. request,
  66. build_public_service_catalog(),
  67. metaservice=metaservice,
  68. )
  69. context = {
  70. "menu": [item["object"] for item in service_catalog],
  71. "service_catalog": service_catalog,
  72. "can_order_catalog": user_can_order_catalog(request.user),
  73. "form": form,
  74. "metaservice": metaservice,
  75. "landing_settings": get_landing_settings(),
  76. "landing_team_members": (
  77. LandingTeamMember.objects
  78. .filter(is_active=True)
  79. .select_related('user')
  80. .order_by('sort_order', 'id')
  81. ),
  82. }
  83. return render(request, 'landing/pages/main.html', context=context)
  84. def page_html(request):
  85. form = MyForm(request.POST or None)
  86. slug = request.path[1:-1]
  87. metaservice = get_current_metaservice()
  88. mainpage = LandingPage.objects.filter(slug=slug).first()
  89. menu = LandingPage.objects.filter(slug__startswith='clean')
  90. if mainpage is None:
  91. return render(request, 'landing/pages/in_work.html', context={
  92. "form": form,
  93. "menu": menu,
  94. "metaservice": metaservice,
  95. })
  96. return render(request, 'landing/pages/simple.html', context={
  97. "mainpage": mainpage,
  98. "form": form,
  99. "menu": menu,
  100. "metaservice": metaservice,
  101. })
  102. def service_category_html(request, pk):
  103. category_catalog = get_public_category_catalog(pk)
  104. if category_catalog is None:
  105. raise Http404('Service category does not exist')
  106. metaservice = get_current_metaservice()
  107. service_catalog = prepare_landing_catalog(
  108. request,
  109. build_public_service_catalog(),
  110. metaservice=metaservice,
  111. )
  112. category_catalog["agreement_url"] = get_landing_category_agreement_url(
  113. request,
  114. category_catalog["object"],
  115. metaservice=metaservice,
  116. )
  117. context = {
  118. "menu": [item["object"] for item in service_catalog],
  119. "category_catalog": category_catalog,
  120. "service_category": category_catalog["object"],
  121. "services_cards": category_catalog["tariffs"],
  122. "can_order_catalog": user_can_order_catalog(request.user),
  123. "metaservice": metaservice,
  124. }
  125. return render(request, 'landing/pages/service_category.html', context=context)
  126. def service_detail_html(request, pk):
  127. tariff_catalog = get_public_tariff_catalog(pk)
  128. if tariff_catalog is None:
  129. raise Http404('Service does not exist')
  130. service = tariff_catalog["object"]
  131. metaservice = get_current_metaservice()
  132. service_catalog = prepare_landing_catalog(
  133. request,
  134. build_public_service_catalog(),
  135. metaservice=metaservice,
  136. )
  137. provider_cards = tariff_catalog["offers"]
  138. for index, provider_card in enumerate(provider_cards, start=1):
  139. provider_card["offer_number"] = index
  140. service_agreement_data = get_landing_service_agreement_data(
  141. request=request,
  142. service=service,
  143. metaservice=metaservice,
  144. )
  145. context = {
  146. "menu": [item["object"] for item in service_catalog],
  147. "service": service,
  148. "tariff_catalog": tariff_catalog,
  149. "provider_cards": provider_cards,
  150. "active_providers": [item["object"] for item in provider_cards],
  151. "can_order_catalog": user_can_order_catalog(request.user),
  152. "metaservice": metaservice,
  153. "service_agreement_data": service_agreement_data,
  154. "service_agreement": service_agreement_data,
  155. }
  156. return render(request, 'landing/pages/service_detail.html', context=context)
  157. def send_email(request):
  158. if request.method == 'POST':
  159. request.POST.get('email')
  160. return HttpResponseRedirect('/')
  161. def make_unique_address_variants(items):
  162. result = []
  163. seen = set()
  164. for item in items:
  165. value = str(item or '').strip()
  166. value = re.sub(r'\s+', ' ', value).strip(' ,.;')
  167. if not value:
  168. continue
  169. key = value.lower()
  170. if key in seen:
  171. continue
  172. seen.add(key)
  173. result.append(value)
  174. return result
  175. def clean_legal_address_for_map(address):
  176. value = str(address or '').strip()
  177. if not value:
  178. return ''
  179. # Убираем почтовый индекс в начале: 199155, ...
  180. value = re.sub(r'^\s*\d{5,6}\s*,?\s*', '', value)
  181. # Убираем офисно-юридические уточнения, которые обычно мешают геокодеру.
  182. value = re.sub(
  183. r'\b(помещ|помещение|офис|каб|кабинет|комн|комната|этаж)\.?\s*[^,;]*',
  184. '',
  185. value,
  186. flags=re.IGNORECASE,
  187. )
  188. # Нормализуем частые русские сокращения.
  189. value = re.sub(r'\bгород\b\s*', '', value, flags=re.IGNORECASE)
  190. value = re.sub(r'\bг\.\s*', '', value, flags=re.IGNORECASE)
  191. value = re.sub(r'\bул\.\s*', 'улица ', value, flags=re.IGNORECASE)
  192. value = re.sub(r'\bпр-?т\.?\s*', 'проспект ', value, flags=re.IGNORECASE)
  193. value = re.sub(r'\bпер\.\s*', 'переулок ', value, flags=re.IGNORECASE)
  194. value = re.sub(r'\bнаб\.\s*', 'набережная ', value, flags=re.IGNORECASE)
  195. value = re.sub(r'\bд\.\s*', '', value, flags=re.IGNORECASE)
  196. # "32 к 1" -> "32 корпус 1"
  197. value = re.sub(r'(\d+)\s*к\s*(\d+)', r'\1 корпус \2', value, flags=re.IGNORECASE)
  198. # "литер д" / "литера д" -> "литера д"
  199. value = re.sub(r'\bлитер(?:а)?\.?\s*([а-яa-z])\b', r'литера \1', value, flags=re.IGNORECASE)
  200. # Чистим лишние запятые и пробелы.
  201. value = re.sub(r'\s*,\s*', ', ', value)
  202. value = re.sub(r'(,\s*)+', ', ', value)
  203. value = re.sub(r'\s+', ' ', value).strip(' ,.;')
  204. return value
  205. def build_contact_address_variants(address):
  206. original = str(address or '').strip()
  207. if not original:
  208. return []
  209. cleaned = clean_legal_address_for_map(original)
  210. # Вариант без литеры часто находится лучше.
  211. cleaned_without_litera = re.sub(
  212. r'\s*,?\s*литера\s+[а-яa-z]\b',
  213. '',
  214. cleaned,
  215. flags=re.IGNORECASE,
  216. ).strip(' ,.;')
  217. # Вариант без корпуса иногда тоже помогает.
  218. cleaned_without_korpus = re.sub(
  219. r'\s+корпус\s+\d+\b',
  220. '',
  221. cleaned_without_litera,
  222. flags=re.IGNORECASE,
  223. ).strip(' ,.;')
  224. variants = [
  225. cleaned,
  226. cleaned_without_litera,
  227. cleaned_without_korpus,
  228. original,
  229. ]
  230. variants_with_country = []
  231. for value in variants:
  232. if not value:
  233. continue
  234. variants_with_country.append(value)
  235. if 'россия' not in value.lower():
  236. variants_with_country.append(f'Россия, {value}')
  237. return make_unique_address_variants(variants_with_country)
  238. def normalize_contact_address(address):
  239. variants = build_contact_address_variants(address)
  240. if variants:
  241. return variants[0]
  242. return ''
  243. def get_first_object_value(obj, *field_names, default=''):
  244. if obj is None:
  245. return default
  246. for field_name in field_names:
  247. if not hasattr(obj, field_name):
  248. continue
  249. value = getattr(obj, field_name)
  250. if callable(value):
  251. continue
  252. if value is not None and str(value).strip() != '':
  253. return value
  254. return default
  255. def contact_html(request):
  256. metaservice = get_current_metaservice()
  257. address = metaservice.address if metaservice and metaservice.address else 'г. Москва, ул. Большая Ордынка 5к3'
  258. contact_data = {
  259. 'email': metaservice.email if metaservice and metaservice.email else 'email@sharix.org',
  260. 'phone': metaservice.phone if metaservice and metaservice.phone else '',
  261. 'address': address,
  262. 'map_search_address': normalize_contact_address(address),
  263. 'map_search_variants': build_contact_address_variants(address),
  264. 'legal_name': metaservice.legal_name if metaservice and metaservice.legal_name else 'SHARiX',
  265. 'inn': metaservice.inn if metaservice and metaservice.inn else '',
  266. 'kpp': metaservice.kpp if metaservice and metaservice.kpp else '',
  267. 'ogrn': metaservice.ogrn if metaservice and metaservice.ogrn else '',
  268. 'bank_name': metaservice.bank_name if metaservice and metaservice.bank_name else '',
  269. 'bik': metaservice.bik if metaservice and metaservice.bik else '',
  270. 'rs': metaservice.rs if metaservice and metaservice.rs else '',
  271. 'ks': metaservice.ks if metaservice and metaservice.ks else '',
  272. }
  273. context = {
  274. "menu": get_active_service_categories(),
  275. "contact": contact_data,
  276. "metaservice": metaservice,
  277. }
  278. return render(request, 'landing/pages/contact.html', context)
  279. def price_html(request):
  280. return render(request, 'landing/pages/price.html', {
  281. "menu": get_active_service_categories(),
  282. "metaservice": get_current_metaservice(),
  283. })
  284. def order_html(request):
  285. metaservice = get_current_metaservice()
  286. form = OrderForm(request.POST or None)
  287. if request.method == 'POST' and form.is_valid():
  288. services = ""
  289. for item in form.cleaned_data['service']:
  290. services += f"{SERVICE_CHOICES[int(item)][1]}, "
  291. body = f"""
  292. Имя: {form.cleaned_data['name']}\n
  293. Фамилия: {form.cleaned_data['surname']}\n
  294. Номер телефона: {form.cleaned_data['phone']}\n
  295. Выбранные услуги: {services}\n
  296. Желаемое время оказания услуги: {form.cleaned_data['datetime']}\n
  297. Комментарий: {form.cleaned_data['comment'] or "Комментарий отсутствует"}
  298. """
  299. send_email_mess(body, "ЗАКАЗ")
  300. context = {
  301. "form": form,
  302. "menu": get_active_service_categories(),
  303. "metaservice": metaservice,
  304. }
  305. return render(request, 'landing/pages/order.html', context)
  306. def faq_html(request):
  307. prompt = ""
  308. answer = None
  309. landing_settings = get_landing_settings()
  310. if request.method == "POST":
  311. prompt = (request.POST.get("prompt") or "").strip()
  312. if prompt:
  313. answer = get_faq_response(prompt, landing_settings)
  314. else:
  315. answer = "Введите текст вопроса."
  316. context = {
  317. "form": MyForm(),
  318. "menu": get_active_service_categories(),
  319. "faq": FAQmodel.objects.all(),
  320. "metaservice": get_current_metaservice(),
  321. "landing_settings": landing_settings,
  322. "prompt": prompt,
  323. "answer": answer,
  324. }
  325. return render(request, 'landing/pages/faq.html', context)
  326. def parse_provider_location(provider, offer_number):
  327. """Build a public marker only from a valid static point."""
  328. if provider.location_type != dicts.location_type["static"]:
  329. return None
  330. raw_location = str(provider.default_location or "").strip()
  331. if not raw_location:
  332. return None
  333. try:
  334. point = parse_coordinates(raw_location)
  335. except CoordinatesValidationError:
  336. return None
  337. title = f"Предложение {offer_number}"
  338. service_caption = provider.service.caption if provider.service else ""
  339. company_name = provider.company.legal_name if provider.company else ""
  340. return {
  341. "id": provider.pk,
  342. "title": title,
  343. "service": service_caption,
  344. "company": company_name,
  345. "location": raw_location,
  346. "lat": float(point.latitude),
  347. "lng": float(point.longitude),
  348. }
  349. def providers_map_html(request):
  350. providers = (
  351. Provider.objects
  352. .filter(status='0', is_visible='t')
  353. .select_related('service', 'company')
  354. .order_by('id')
  355. )
  356. offer_markers = [
  357. item
  358. for item in (
  359. parse_provider_location(provider, index)
  360. for index, provider in enumerate(providers, start=1)
  361. )
  362. if item
  363. ]
  364. context = {
  365. "menu": get_active_service_categories(),
  366. "metaservice": get_current_metaservice(),
  367. "offer_markers": offer_markers,
  368. }
  369. return render(request, 'landing/pages/providers_map.html', context)
  370. def ai_demo_html(request):
  371. return faq_html(request)
  372. def support_html(request):
  373. return faq_html(request)