create.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. from urllib.parse import quote, urlencode
  2. from django.conf import settings
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.core.exceptions import PermissionDenied
  5. from django.http import Http404
  6. from django.shortcuts import get_object_or_404, redirect
  7. from django.template.response import TemplateResponse
  8. from django.urls import reverse
  9. from django.utils import timezone
  10. from django.utils.translation import gettext as _
  11. from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
  12. from wagtail.admin import messages, signals
  13. from wagtail.admin.action_menu import PageActionMenu
  14. from wagtail.admin.views.generic import HookResponseMixin
  15. from wagtail.admin.views.pages.utils import get_valid_next_url_from_request
  16. from wagtail.models import Locale, Page, PageSubscription, UserPagePermissionsProxy
  17. def add_subpage(request, parent_page_id):
  18. parent_page = get_object_or_404(Page, id=parent_page_id).specific
  19. if not parent_page.permissions_for_user(request.user).can_add_subpage():
  20. raise PermissionDenied
  21. page_types = [
  22. (
  23. model.get_verbose_name(),
  24. model._meta.app_label,
  25. model._meta.model_name,
  26. model.get_page_description(),
  27. )
  28. for model in type(parent_page).creatable_subpage_models()
  29. if model.can_create_at(parent_page)
  30. ]
  31. # sort by lower-cased version of verbose name
  32. page_types.sort(key=lambda page_type: page_type[0].lower())
  33. if len(page_types) == 1:
  34. # Only one page type is available - redirect straight to the create form rather than
  35. # making the user choose
  36. verbose_name, app_label, model_name, description = page_types[0]
  37. return redirect("wagtailadmin_pages:add", app_label, model_name, parent_page.id)
  38. return TemplateResponse(
  39. request,
  40. "wagtailadmin/pages/add_subpage.html",
  41. {
  42. "parent_page": parent_page,
  43. "page_types": page_types,
  44. "next": get_valid_next_url_from_request(request),
  45. },
  46. )
  47. class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
  48. template_name = "wagtailadmin/pages/create.html"
  49. def dispatch(
  50. self, request, content_type_app_name, content_type_model_name, parent_page_id
  51. ):
  52. self.parent_page = get_object_or_404(Page, id=parent_page_id).specific
  53. self.parent_page_perms = self.parent_page.permissions_for_user(
  54. self.request.user
  55. )
  56. if not self.parent_page_perms.can_add_subpage():
  57. raise PermissionDenied
  58. try:
  59. self.page_content_type = ContentType.objects.get_by_natural_key(
  60. content_type_app_name, content_type_model_name
  61. )
  62. except ContentType.DoesNotExist:
  63. raise Http404
  64. # Get class
  65. self.page_class = self.page_content_type.model_class()
  66. # Make sure the class is a descendant of Page
  67. if not issubclass(self.page_class, Page):
  68. raise Http404
  69. # page must be in the list of allowed subpage types for this parent ID
  70. if self.page_class not in self.parent_page.creatable_subpage_models():
  71. raise PermissionDenied
  72. if not self.page_class.can_create_at(self.parent_page):
  73. raise PermissionDenied
  74. response = self.run_hook(
  75. "before_create_page", self.request, self.parent_page, self.page_class
  76. )
  77. if response:
  78. return response
  79. self.locale = self.parent_page.locale
  80. # If the parent page is the root page. The user may specify any locale they like
  81. if self.parent_page.is_root():
  82. selected_locale = request.GET.get("locale", None) or request.POST.get(
  83. "locale", None
  84. )
  85. if selected_locale:
  86. self.locale = get_object_or_404(Locale, language_code=selected_locale)
  87. self.page = self.page_class(owner=self.request.user)
  88. self.page.locale = self.locale
  89. self.edit_handler = self.page_class.get_edit_handler()
  90. self.edit_handler = self.edit_handler.bind_to(
  91. request=self.request, instance=self.page
  92. )
  93. self.form_class = self.edit_handler.get_form_class()
  94. # Note: Comment notifications should be enabled by default for pages that a user creates
  95. self.subscription = PageSubscription(
  96. page=self.page, user=self.request.user, comment_notifications=True
  97. )
  98. self.next_url = get_valid_next_url_from_request(self.request)
  99. return super().dispatch(request)
  100. def post(self, request):
  101. self.form = self.form_class(
  102. self.request.POST,
  103. self.request.FILES,
  104. instance=self.page,
  105. subscription=self.subscription,
  106. parent_page=self.parent_page,
  107. for_user=self.request.user,
  108. )
  109. if self.form.is_valid():
  110. return self.form_valid(self.form)
  111. else:
  112. return self.form_invalid(self.form)
  113. def form_valid(self, form):
  114. if (
  115. bool(self.request.POST.get("action-publish"))
  116. and self.parent_page_perms.can_publish_subpage()
  117. ):
  118. return self.publish_action()
  119. elif (
  120. bool(self.request.POST.get("action-submit"))
  121. and self.parent_page.has_workflow
  122. ):
  123. return self.submit_action()
  124. else:
  125. return self.save_action()
  126. def get_edit_message_button(self):
  127. return messages.button(
  128. reverse("wagtailadmin_pages:edit", args=(self.page.id,)), _("Edit")
  129. )
  130. def get_view_draft_message_button(self):
  131. return messages.button(
  132. reverse("wagtailadmin_pages:view_draft", args=(self.page.id,)),
  133. _("View draft"),
  134. new_window=False,
  135. )
  136. def get_view_live_message_button(self):
  137. return messages.button(self.page.url, _("View live"), new_window=False)
  138. def save_action(self):
  139. self.page = self.form.save(commit=False)
  140. self.page.live = False
  141. # Save page
  142. self.parent_page.add_child(instance=self.page)
  143. # Save revision
  144. self.page.save_revision(user=self.request.user, log_action=False)
  145. # Save subscription settings
  146. self.subscription.page = self.page
  147. self.subscription.save()
  148. # Notification
  149. messages.success(
  150. self.request,
  151. _("Page '{0}' created.").format(self.page.get_admin_display_title()),
  152. )
  153. response = self.run_hook("after_create_page", self.request, self.page)
  154. if response:
  155. return response
  156. # remain on edit page for further edits
  157. return self.redirect_and_remain()
  158. def publish_action(self):
  159. self.page = self.form.save(commit=False)
  160. # Save page
  161. self.parent_page.add_child(instance=self.page)
  162. # Save revision
  163. revision = self.page.save_revision(user=self.request.user, log_action=False)
  164. # Save subscription settings
  165. self.subscription.page = self.page
  166. self.subscription.save()
  167. # Publish
  168. response = self.run_hook("before_publish_page", self.request, self.page)
  169. if response:
  170. return response
  171. revision.publish(user=self.request.user)
  172. # get a fresh copy so that any changes coming from revision.publish() are passed on
  173. self.page.refresh_from_db()
  174. response = self.run_hook("after_publish_page", self.request, self.page)
  175. if response:
  176. return response
  177. # Notification
  178. if self.page.go_live_at and self.page.go_live_at > timezone.now():
  179. messages.success(
  180. self.request,
  181. _("Page '{0}' created and scheduled for publishing.").format(
  182. self.page.get_admin_display_title()
  183. ),
  184. buttons=[self.get_edit_message_button()],
  185. )
  186. else:
  187. buttons = []
  188. if self.page.url is not None:
  189. buttons.append(self.get_view_live_message_button())
  190. buttons.append(self.get_edit_message_button())
  191. messages.success(
  192. self.request,
  193. _("Page '{0}' created and published.").format(
  194. self.page.get_admin_display_title()
  195. ),
  196. buttons=buttons,
  197. )
  198. response = self.run_hook("after_create_page", self.request, self.page)
  199. if response:
  200. return response
  201. return self.redirect_away()
  202. def submit_action(self):
  203. self.page = self.form.save(commit=False)
  204. self.page.live = False
  205. # Save page
  206. self.parent_page.add_child(instance=self.page)
  207. # Save revision
  208. self.page.save_revision(user=self.request.user, log_action=False)
  209. # Submit
  210. workflow = self.page.get_workflow()
  211. workflow.start(self.page, self.request.user)
  212. # Save subscription settings
  213. self.subscription.page = self.page
  214. self.subscription.save()
  215. # Notification
  216. buttons = []
  217. if self.page.is_previewable():
  218. buttons.append(self.get_view_draft_message_button())
  219. buttons.append(self.get_edit_message_button())
  220. messages.success(
  221. self.request,
  222. _("Page '{0}' created and submitted for moderation.").format(
  223. self.page.get_admin_display_title()
  224. ),
  225. buttons=buttons,
  226. )
  227. response = self.run_hook("after_create_page", self.request, self.page)
  228. if response:
  229. return response
  230. return self.redirect_away()
  231. def redirect_away(self):
  232. if self.next_url:
  233. # redirect back to 'next' url if present
  234. return redirect(self.next_url)
  235. else:
  236. # redirect back to the explorer
  237. return redirect("wagtailadmin_explore", self.page.get_parent().id)
  238. def redirect_and_remain(self):
  239. target_url = reverse("wagtailadmin_pages:edit", args=[self.page.id])
  240. if self.next_url:
  241. # Ensure the 'next' url is passed through again if present
  242. target_url += "?next=%s" % quote(self.next_url)
  243. return redirect(target_url)
  244. def form_invalid(self, form):
  245. messages.validation_error(
  246. self.request,
  247. _("The page could not be created due to validation errors"),
  248. self.form,
  249. )
  250. self.has_unsaved_changes = True
  251. self.edit_handler = self.edit_handler.bind_to(form=self.form)
  252. return self.render_to_response(self.get_context_data())
  253. def get(self, request):
  254. signals.init_new_page.send(
  255. sender=CreateView, page=self.page, parent=self.parent_page
  256. )
  257. self.form = self.form_class(
  258. instance=self.page,
  259. subscription=self.subscription,
  260. parent_page=self.parent_page,
  261. for_user=self.request.user,
  262. )
  263. self.has_unsaved_changes = False
  264. self.edit_handler = self.edit_handler.bind_to(form=self.form)
  265. return self.render_to_response(self.get_context_data())
  266. def get_context_data(self, **kwargs):
  267. context = super().get_context_data(**kwargs)
  268. context.update(
  269. {
  270. "content_type": self.page_content_type,
  271. "page_class": self.page_class,
  272. "parent_page": self.parent_page,
  273. "edit_handler": self.edit_handler,
  274. "action_menu": PageActionMenu(
  275. self.request, view="create", parent_page=self.parent_page
  276. ),
  277. "preview_modes": self.page.preview_modes,
  278. "form": self.form,
  279. "next": self.next_url,
  280. "has_unsaved_changes": self.has_unsaved_changes,
  281. "locale": None,
  282. "translations": [],
  283. }
  284. )
  285. if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
  286. # Pages can be created in any language at the root level
  287. if self.parent_page.is_root():
  288. translations = [
  289. {
  290. "locale": locale,
  291. "url": reverse(
  292. "wagtailadmin_pages:add",
  293. args=[
  294. self.page_content_type.app_label,
  295. self.page_content_type.model,
  296. self.parent_page.id,
  297. ],
  298. )
  299. + "?"
  300. + urlencode({"locale": locale.language_code}),
  301. }
  302. for locale in Locale.objects.all()
  303. ]
  304. else:
  305. user_perms = UserPagePermissionsProxy(self.request.user)
  306. translations = [
  307. {
  308. "locale": translation.locale,
  309. "url": reverse(
  310. "wagtailadmin_pages:add",
  311. args=[
  312. self.page_content_type.app_label,
  313. self.page_content_type.model,
  314. translation.id,
  315. ],
  316. ),
  317. }
  318. for translation in self.parent_page.get_translations()
  319. .only("id", "locale")
  320. .select_related("locale")
  321. if user_perms.for_page(translation).can_add_subpage()
  322. and self.page_class
  323. in translation.specific_class.creatable_subpage_models()
  324. and self.page_class.can_create_at(translation)
  325. ]
  326. context.update(
  327. {
  328. "locale": self.locale,
  329. "translations": translations,
  330. }
  331. )
  332. return context