views.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import mimetypes
  2. import os
  3. from itertools import chain
  4. from datetime import datetime
  5. from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect, JsonResponse
  6. from django.contrib.auth.decorators import login_required
  7. from django.contrib.contenttypes.models import ContentType
  8. from django.core.paginator import Paginator, InvalidPage, EmptyPage, PageNotAnInteger
  9. from django.shortcuts import redirect, render
  10. from django.utils import timezone
  11. from django.utils.translation import ungettext, gettext_lazy as _
  12. from icalendar import Calendar
  13. from wagtail.admin import messages
  14. from wagtail.search.backends import db, get_search_backend
  15. from wagtail.search.models import Query
  16. from coderedcms import utils
  17. from coderedcms.forms import SearchForm
  18. from coderedcms.models import (
  19. CoderedPage,
  20. CoderedEventPage,
  21. get_page_models,
  22. GeneralSettings,
  23. LayoutSettings
  24. )
  25. from coderedcms.importexport import convert_csv_to_json, import_pages, ImportPagesFromCSVFileForm
  26. from coderedcms.settings import cr_settings
  27. def search(request):
  28. """
  29. Searches pages across the entire site.
  30. """
  31. search_form = SearchForm(request.GET)
  32. pagetypes = []
  33. results = None
  34. results_paginated = None
  35. if search_form.is_valid():
  36. search_query = search_form.cleaned_data['s']
  37. search_model = search_form.cleaned_data['t']
  38. # get all codered models
  39. pagemodels = sorted(get_page_models(), key=lambda k: k.search_name)
  40. # get filterable models
  41. for model in pagemodels:
  42. if model.search_filterable:
  43. pagetypes.append(model)
  44. # get backend
  45. backend = get_search_backend()
  46. # DB search. Since this backend can't handle inheritance or scoring,
  47. # search specified page types in the desired order and chain the results together.
  48. # This provides better search results than simply searching limited fields on CoderedPage.
  49. db_models = []
  50. if backend.__class__ == db.SearchBackend:
  51. for model in get_page_models():
  52. if model.search_db_include:
  53. db_models.append(model)
  54. db_models = sorted(db_models, reverse=True, key=lambda k: k.search_db_boost)
  55. if backend.__class__ == db.SearchBackend and db_models:
  56. for model in db_models:
  57. # if search_model is provided, only search on that model
  58. if not search_model or search_model == ContentType.objects.get_for_model(model).model: # noqa
  59. curr_results = model.objects.live().search(search_query)
  60. if results:
  61. results = list(chain(results, curr_results))
  62. else:
  63. results = curr_results
  64. # Fallback for any other search backend
  65. else:
  66. if search_model:
  67. try:
  68. model = ContentType.objects.get(model=search_model).model_class()
  69. results = model.objects.live().search(search_query)
  70. except ContentType.DoesNotExist:
  71. results = None
  72. else:
  73. results = CoderedPage.objects.live().order_by('-last_published_at').search(search_query) # noqa
  74. # paginate results
  75. if results:
  76. paginator = Paginator(results, GeneralSettings.for_request(request).search_num_results)
  77. page = request.GET.get('p', 1)
  78. try:
  79. results_paginated = paginator.page(page)
  80. except PageNotAnInteger:
  81. results_paginated = paginator.page(1)
  82. except EmptyPage:
  83. results_paginated = paginator.page(1)
  84. except InvalidPage:
  85. results_paginated = paginator.page(1)
  86. # Log the query so Wagtail can suggest promoted results
  87. Query.get(search_query).add_hit()
  88. # Render template
  89. return render(request, 'coderedcms/pages/search.html', {
  90. 'request': request,
  91. 'pagetypes': pagetypes,
  92. 'form': search_form,
  93. 'results': results,
  94. 'results_paginated': results_paginated
  95. })
  96. @login_required
  97. def serve_protected_file(request, path):
  98. """
  99. Function that serves protected files uploaded from forms.
  100. """
  101. fullpath = os.path.join(cr_settings['PROTECTED_MEDIA_ROOT'], path)
  102. if os.path.isfile(fullpath):
  103. mimetype, encoding = mimetypes.guess_type(fullpath)
  104. with open(fullpath, 'rb') as f:
  105. response = HttpResponse(f.read(), content_type=mimetype)
  106. if encoding:
  107. response["Content-Encoding"] = encoding
  108. return response
  109. raise Http404()
  110. def favicon(request):
  111. icon = LayoutSettings.for_request(request).favicon
  112. if icon:
  113. return HttpResponsePermanentRedirect(icon.get_rendition('original').url)
  114. raise Http404()
  115. def robots(request):
  116. return render(
  117. request,
  118. 'robots.txt',
  119. content_type='text/plain'
  120. )
  121. def event_generate_single_ical_for_event(request):
  122. if request.method == "POST":
  123. event_pk = request.POST['event_pk']
  124. event_page_models = CoderedEventPage.__subclasses__()
  125. dt_start_str = utils.fix_ical_datetime_format(request.POST['datetime_start'])
  126. dt_end_str = utils.fix_ical_datetime_format(request.POST['datetime_end'])
  127. dt_start = datetime.strptime(dt_start_str, "%Y-%m-%dT%H:%M:%S%z") if dt_start_str else None
  128. dt_end = datetime.strptime(dt_end_str, "%Y-%m-%dT%H:%M:%S%z") if dt_end_str else None
  129. for event_page_model in event_page_models:
  130. try:
  131. event = event_page_model.objects.get(pk=event_pk)
  132. break
  133. except event_page_model.DoesNotExist:
  134. pass
  135. ical = Calendar()
  136. ical.add_component(event.create_single_ical(dt_start=dt_start, dt_end=dt_end))
  137. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  138. response['Filename'] = "{0}.ics".format(event.slug)
  139. response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
  140. return response
  141. raise Http404()
  142. def event_generate_recurring_ical_for_event(request):
  143. if request.method == "POST":
  144. event_pk = request.POST['event_pk']
  145. event_page_models = CoderedEventPage.__subclasses__()
  146. for event_page_model in event_page_models:
  147. try:
  148. event = event_page_model.objects.get(pk=event_pk)
  149. break
  150. except event_page_model.DoesNotExist:
  151. pass
  152. ical = Calendar()
  153. for e in event.create_recurring_ical():
  154. ical.add_component(e)
  155. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  156. response['Filename'] = "{0}.ics".format(event.slug)
  157. response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
  158. return response
  159. raise Http404()
  160. def event_generate_ical_for_calendar(request):
  161. if request.method == "POST":
  162. try:
  163. page = CoderedPage.objects.get(id=request.POST.get('page_id')).specific
  164. except ValueError:
  165. raise Http404
  166. ical = Calendar()
  167. for event_page in page.get_index_children():
  168. for e in event_page.specific.create_recurring_ical():
  169. ical.add_component(e)
  170. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  171. response['Filename'] = "calendar.ics"
  172. response['Content-Disposition'] = 'attachment; filename=calendar.ics'
  173. return response
  174. raise Http404()
  175. def event_get_calendar_events(request):
  176. """
  177. JSON list of events compatible with fullcalendar.js
  178. """
  179. try:
  180. page = CoderedPage.objects.get(id=request.GET.get('pid')).specific
  181. except ValueError:
  182. raise Http404
  183. start = None
  184. end = None
  185. start_str = request.GET.get('start', None)
  186. end_str = request.GET.get('end', None)
  187. if start_str:
  188. start = timezone.make_aware(
  189. datetime.strptime(start_str[:10], "%Y-%m-%d"),
  190. )
  191. if end_str:
  192. end = timezone.make_aware(
  193. datetime.strptime(end_str[:10], "%Y-%m-%d"),
  194. )
  195. return JsonResponse(
  196. page.get_calendar_events(start=start, end=end),
  197. safe=False
  198. )
  199. @login_required
  200. def import_pages_from_csv_file(request):
  201. """
  202. Overwrite of the `import_pages` view from wagtailimportexport. By default, the `import_pages`
  203. view expects a json file to be uploaded. This view converts the uploaded csv into the json
  204. format that the importer expects.
  205. """
  206. if request.method == 'POST':
  207. form = ImportPagesFromCSVFileForm(request.POST, request.FILES)
  208. if form.is_valid():
  209. import_data = convert_csv_to_json(
  210. form.cleaned_data['file'].read().decode('utf-8').splitlines(),
  211. form.cleaned_data['page_type']
  212. )
  213. parent_page = form.cleaned_data['parent_page']
  214. try:
  215. page_count = import_pages(import_data, parent_page)
  216. except LookupError as e:
  217. messages.error(request, _(
  218. "Import failed: %(reason)s") % {'reason': e}
  219. )
  220. else:
  221. messages.success(request, ungettext(
  222. "%(count)s page imported.",
  223. "%(count)s pages imported.",
  224. page_count) % {'count': page_count}
  225. )
  226. return redirect('wagtailadmin_explore', parent_page.pk)
  227. else:
  228. form = ImportPagesFromCSVFileForm()
  229. return render(request, 'wagtailimportexport/import_from_csv.html', {
  230. 'form': form,
  231. })