publish_revision.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import logging
  2. from django.conf import settings
  3. from django.core.exceptions import PermissionDenied
  4. from django.utils import timezone
  5. from wagtail.log_actions import log
  6. from wagtail.permission_policies.base import ModelPermissionPolicy
  7. from wagtail.signals import published
  8. logger = logging.getLogger("wagtail")
  9. class PublishPermissionError(PermissionDenied):
  10. """
  11. Raised when the publish cannot be performed due to insufficient permissions.
  12. """
  13. pass
  14. class PublishRevisionAction:
  15. """
  16. Publish or schedule revision for publishing.
  17. :param revision: revision to publish
  18. :param user: the publishing user
  19. :param changed: indicated whether content has changed
  20. :param log_action:
  21. flag for the logging action. Pass False to skip logging. Cannot pass an action string as the method
  22. performs several actions: "publish", "revert" (and publish the reverted revision),
  23. "schedule publishing with a live revision", "schedule revision reversal publishing, with a live revision",
  24. "schedule publishing", "schedule revision reversal publishing"
  25. :param previous_revision: indicates a revision reversal. Should be set to the previous revision instance
  26. """
  27. def __init__(
  28. self, revision, user=None, changed=True, log_action=True, previous_revision=None
  29. ):
  30. self.revision = revision
  31. self.object = self.revision.as_object()
  32. self.permission_policy = ModelPermissionPolicy(type(self.object))
  33. self.user = user
  34. self.changed = changed
  35. self.log_action = log_action
  36. self.previous_revision = previous_revision
  37. def check(self, skip_permission_checks=False):
  38. if (
  39. self.user
  40. and not skip_permission_checks
  41. and not self.permission_policy.user_has_permission(self.user, "publish")
  42. ):
  43. raise PublishPermissionError(
  44. "You do not have permission to publish this object"
  45. )
  46. def log_scheduling_action(self):
  47. log(
  48. instance=self.object,
  49. action="wagtail.publish.schedule",
  50. user=self.user,
  51. data={
  52. "revision": {
  53. "id": self.revision.id,
  54. "created": self.revision.created_at.strftime("%d %b %Y %H:%M"),
  55. "go_live_at": self.object.go_live_at.strftime("%d %b %Y %H:%M"),
  56. "has_live_version": self.object.live,
  57. }
  58. },
  59. revision=self.revision,
  60. content_changed=self.changed,
  61. )
  62. def _after_publish(self):
  63. from wagtail.models import WorkflowMixin
  64. published.send(
  65. sender=type(self.object),
  66. instance=self.object,
  67. revision=self.revision,
  68. )
  69. if isinstance(self.object, WorkflowMixin):
  70. workflow_state = self.object.current_workflow_state
  71. if workflow_state and getattr(
  72. settings, "WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH", True
  73. ):
  74. workflow_state.cancel(user=self.user)
  75. def _publish_revision(
  76. self, revision, object, user, changed, log_action, previous_revision
  77. ):
  78. from wagtail.models import Revision
  79. if object.go_live_at and object.go_live_at > timezone.now():
  80. object.has_unpublished_changes = True
  81. # Instead set the approved_go_live_at of this revision
  82. revision.approved_go_live_at = object.go_live_at
  83. revision.save()
  84. # And clear the approved_go_live_at of any other revisions
  85. object.revisions.exclude(id=revision.id).update(approved_go_live_at=None)
  86. # if we are updating a currently live object skip the rest
  87. if object.live_revision:
  88. # Log scheduled publishing
  89. if log_action:
  90. self.log_scheduling_action()
  91. return
  92. # if we have a go_live in the future don't make the object live
  93. object.live = False
  94. else:
  95. object.live = True
  96. # at this point, the object has unpublished changes if and only if there are newer revisions than this one
  97. object.has_unpublished_changes = not revision.is_latest_revision()
  98. # If object goes live clear the approved_go_live_at of all revisions
  99. object.revisions.update(approved_go_live_at=None)
  100. object.expired = False # When a object is published it can't be expired
  101. # Set first_published_at, last_published_at and live_revision
  102. # if the object is being published now
  103. if object.live:
  104. now = timezone.now()
  105. object.last_published_at = now
  106. object.live_revision = revision
  107. if object.first_published_at is None:
  108. object.first_published_at = now
  109. if previous_revision:
  110. previous_revision_object = previous_revision.as_object()
  111. old_object_title = (
  112. str(previous_revision_object)
  113. if str(object) != str(previous_revision_object)
  114. else None
  115. )
  116. else:
  117. try:
  118. previous = revision.get_previous()
  119. except Revision.DoesNotExist:
  120. previous = None
  121. old_object_title = (
  122. str(previous.content_object)
  123. if previous and str(object) != str(previous.content_object)
  124. else None
  125. )
  126. else:
  127. # Unset live_revision if the object is going live in the future
  128. object.live_revision = None
  129. object.save()
  130. revision.submitted_for_moderation = False
  131. object.revisions.update(submitted_for_moderation=False)
  132. self._after_publish()
  133. if object.live:
  134. if log_action:
  135. data = None
  136. if previous_revision:
  137. data = {
  138. "revision": {
  139. "id": previous_revision.id,
  140. "created": previous_revision.created_at.strftime(
  141. "%d %b %Y %H:%M"
  142. ),
  143. }
  144. }
  145. if old_object_title:
  146. data = data or {}
  147. data["title"] = {
  148. "old": old_object_title,
  149. "new": str(object),
  150. }
  151. log(
  152. instance=object,
  153. action="wagtail.rename",
  154. user=user,
  155. data=data,
  156. revision=revision,
  157. )
  158. log(
  159. instance=object,
  160. action=log_action
  161. if isinstance(log_action, str)
  162. else "wagtail.publish",
  163. user=user,
  164. data=data,
  165. revision=revision,
  166. content_changed=changed,
  167. )
  168. logger.info(
  169. 'Published: "%s" pk=%s revision_id=%d',
  170. str(object),
  171. str(object.pk),
  172. revision.id,
  173. )
  174. elif object.go_live_at:
  175. logger.info(
  176. 'Scheduled for publish: "%s" pk=%s revision_id=%d go_live_at=%s',
  177. str(object),
  178. str(object.pk),
  179. revision.id,
  180. object.go_live_at.isoformat(),
  181. )
  182. if log_action:
  183. self.log_scheduling_action()
  184. def execute(self, skip_permission_checks=False):
  185. self.check(skip_permission_checks=skip_permission_checks)
  186. return self._publish_revision(
  187. self.revision,
  188. self.object,
  189. user=self.user,
  190. changed=self.changed,
  191. log_action=self.log_action,
  192. previous_revision=self.previous_revision,
  193. )