views.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import mimetypes
  2. import os
  3. from datetime import datetime
  4. from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect, JsonResponse
  5. from django.contrib.auth.decorators import login_required
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.core.paginator import Paginator, InvalidPage, EmptyPage, PageNotAnInteger
  8. from django.shortcuts import redirect, render
  9. from django.utils import timezone
  10. from django.utils.translation import ngettext, gettext_lazy as _
  11. from django.views.decorators.http import require_POST
  12. from icalendar import Calendar
  13. from wagtail.admin import messages
  14. from wagtail.models import Page, get_page_models
  15. from wagtailcrx import utils
  16. from wagtailcrx.forms import SearchForm
  17. from wagtailcrx.models import (
  18. CoderedPage,
  19. GeneralSettings,
  20. LayoutSettings
  21. )
  22. from wagtailcrx.importexport import convert_csv_to_json, import_pages, ImportPagesFromCSVFileForm
  23. from wagtailcrx.settings import crx_settings
  24. from wagtailcrx.templatetags.wagtailcrx_tags import get_name_of_class
  25. def search(request):
  26. """
  27. Searches pages across the entire site.
  28. """
  29. search_form = SearchForm(request.GET)
  30. pagetypes = []
  31. results = None
  32. results_paginated = None
  33. if search_form.is_valid():
  34. search_query = search_form.cleaned_data['s']
  35. search_model = search_form.cleaned_data['t']
  36. # get all page models
  37. pagemodels = sorted(get_page_models(), key=get_name_of_class)
  38. # filter based on is search_filterable
  39. for model in pagemodels:
  40. if hasattr(model, "search_filterable") and model.search_filterable:
  41. pagetypes.append(model)
  42. results = Page.objects.live()
  43. if search_model:
  44. try:
  45. # If provided a model name, try to get it
  46. model = ContentType.objects.get(model=search_model).model_class()
  47. results = results.type(model)
  48. except ContentType.DoesNotExist:
  49. # Maintain existing behavior of only returning objects if the page type is real
  50. results = None
  51. # get and paginate results
  52. if results:
  53. results = results.search(search_query)
  54. paginator = Paginator(results, GeneralSettings.for_request(request).search_num_results)
  55. page = request.GET.get('p', 1)
  56. try:
  57. results_paginated = paginator.page(page)
  58. except PageNotAnInteger:
  59. results_paginated = paginator.page(1)
  60. except EmptyPage:
  61. results_paginated = paginator.page(1)
  62. except InvalidPage:
  63. results_paginated = paginator.page(1)
  64. # Render template
  65. return render(request, 'wagtailcrx/pages/search.html', {
  66. 'request': request,
  67. 'pagetypes': pagetypes,
  68. 'form': search_form,
  69. 'results': results,
  70. 'results_paginated': results_paginated
  71. })
  72. @login_required
  73. def serve_protected_file(request, path):
  74. """
  75. Function that serves protected files uploaded from forms.
  76. """
  77. # Fully resolve all provided paths.
  78. mediapath = os.path.abspath(crx_settings.CRX_PROTECTED_MEDIA_ROOT)
  79. fullpath = os.path.abspath(os.path.join(mediapath, path))
  80. # Path must be a sub-path of the PROTECTED_MEDIA_ROOT, and exist.
  81. if fullpath.startswith(mediapath) and os.path.isfile(fullpath):
  82. mimetype, encoding = mimetypes.guess_type(fullpath)
  83. with open(fullpath, 'rb') as f:
  84. response = HttpResponse(f.read(), content_type=mimetype)
  85. if encoding:
  86. response["Content-Encoding"] = encoding
  87. return response
  88. raise Http404()
  89. def favicon(request):
  90. icon = LayoutSettings.for_request(request).favicon
  91. if icon:
  92. return HttpResponsePermanentRedirect(icon.get_rendition('original').url)
  93. raise Http404()
  94. def robots(request):
  95. return render(
  96. request,
  97. 'robots.txt',
  98. content_type='text/plain'
  99. )
  100. @require_POST
  101. def event_generate_single_ical_for_event(request):
  102. # Parse input.
  103. try:
  104. event_pk = request.POST['event_pk']
  105. except KeyError:
  106. return HttpResponse("event_pk required", status=400)
  107. try:
  108. dt_start_str = utils.fix_ical_datetime_format(request.POST['datetime_start'])
  109. dt_end_str = utils.fix_ical_datetime_format(request.POST['datetime_end'])
  110. dt_start = None
  111. dt_end = None
  112. if dt_start_str:
  113. dt_start = datetime.strptime(dt_start_str, "%Y-%m-%dT%H:%M:%S%z")
  114. if dt_end_str:
  115. dt_end = datetime.strptime(dt_end_str, "%Y-%m-%dT%H:%M:%S%z")
  116. except KeyError:
  117. return HttpResponse(
  118. "datetime_start and datetime_end required.",
  119. status=400,
  120. )
  121. except ValueError:
  122. return HttpResponse(
  123. "datetime_start and datetime_end must be valid datetimes.",
  124. status=400,
  125. )
  126. # Get the page.
  127. try:
  128. event = CoderedPage.objects.get(pk=event_pk).specific
  129. except (CoderedPage.DoesNotExist, ValueError):
  130. raise Http404("Event does not exist")
  131. # Generate the ical file.
  132. ical = Calendar()
  133. ical.add('prodid', '-//Wagtail CRX//')
  134. ical.add('version', '2.0')
  135. ical.add_component(event.create_single_ical(dt_start=dt_start, dt_end=dt_end))
  136. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  137. response['Filename'] = "{0}.ics".format(event.slug)
  138. response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
  139. return response
  140. @require_POST
  141. def event_generate_recurring_ical_for_event(request):
  142. # Parse input.
  143. try:
  144. event_pk = request.POST['event_pk']
  145. except KeyError:
  146. return HttpResponse("event_pk required", status=400)
  147. # Get the page.
  148. try:
  149. event = CoderedPage.objects.get(pk=event_pk).specific
  150. except (CoderedPage.DoesNotExist, ValueError):
  151. raise Http404("Event does not exist")
  152. # Generate the ical file.
  153. ical = Calendar()
  154. ical.add('prodid', '-//Wagtail CRX//')
  155. ical.add('version', '2.0')
  156. for e in event.create_recurring_ical():
  157. ical.add_component(e)
  158. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  159. response['Filename'] = "{0}.ics".format(event.slug)
  160. response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
  161. return response
  162. @require_POST
  163. def event_generate_ical_for_calendar(request):
  164. # Parse input.
  165. try:
  166. page_id = request.POST["page_id"]
  167. except KeyError:
  168. return HttpResponse("page_id required", status=400)
  169. # Get the page.
  170. try:
  171. page = CoderedPage.objects.get(pk=page_id).specific
  172. except (CoderedPage.DoesNotExist, ValueError):
  173. raise Http404("Page does not exist")
  174. # Generate the ical file.
  175. ical = Calendar()
  176. ical.add('prodid', '-//Wagtail CRX//')
  177. ical.add('version', '2.0')
  178. for event_page in page.get_index_children():
  179. for e in event_page.specific.create_recurring_ical():
  180. ical.add_component(e)
  181. response = HttpResponse(ical.to_ical(), content_type="text/calendar")
  182. response['Filename'] = "calendar.ics"
  183. response['Content-Disposition'] = 'attachment; filename=calendar.ics'
  184. return response
  185. def event_get_calendar_events(request):
  186. """
  187. JSON list of events compatible with fullcalendar.js
  188. """
  189. # Parse input.
  190. try:
  191. page_id = request.GET["pid"]
  192. except KeyError:
  193. return HttpResponse("pid required", status=400)
  194. start = None
  195. end = None
  196. start_str = request.GET.get('start', None)
  197. end_str = request.GET.get('end', None)
  198. try:
  199. if start_str:
  200. start = timezone.make_aware(
  201. datetime.strptime(start_str[:10], "%Y-%m-%d"),
  202. )
  203. if end_str:
  204. end = timezone.make_aware(
  205. datetime.strptime(end_str[:10], "%Y-%m-%d"),
  206. )
  207. except ValueError:
  208. return HttpResponse(
  209. "start and end must be valid datetimes.",
  210. status=400
  211. )
  212. # Get the page.
  213. try:
  214. page = CoderedPage.objects.get(pk=page_id).specific
  215. except (CoderedPage.DoesNotExist, ValueError):
  216. raise Http404("Page does not exist")
  217. return JsonResponse(
  218. page.get_calendar_events(start=start, end=end),
  219. safe=False
  220. )
  221. @login_required
  222. def import_index(request):
  223. """
  224. Landing page to replace wagtailimportexport.
  225. """
  226. return render(request, 'wagtailimportexport/index.html')
  227. @login_required
  228. def import_pages_from_csv_file(request):
  229. """
  230. Overwrite of the `import_pages` view from wagtailimportexport. By default, the `import_pages`
  231. view expects a json file to be uploaded. This view converts the uploaded csv into the json
  232. format that the importer expects.
  233. """
  234. if request.method == 'POST':
  235. form = ImportPagesFromCSVFileForm(request.POST, request.FILES)
  236. if form.is_valid():
  237. import_data = convert_csv_to_json(
  238. form.cleaned_data['file'].read().decode('utf-8').splitlines(),
  239. form.cleaned_data['page_type']
  240. )
  241. parent_page = form.cleaned_data['parent_page']
  242. try:
  243. page_count = import_pages(import_data, parent_page)
  244. except LookupError as e:
  245. messages.error(request, _(
  246. "Import failed: %(reason)s") % {'reason': e}
  247. )
  248. else:
  249. messages.success(request, ngettext(
  250. "%(count)s page imported.",
  251. "%(count)s pages imported.",
  252. page_count) % {'count': page_count}
  253. )
  254. return redirect('wagtailadmin_explore', parent_page.pk)
  255. else:
  256. form = ImportPagesFromCSVFileForm()
  257. return render(request, 'wagtailimportexport/import_from_csv.html', {
  258. 'form': form,
  259. })