a
    Dg                    @  s  d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ ddlmZ ddlmZ dd	lmZmZ dd
lmZmZ ddlmZ ddlmZ ddlmZmZmZmZ ddl m!Z! ddl"m#Z# ddl$m%Z% ddl&m'Z'm(Z( ddl)m*Z*m+Z+ ddl,m-Z-m.Z. ddl/m0Z0m1Z1 ddl2m3Z3 ddl4m5Z5m6Z6m7Z7m8Z8 ddl9m:Z: ddl;m<Z< ddl=m>Z>m?Z? ddl@mAZA ddl@mBZB ddlCmDZD ddlEmFZFmGZG ddlHmIZImJZJ ddlKmLZL dd lMmNZNmOZO dd!lPmQZR dd"lSmTZT dd#lUmVZVmWZWmXZX dd$lYmZZZ dd%l[m\Z\ dd&l]m^Z^ dd'l_m`Z` dd(lambZb dd)lcmdZd dd*lemfZf dd+lgmhZh dd,limjZj dd-lkmlZl dd.lmmnZn dd/lompZpmqZqmrZrmsZsmtZtmuZu dd0lvmwZw dd1lxmyZy dd2lzm{Z{m|Z|m}Z} dd3l~mZ dd4lmZmZ dd5lmZ dd6lmZmZmZmZmZmZmZmZmZmZmZ dd7lmZ dd8lmZ dd9lmZ d:d;lmZmZmZmZ d:d<lmZmZmZ d:d=lmZmZmZmZmZmZmZ d:d>lmZmZmZmZmZmZmZmZmZ d:d?lmZmZ d:d@lmZ d:dAlmZmZmZ d:dBlmZ d:dClmZ edDZdEZeedFdGZe3eedHdIdJ Zg ZdKdL ZddNdOZdPdQ ZejdRdS ZG dTdU dUe'j˃Ze̠eZG dVdW dWe'jjЃZG dXdY dYe'j҃ZG dZd[ d[e'j҃ZG d\d] d]ZG d^d_ d_e'j҃ZG d`da daZG dbdc dceeeeeeeeZ
ZddeRdeeRdffdgeRdheRdifdjeRdkeRdlfdmeRdneRdofdpeRdqeRdrfdseRdteRdufgZdvdw eD Zdxdw eD ZG dydz dzeejeVed{ZG d|d} d}e'j҃ZG d~d de'j߃ZG dd de'jˠeZG dd deZG dd de'j҃ZG dd de'j˃ZG dd de'j҃ZG dd dZG dd deZG dd de'j҃ZG dd de'j҃ZG dd deރZG dd dee'j߃Ze'jˠeZG dd dee'j҃ZG dd de'j˃ZG dd deVZG dd deZG dd deZG dd deZG dd de'j߃Ze'jˠeZG dd de'j҃ZG dd de'j˃ZG dd dee'j߃ZeeZG dd dee'j҃ZG dd deZG dd deZG dd deZG dd deVZG dd de'j҃ZG dd de'j҃ZdS )a  
``wagtail.models`` is split into submodules for maintainability. All definitions intended as
public should be imported here (with 'noqa: F401' comments as required) and outside code should
continue to import them from wagtail.models (e.g. ``from wagtail.models import Site``, not
``from wagtail.models.sites import Site``.)

Submodules should take care to keep the direction of dependencies consistent; where possible they
should implement low-level generic functionality which is then imported by higher-level models such
as Page.
    )annotationsN)StringIO)urlsplit)warn)forms)settings)Group
Permission)GenericForeignKeyGenericRelation)ContentType)checks)FieldDoesNotExistImproperlyConfiguredPermissionDeniedValidationError)BaseHandler)WSGIRequest)DjangoJSONEncoder)modelstransaction)QValue)OuterRefSubquery)ConcatSubstr)receiver)Http404HttpRequestHttpResponseHttpResponseNotAllowed)validate_host)TemplateResponse)NoReverseMatchreverse)timezone)translation)patch_cache_control)force_bytes	force_str)Promisecached_property)import_string)capfirstslugify)gettext_lazy)ParentalKey)ClusterableModel get_serializable_data_for_fieldsmodel_from_serializable_data)MP_Node)CopyPageForTranslationAction)CopyPageAction)CreatePageAliasAction)DeletePageAction)MovePageAction)PublishPageRevisionAction)PublishRevisionAction)UnpublishAction)UnpublishPageAction)
HTTPMethod)WAGTAIL_APPEND_SLASHcamelcase_to_underscoreget_content_type_label&get_supported_content_language_variantresolve_model_stringsafe_md5)StreamFieldTaskStateCommentForm)	BasicLockScheduledForPublishLockWorkflowLock)log)PageQuerySetSpecificQuerySetMixin)index)page_publishedpage_slug_changedpre_validate_deletetask_approvedtask_cancelledtask_rejectedtask_submittedworkflow_approvedworkflow_cancelledworkflow_rejectedworkflow_submitted)RouteResult)RemovedInWagtail70Warning)
ensure_utc   )BaseLogEntryBaseLogEntryManagerLogEntryQuerySetModelLogEntry)_copy_copy_m2m_relations_extract_field_data)BootstrapTranslatableMixinBootstrapTranslatableModelLocaleLocaleManagerTranslatableMixinbootstrap_translatable_modelget_translatable_models)	BaseCollectionManager
CollectionCollectionManagerCollectionMemberCollectionViewRestrictionGroupCollectionPermission GroupCollectionPermissionManagerUploadedFileget_root_collection_id)CommentPanelPlaceholderPanelPlaceholder)ReferenceIndex)SiteSiteManagerSiteRootPath)SpecificMixin)BaseViewRestrictionZwagtailpageZWAGTAIL_COMMENTS_RELATION_NAMEZwagtail_admin_comments)senderc              	   K  sr   t jjd|d}| rnzt }||k}W n tjtfyH   d}Y n0 |sbtjj	|j
d }|j|d d S )Nr^   )depthlocaleFpk)r   )Pageobjectsfilterexistsrh   get_defaultDoesNotExistLookupErrorZall_objectsexcluder   firstupdate)r   instancekwargsZroot_page_with_this_localeZ
new_localeZdefault_locale_is_ok r   V/var/www/lab.imftr.de/x/nb_venv/lib/python3.9/site-packages/wagtail/models/__init__.py#reassign_root_page_locale_on_delete   s    
r   c                   C  s   t  S )zX
    Returns a list of all non-abstract Page model classes defined in this project.
    )PAGE_MODEL_CLASSEScopyr   r   r   r   get_page_models   s    r   Tc                 C  sB   t  }| s|t dd tjj|  D }tjj|ddS )z\
    Returns a queryset of all ContentType objects corresponding to Page model classes.
    c                 S  s   g | ]
}|j qS r   r   ).0ctr   r   r   
<listcomp>   s   z*get_page_content_types.<locals>.<listcomp>)Zpk__inmodel)	r   remover   r   r   Zget_for_modelsvaluesr   order_by)Zinclude_base_page_typer   Zcontent_type_idsr   r   r   get_page_content_types   s    
r   c                   C  s   t jtS )zi
    Returns the content type to use as a default for pages whose content type
    has been deleted.
    )r   r   get_for_modelr   r   r   r   r   get_default_page_content_type   s    r   c                 C  s   t dd | jjD S )Nc                 s  s   | ]}t |tr|jV  qd S N)
isinstancerF   namer   fieldr   r   r   	<genexpr>   s   
z(get_streamfield_names.<locals>.<genexpr>)tuple_metaZconcrete_fields)Zmodel_classr   r   r   get_streamfield_names   s    r   c                   @  s&   e Zd Zdd Zd	ddZdd ZdS )
BasePageManagerc                 C  s   |  | jdS )Npath)Z_queryset_classr   r   selfr   r   r   get_queryset   s    zBasePageManager.get_querysetFc                   s   |s|r j d j  S |r6tdd |D }nt fdd|D }t|}t| j j }|dkr||d|  }|dkr|r j d j  S  j|d	S )
z
        This is similar to ``PageQuerySet.first_common_ancestor`` but works
        for a list of pages instead of a queryset.
        z#Can not find ancestor of empty listc                 S  s   h | ]
}|j qS r   r   r   r~   r   r   r   	<setcomp>       z;BasePageManager.first_common_ancestor_of.<locals>.<setcomp>c                   s    h | ]}|j d  jj  qS r   )r   r   steplenr   r   r   r   r      r   r   N zNo common ancestor found!r   )	r   r   get_first_root_nodelist	posixpathcommonprefixlenr   get)r   pagesZinclude_selfstrictpathsZcommon_parent_pathZextra_charsr   r   r   first_common_ancestor_of   s     


z(BasePageManager.first_common_ancestor_ofc                 C  sT   dd |D }dd t jj|djddD }|D ]}|t |j}||_q2dS )	a  
        Annotates each page with its parent page. This is implemented as a
        manager-only method instead of a QuerySet method so it can be used with
        search results.

        If given a QuerySet, this method will evaluate it. Only use this method
        when you are ready to consume the queryset, e.g. after pagination has
        been applied. This is typically done in the view's ``get_context_data``
        using ``context["object_list"]``.

        This method does not return a new queryset, but modifies the existing one,
        to ensure any references to the queryset in the view's context are updated
        (e.g. when using ``context_object_name``).
        c                 S  s   h | ]}t |jqS r   )r   _get_parent_path_from_pathr   r   r   r   r   r      s   z7BasePageManager.annotate_parent_page.<locals>.<setcomp>c                 S  s   i | ]}|j |qS r   r   r   r   r   r   
<dictcomp>   s   z8BasePageManager.annotate_parent_page.<locals>.<dictcomp>)Zpath__inT)deferN)r   r   r   specificr   r   r   Z_parent_page)r   r   Zparent_page_pathsZparent_pages_by_pathr~   parent_pager   r   r   annotate_parent_page   s    
z$BasePageManager.annotate_parent_pageN)FF)__name__
__module____qualname__r   r   r   r   r   r   r   r      s   
r   c                      s    e Zd ZdZ fddZ  ZS )PageBasezMetaclass for Pagec                   sr   t  ||| d|vr.d| jjt|| _d|vr<d | _d | _d | _	d|vr\| jj
 | _| jj
snt|  d S )Ntemplatez
{}/{}.htmlajax_templateis_creatable)super__init__formatr   	app_labelrA   r   r   _clean_subpage_models_clean_parent_page_modelsabstractr   r   append)clsr   basesdct	__class__r   r   r     s     zPageBase.__init__)r   r   r   __doc__r   __classcell__r   r   r   r   r     s   r   c                	      s   e Zd ZdZejddedejddddZdgZ	e
d	d
 Zdd Zdd Zdd Zdd Z fddZed  fdd	Zdd Zd!ddZd"ddZG dd dZ  ZS )#RevisionMixinz.A mixin that allows a model to have revisions.wagtailcore.Revision+zlatest revisionTFrelated_nameverbose_name	on_deletenullblankeditablelatest_revisionc                 C  s   t jj|  | jdS )a  
        Returns revisions that belong to the object.

        Subclasses should define a
        :class:`~django.contrib.contenttypes.fields.GenericRelation` to
        :class:`~wagtail.models.Revision` and override this property to return
        that ``GenericRelation``. This allows subclasses to customize the
        ``related_query_name`` of the ``GenericRelation`` and add custom logic
        (e.g. to always use the specific instance in ``Page``).
        content_type	object_id)Revisionr   r   get_content_typer   r   r   r   r   	revisionsB  s    zRevisionMixin.revisionsc                 C  s2   | j  }|r"tjj|d ddS tjj| ddS )NFfor_concrete_model)r   get_parent_listr   r   r   )r   parentsr   r   r   get_base_content_typeS  s    
z#RevisionMixin.get_base_content_typec                 C  s   t jj| ddS )NFr   )r   r   r   r   r   r   r   r   _  s    zRevisionMixin.get_content_typec                 C  s   | j S r   )r   r   r   r   r   get_latest_revisionb  s    z!RevisionMixin.get_latest_revisionc                 C  s   |   }|r| S | S )z
        Returns the latest revision of the object as an instance of the model.
        If no latest revision exists, returns the object itself.
        )r   	as_objectr   r   r   r   r   get_latest_revision_as_objecte  s    z+RevisionMixin.get_latest_revision_as_objectc                   s,   zt   W S  ty&   t|  Y S 0 d S r   )r   serializable_dataAttributeErrorr3   r   r   r   r   r   o  s    zRevisionMixin.serializable_datac              	     s:   zt  |||W S  ty4   t| |||d Y S 0 d S )N)	check_fks
strict_fks)r   from_serializable_datar   r4   )r   datar   r   r   r   r   r   u  s    z$RevisionMixin.from_serializable_datac                 C  s8   |  |}| j|_| j|_t| tr4| j|_| j|_|S )a  
        Returns a new version of the object with field values updated to reflect changes
        in the provided ``content`` (which usually comes from a previously-saved revision).

        Certain field values are preserved in order to prevent errors if the returned
        object is saved, such as ``id``. The following field values are also preserved,
        as they are considered to be meaningful to the object as a whole, rather than
        to a specific revision:

        * ``latest_revision``

        If :class:`~wagtail.models.TranslatableMixin` is applied, the following field values
        are also preserved:

        * ``translation_key``
        * ``locale``
        )r   r   r   r   rj   translation_keyr   r   contentobjr   r   r   with_content_json~  s    

zRevisionMixin.with_content_jsonc                 C  s   || _ | jdgd d S )Nr   update_fields)r   save)r   revisionchangedr   r   r   _update_from_revision  s    z#RevisionMixin._update_from_revisionNc                 C  s   |r|    tjj| |  |||  t| d}| || t	dt| | j
|j |r|s~t| t|trn|nd|||d n4t| t|tr|nd|d|jt|jdi||d |S )	a8  
        Creates and saves a revision.

        :param user: The user performing the action.
        :param approved_go_live_at: The date and time the revision is approved to go live.
        :param changed: Indicates whether there were any content changes.
        :param log_action: Flag for logging the action. Pass ``True`` to also create a log entry. Can be passed an action string.
            Defaults to ``"wagtail.edit"`` when no ``previous_revision`` param is passed, otherwise ``"wagtail.revert"``.
        :param previous_revision: Indicates a revision reversal. Should be set to the previous revision instance.
        :type previous_revision: Revision
        :param clean: Set this to ``False`` to skip cleaning object content before saving this revision.
        :return: The newly created revision.
        content_objectbase_content_typeuserapproved_go_live_atr   
object_strz!Edited: "%s" pk=%d revision_id=%dwagtail.editr   actionr	  r  content_changedwagtail.revertr  idcreatedr   r  r	  r   r  r  )
full_cleanr   r   creater   r   strr  loggerinfor   r  rL   r   r]   
created_at)r   r	  r
  r  
log_actionprevious_revisioncleanr  r   r   r   save_revision  sP    	
zRevisionMixin.save_revisionc                   @  s   e Zd ZdZdS )zRevisionMixin.MetaTNr   r   r   r   r   r   r   r   Meta  s   r   )TF)T)NNTFNT)r   r   r   r   r   
ForeignKey_SET_NULLr   default_exclude_fields_in_copypropertyr   r   r   r   r   r   classmethodr   r   r  r  r   r   r   r   r   r   r   0  s>   

!
      
Er   c                	      sP  e Zd ZejeddddZejeddddZejedddddZ	ejedddd	Z
ejd
dedejddddZejeddddZejeddddZejeddddZG dd dZe fddZedd Zedd Zedd Zd-ddZd.dd Z fd!d"Zd#d$ Zed%d& Zd'd( Zd/d)d*Z fd+d,Z   Z!S )0DraftStateMixinliveTFr   defaultr   zhas unpublished changeszfirst published at)r   r   r   db_indexzlast published atr   r   r   r   r   zlive revisionr   zgo live date/timer   r   r   zexpiry date/timeexpiredc                   @  s   e Zd ZdZdS )zDraftStateMixin.MetaTNr  r   r   r   r   r     s   r   c                   s   g t  jf i ||  S r   r   check_check_revision_mixinr   r   r   r   r   r0    s
    zDraftStateMixin.checkc                 C  sX   |   }tjdd| dd}z |t|tk r8|gW S W n tyR   |g Y S 0 g S )NzKDraftStateMixin requires RevisionMixin to be applied after DraftStateMixin.zDAdd RevisionMixin to the model's base classes after DraftStateMixin.zwagtailcore.E004hintr   r  )mror   ErrorrO   r   r'  
ValueErrorr   r5  errorr   r   r   r1    s    z%DraftStateMixin._check_revision_mixinc                 C  s
   | j d uS r   )scheduled_revisionr   r   r   r   approved_schedule(  s    z!DraftStateMixin.approved_schedulec                 C  sT   | j s,| jrtdS | jr"tdS tdS n$| jr:tdS | jrHtdS tdS d S )Nr.  	scheduleddraftlive + scheduledlive + draftr(  )r(  r.  r"  r;  has_unpublished_changesr   r   r   r   status_string,  s    
zDraftStateMixin.status_stringNc                 C  s   t |||||dj|dS )a  
        Publish a revision of the object by applying the changes in the revision to the live object.

        :param revision: Revision to publish.
        :type revision: Revision
        :param user: The publishing user.
        :param changed: Indicated whether content has changed.
        :param log_action: Flag for the logging action, pass ``False`` to skip logging.
        :param previous_revision: Indicates a revision reversal. Should be set to the previous revision instance.
        :type previous_revision: Revision
        r	  r  r  r  skip_permission_checks)r<   executer   r  r	  r  r  r  rD  r   r   r   publish=  s    zDraftStateMixin.publishc                 C  s   t | ||||d S )a$  
        Unpublish the live object.

        :param set_expired: Mark the object as expired.
        :param commit: Commit the changes to the database.
        :param user: The unpublishing user.
        :param log_action: Flag for the logging action, pass ``False`` to skip logging.
        set_expiredcommitr	  r  )r=   rE  r   rI  rJ  r	  r  r   r   r   	unpublishY  s    	zDraftStateMixin.unpublishc                   s(   t  |}| j|_| j|_| j|_|S )z
        Similar to :meth:`RevisionMixin.with_content_json`,
        but with the following fields also preserved:

        * ``live``
        * ``has_unpublished_changes``
        * ``first_published_at``
        )r   r   r(  r@  first_published_atr   r   r   r   r   j  s
    	z!DraftStateMixin.with_content_jsonc                 C  s&   | j s
| S |  }|r| S | S d S r   )r@  r   r   r   r   r   r   r   }  s    z-DraftStateMixin.get_latest_revision_as_objectc                 C  s   | j jdd S )NF)Zapproved_go_live_at__isnull)r   r   r   r   r   r   r   r:    s    z"DraftStateMixin.scheduled_revisionc                 C  s   | j }|o| S r   )r:  r   )r   r:  r   r   r    get_scheduled_revision_as_object  s    z0DraftStateMixin.get_scheduled_revision_as_objectc                 C  s0   dg}|| _ |r d| _|d | j|d d S )Nr   Tr@  r   )r   r@  r   r  )r   r  r  r  r   r   r   r    s    
z%DraftStateMixin._update_from_revisionc                   s   | j rt| S t  S r   )r;  rJ   r   get_lockr   r   r   r   rO    s    zDraftStateMixin.get_lock)NTTNF)FTNT)T)"r   r   r   r   BooleanFieldr"  r(  r@  DateTimeFieldrM  last_published_atr!  r#  Zlive_revision
go_live_atZ	expire_atr.  r   r&  r0  r1  r%  r;  rA  rG  rL  r   r   r,   r:  rN  r  rO  r   r   r   r   r   r'    sf   








     




r'  c                   @  s   e Zd ZdZd+ddZdd Zd,ddZd-d	d
Zdd Ze	eZ
dedfgZdddedddddedddddeddgZe	dd Ze	dd Ze	dd  Ze	d!d" Zd#d$ Zd%d& Zd'd( Zd)d* ZdS ).PreviewableMixinz-A mixin that allows a model to have previews.Nc           
        sl   |  |}t|}d|_|r:| D ]\}}t||| q$|  G  fdddt}| }	|	  |	|S )a  
        Simulate a request to this object, by constructing a fake HttpRequest object that is (as far
        as possible) representative of a real request to this object's front-end URL, and invoking
        serve_preview with that request (and the given preview_mode).

        Used for previewing / moderation and any other place where we
        want to display a view of this object in the admin interface without going through the regular
        page routing logic.

        If you pass in a real request object as original_request, additional information (e.g. client IP, cookies)
        will be included in the dummy request.
        Tc                      s   e Zd Z fddZdS )z6PreviewableMixin.make_preview_request.<locals>.Handlerc                   sD   d|_ |_ |}t|dr4t|jr4| }t|dd |S )NTrender)private)
is_previewpreview_modeserve_previewhasattrcallablerU  r(   )r   requestresponser   rX  r   r   _get_response  s    zDPreviewableMixin.make_preview_request.<locals>.Handler._get_responseN)r   r   r   r_  r   r^  r   r   Handler  s   r`  )_get_dummy_headersr   Zis_dummyitemssetattrr   Zload_middlewareZget_response)
r   original_requestrX  Zextra_request_attrsZ
dummy_metar\  kvr`  handlerr   r^  r   make_preview_request  s    
z%PreviewableMixin.make_preview_requestc                 C  s:   zt jd }W n ty"   Y dS 0 |dkr0dS |dS )z
        Return a hostname that can be used on preview requests when the object has no
        routable URL, or the real hostname is not valid according to ALLOWED_HOSTS.
        r   	localhost*.)r   ALLOWED_HOSTS
IndexErrorlstrip)r   hostnamer   r   r   _get_fallback_hostname  s    z'PreviewableMixin._get_fallback_hostnamec                 C  s  |  |}|r^t|}|j}t|tjp,g ds8|  }|j}|jpT|j	dkrRdnd}|j	}n|  }d}d}d}|}||dkrdndkr| d| }d|||d	|d
t
 t
 |dddd}	g d}
tjr|
tjd  |r|
D ]}||jv r|j| |	|< q|	S )z
        Return a dict of META information to be included in a faked HttpRequest object to pass to
        serve_preview.
        )z
.localhostz	127.0.0.1z[::1]httpsi  P   /http:GETzHTTP/1.1)r^   r   TF)REQUEST_METHODZ	PATH_INFOZSERVER_NAMEZSERVER_PORTZSERVER_PROTOCOLZ	HTTP_HOSTwsgi.versionz
wsgi.inputzwsgi.errorszwsgi.url_schemewsgi.multithreadwsgi.multiprocesswsgi.run_once)	ZREMOTE_ADDRZHTTP_X_FORWARDED_FORZHTTP_COOKIEZHTTP_USER_AGENTZHTTP_AUTHORIZATIONrx  ry  rz  r{  r   )_get_dummy_header_urlr   ro  r"   r   rl  rp  r   portschemer   ZSECURE_PROXY_SSL_HEADERr   ZMETA)r   rd  urlZurl_inforo  r   r}  r~  Z	http_hostZdummy_valuesZHEADERS_FROM_ORIGINAL_REQUESTheaderr   r   r   ra    sR    

z#PreviewableMixin._get_dummy_headersc                 C  s   | j S )z|
        Return the URL that _get_dummy_headers() should use to set META headers
        for the faked HttpRequest.
        )full_url)r   rd  r   r   r   r|  -  s    z&PreviewableMixin._get_dummy_header_urlc                 C  s   d S r   r   r   r   r   r   get_full_url4  s    zPreviewableMixin.get_full_urlr   ZDefaultZmobilez
mobile-altiw  zPreview in mobile size)r   ZiconZdevice_widthlabelZtabletz
tablet-alti   zPreview in tablet sizeZdesktopi   zPreview in desktop sizec                 C  s   t jS )a  
        A list of ``(internal_name, display_name)`` tuples for the modes in which
        this object can be displayed for preview/moderation purposes. Ordinarily an object
        will only have one display mode, but subclasses can override this -
        for example, a page containing a form might have a default view of the form,
        and a post-submission 'thank you' page.
        Set to ``[]`` to completely disable previewing for this model.
        )rT  DEFAULT_PREVIEW_MODESr   r   r   r   preview_modesO  s    
zPreviewableMixin.preview_modesc                 C  s   | j d d S )a,  
        The default preview mode to use in live preview.
        This default is also used in areas that do not give the user the option of selecting a
        mode explicitly, e.g. in the moderator approval workflow.
        If ``preview_modes`` is empty, an ``IndexError`` will be raised.
        r   )r  r   r   r   r   default_preview_mode[  s    z%PreviewableMixin.default_preview_modec                 C  s   t jS )a  
        A list of dictionaries, each representing a preview size option for this object.
        Override this property to customize the preview sizes.
        Each dictionary in the list should include the following keys:

        - ``name``: A string representing the internal name of the preview size.
        - ``icon``: A string specifying the icon's name for the preview size button.
        - ``device_width``: An integer indicating the device's width in pixels.
        - ``label``: A string for the aria label on the preview size button.

        .. code-block:: python

            @property
            def preview_sizes(self):
                return [
                    {
                        "name": "mobile",
                        "icon": "mobile-icon",
                        "device_width": 320,
                        "label": "Preview in mobile size"
                    },
                    # Add more preview size dictionaries as needed.
                ]
        )rT  DEFAULT_PREVIEW_SIZESr   r   r   r   preview_sizese  s    zPreviewableMixin.preview_sizesc                 C  s   | j d d S )z
        The default preview size name to use in live preview.
        Defaults to ``"mobile"``, which is the first one defined in ``preview_sizes``.
        If ``preview_sizes`` is empty, an ``IndexError`` will be raised.
        r   r   )r  r   r   r   r   default_preview_size  s    z%PreviewableMixin.default_preview_sizec                 C  s
   t | jS )zPReturns ``True`` if at least one preview mode is specified in ``preview_modes``.)boolr  r   r   r   r   is_previewable  s    zPreviewableMixin.is_previewablec                 C  s   t || ||| ||S )a  
        Returns an HTTP response for use in object previews.

        This method can be overridden to implement custom rendering and/or
        routing logic.

        Any templates rendered during this process should use the ``request``
        object passed here - this ensures that ``request.user`` and other
        properties are set appropriately for the wagtail user bar to be
        displayed/hidden. This request will always be a GET.
        )r#   get_preview_templateget_preview_contextr   r\  Z	mode_namer   r   r   rY    s
    

zPreviewableMixin.serve_previewc                 C  s
   | |dS )z_
        Returns a context dictionary for use in templates for previewing this object.
        )objectr\  r   r  r   r   r   r    s    z$PreviewableMixin.get_preview_contextc                 C  s   t dt| j dS )a_  
        Returns a template to be used when previewing this object.

        Subclasses of ``PreviewableMixin`` must override this method to return the
        template name to be used in the preview. Alternatively, subclasses can also
        override the ``serve_preview`` method to completely customise the preview
        rendering logic.
        zU%s (subclass of PreviewableMixin) must override get_preview_template or serve_previewN)r   typer   r  r   r   r   r    s
    	z%PreviewableMixin.get_preview_template)NNN)N)N)r   r   r   r   rh  rp  ra  r|  r  r%  r  r"  r  r  r  r  r  r  r  rY  r  r  r   r   r   r   rT    sJ    
-
B


	

rT  c                	      s   e Zd ZejeddddZejeddddZej	e
jeddddejdd	Zde_G d
d dZe fddZedd Z fddZdd Z  ZS )LockableMixinlockedFr)  z	locked atTr,  z	locked byzlocked_%(class)ssr   r   r   r   r   r   c                   @  s   e Zd ZdZdS )zLockableMixin.MetaTNr  r   r   r   r   r     s   r   c                   s   g t  jf i ||  S r   r/  r2  r   r   r   r0    s
    zLockableMixin.checkc                 C  sV   |   }tjdd| dd}z |t|tk r8|gW S W n tyP   g  Y S 0 g S )Nz3LockableMixin must be applied before RevisionMixin.zDMove LockableMixin in the model's base classes before RevisionMixin.zwagtailcore.E005r3  )r5  r   r6  rO   r   r  r7  r8  r   r   r   r1    s    
z#LockableMixin._check_revision_mixinc                   s(   t  |}| j|_| j|_| j|_|S )z
        Similar to :meth:`RevisionMixin.with_content_json`,
        but with the following fields also preserved:

        * ``locked``
        * ``locked_at``
        * ``locked_by``
        )r   r   r  	locked_at	locked_byr   r   r   r   r     s
    	zLockableMixin.with_content_jsonc                 C  s   | j rt| S dS )zd
        Returns a sub-class of ``BaseLock`` if the instance is locked, otherwise ``None``.
        N)r  rI   r   r   r   r   rO    s    zLockableMixin.get_lock)r   r   r   r   rP  r"  r  rQ  r  r!  r   AUTH_USER_MODELr#  r  wagtail_reference_index_ignorer   r&  r0  r1  r   rO  r   r   r   r   r   r    s.   

	
r  c                      s   e Zd ZdZe fddZedd Zedd Zedd	 Z	d
d Z
edd Zedd Zedd Zedd Zedd Zedd Z fddZ  ZS )WorkflowMixinz.A mixin that allows a model to have workflows.c                   s   g t  jf i ||  S r   )r   r0  %_check_draftstate_and_revision_mixinsr2  r   r   r   r0    s
    zWorkflowMixin.checkc                 C  sl   |   }tjdd| dd}z4|t|t  k rB|tk sLn |gW S W n tyf   |g Y S 0 g S )NzIWorkflowMixin requires DraftStateMixin and RevisionMixin (in that order).zfMake sure your model's inheritance order is as follows: WorkflowMixin, DraftStateMixin, RevisionMixin.zwagtailcore.E006r3  )r5  r   r6  rO   r  r'  r   r7  r8  r   r   r   r  	  s"    z3WorkflowMixin._check_draftstate_and_revision_mixinsc                 C  sH   t tddsdS tjj| dd}tjjd|dd }|rD|j	S dS )a  
        Returns the active workflow assigned to the model.

        For non-``Page`` models, workflows are assigned to the model's content type,
        thus shared across all instances instead of being assigned to individual
        instances (unless :meth:`~WorkflowMixin.get_workflow` is overridden).

        This method is used to determine the workflow to use when creating new
        instances of the model. On ``Page`` models, this method is unused as the
        workflow can be determined from the parent page's workflow.
        WAGTAIL_WORKFLOW_ENABLEDTNFr   )Zworkflow__activer   workflow)
getattrr   r   r   r   WorkflowContentTyper   select_relatedr   r  )r   r   Zworkflow_content_typer   r   r   get_default_workflow#  s    	z"WorkflowMixin.get_default_workflowc                 C  s   |   duS )zj
        Returns ```True``` if the object has an active workflow assigned, otherwise ```False```.
        N)get_workflowr   r   r   r   has_workflowA  s    zWorkflowMixin.has_workflowc                 C  s   |   S )zE
        Returns the active workflow assigned to the object.
        )r  r   r   r   r   r  H  s    zWorkflowMixin.get_workflowc                 C  s   t j| S )a  
        Returns workflow states that belong to the object.

        To allow filtering ``WorkflowState`` queries by the object,
        subclasses should define a
        :class:`~django.contrib.contenttypes.fields.GenericRelation` to
        :class:`~wagtail.models.WorkflowState` with the desired
        ``related_query_name``. This property can be replaced with the
        ``GenericRelation`` or overridden to allow custom logic, which can be
        useful if the model has inheritance.
        )WorkflowStater   for_instancer   r   r   r   workflow_statesN  s    zWorkflowMixin.workflow_statesc                 C  sP   t tddsdS t| dr<| jD ]}|jtjkr  dS q dS | jjtjd	 S )zo
        Returns ```True``` if a workflow is in progress on the current object, otherwise ```False```.
        r  TF_current_workflow_statesstatus)
r  r   rZ  r  r  r  STATUS_IN_PROGRESSr  r   r   )r   stater   r   r   workflow_in_progress]  s    

z"WorkflowMixin.workflow_in_progressc                 C  sP   t tddsdS t| dr<z| jd W S  ty:   Y dS 0 | j d S )zg
        Returns the in progress or needs changes workflow state on this object, if it exists.
        r  TNr  r   Zcurrent_task_state__task)	r  r   rZ  r  rm  r  activer  r   r   r   r   r   current_workflow_stateq  s    

z$WorkflowMixin.current_workflow_statec                 C  s(   | j }|r$|jtjkr$|jr$|jjS dS )zr
        Returns (specific class of) the current task state of the workflow on this object, if it exists.
        N)r  r  r  r  current_task_stater   )r   r  r   r   r   current_workflow_task_state  s    
z)WorkflowMixin.current_workflow_task_statec                 C  s   | j }|r|jjS dS )zh
        Returns (specific class of) the current task in progress on this object, if it exists.
        N)r  taskr   )r   r  r   r   r   current_workflow_task  s    z#WorkflowMixin.current_workflow_taskc                 C  sp   | j s:| jrtdS | jr"tdS | jr0tdS tdS n2| jrHtdS | jrVtdS | jrdtdS tdS d S )	Nr.  r<  zin moderationr=  r>  zlive + in moderationr?  r(  )r(  r.  r"  r;  r  r@  r   r   r   r   rA    s    
zWorkflowMixin.status_stringc                   s*   t   }|r|S | j}|r&t| |S d S r   )r   rO  r  rK   )r   lockr  r   r   r   rO    s    
zWorkflowMixin.get_lock)r   r   r   r   r&  r0  r  r  r%  r  r  r  r  r  r  r  rA  rO  r   r   r   r   r   r    s.   








r  c                   @  s$   e Zd ZdZe ZG dd dZdS )AbstractPageak  
    Abstract superclass for Page. According to Django's inheritance rules, managers set on
    abstract models are inherited by subclasses, but managers set on concrete models that are extended
    via multi-table inheritance are not. We therefore need to attach PageManager to an abstract
    superclass to ensure that it is retained by subclasses of Page.
    c                   @  s   e Zd ZdZdS )zAbstractPage.MetaTNr  r   r   r   r   r     s   r   N)r   r   r   r   PageManagerr   r   r   r   r   r   r    s   
r  add_pageAddzAdd/edit pages you ownZbulk_delete_pagezBulk deletezDelete pages with childrenchange_pageZEditzEdit any pageZ	lock_pageLockzLock/unlock pages you've lockedZpublish_pageZPublishzPublish any pageZunlock_pageZUnlockzUnlock any pagec                 C  s"   g | ]\}}}|d d |fqS )Nr   )r   
identifierr"  Z
long_labelr   r   r   r     s   r   c                 C  s   g | ]^}}|qS r   r   )r   r  r"  r   r   r   r     r   c                      s  e Zd ZejeddeddZejdddZejeddded	d
Z	ej
eeddeedZde_ejeddddZej
ejeddddejddZde_ejedddeddZdZejeddeddZejeddeddZejeddddZeddd Zed!d"d#ddd$Zed!d%d#ddd$Z ej
d&ejdddd'd(Z!de!_e"j#dd)d*e"$de"%de"%d+e"%d,e"%de"%d%e"%d-e"%d.e"%d/e"%d0e"%d1e"%d2e"%d3e"%d4e"%d5gZ&dZ'd6Z(d6Z)d7Z*g Z+d+d.d8d9d-d:d;d<e,g	Z-e.d=dgi gZ/e.d>g d?ed@gi e.d>d0gedAgi gZ0e.dBg i e1 gZ2g dCZ3e4j5e4j6e4j7e4j8e4j9e4j:e4j;gZ<e=dDdEdFdGdHdIZ>e=dDdEdJdGdKdLZ?e@dMdN ZA fdOdPZBdQdR ZCeDdSdT ZEdUdV ZFdWdX ZGe@dYdZ ZHd[d\ ZIe=dd]d^ZJd_d` ZK fdadbZLdcdd ZM fdedfZN fdgdhZOdidj ZPeQjRd fdkdl	ZSdmdn ZTe@ fdodpZUdqdr ZVeDdsdt ZWdudv ZXdwdx ZYddydzZZd{d| Z[d6d6d6d}d~dZ\de\_]dddZ^dddZ_d6Z`dd Zadd Zbdd Zcdd Zddd Zedd Zfdd Zgdd ZhdddZidddZjdddZkdddZleDelZmdddZneDenZodddZpdd Zqe@ fddZrdd Zse@dd Zte@dd Zue@dd Zve@dd Zwe@dd Zxe@dd Zye@dd Zzdd Z{e@dd Z|e@dd Z}eDdd Z~dd ZdddZdddZde_]dd6d6d6d6ddd6dĜddƄZde_]dddȄZde_]ddʄ Zdd̄ Zdd΄ ZddЄ Zdd҄ ZeDddԄ ZdddքZddd؄ZdddڄZddd܄ZdddބZd ddZdd Zd6Zdd Zdd ZeDdd Zdd ZG dd dZ  ZS (  r   title   z8The page title as you'd like it to be seen by the public)r   
max_length	help_textF)r  r   slugTzTThe name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/)r   allow_unicoder  r  content typer   r   r   r   zURL path)r   r   r   ownerZowned_pagesr  z	title tagzRThe name of the page displayed on search engine results as the clickable headline.)r   r  r   r  zshow in menuszHWhether a link to this page will appear in automatically generated menusr   r*  r  zmeta descriptionzNThe descriptive text displayed underneath a headline in search engine results.)r   r   r  zlatest revision created atr,  r   r~   )related_query_namezwagtailcore.WorkflowStater  r   )Zcontent_type_fieldZobject_id_fieldr  r   r   r   aliases)r   r   r   r   r      )Zboostr  r(  r   r   r  show_in_menusrM  rR  latest_revision_created_atr   r   Nz-latest_revision_created_atnumchildurl_pathpostgres_index_entriesindex_entriesr   z$wagtail.admin.panels.TitleFieldPanelz$wagtail.admin.panels.MultiFieldPanel)r  	seo_titlesearch_descriptionzFor search engineszFor site menusz$wagtail.admin.panels.PublishingPanel)passwordgroupsloginr   r  zRouteResult | None)r\  r   returnc                 C  sl   t | dsfzBt|  }rDdd |dD }|jjj| || _nd| _W n t	yd   d| _Y n0 | jS )z
        Find the page route for the given HTTP request object, and URL path. The route
        result (`page`, `args`, and `kwargs`) will be cached via
        ``request._wagtail_route_for_request``.
        _wagtail_route_for_requestc                 S  s   g | ]}|r|qS r   r   )r   	componentr   r   r   r     s   z*Page.route_for_request.<locals>.<listcomp>rs  N)
rZ  ry   find_for_requestsplitZ	root_pageZ	localizedr   router  r   )r\  r   sitepath_componentsr   r   r   route_for_request  s    


zPage.route_for_requestzPage | Nonec                 C  s    t | |}|dur|d S dS )z
        Find the page for the given HTTP request object, and URL path. The full
        page route will be cached via ``request._wagtail_route_for_request``.
        Nr   )r   r  )r\  r   resultr   r   r   r    s    zPage.find_for_requestc                 C  s   dd | j D S )Nc                 S  s    g | ]}t |d r|jn|qS )value)rZ  r  )r   methodr   r   r   r     s   z2Page.allowed_http_method_names.<locals>.<listcomp>)allowed_http_methodsr   r   r   r   allowed_http_method_names  s    zPage.allowed_http_method_namesc                   s@   t  j|i | | js<| js,tj| | _d|vr<| j| _	d S )Nr  )
r   r   r  content_type_idr   r   r   r   show_in_menus_defaultr  r   argsr   r   r   r   r     s    zPage.__init__c                 C  s   | j S r   )r  r   r   r   r   __str__  s    zPage.__str__c                 C  s   | j jS r   )specific_deferred
_revisionsr   r   r   r   r     s    zPage.revisionsc                 C  s   t  S r   )r   r   r   r   r   r     s    zPage.get_base_content_typec                 C  s   | j S r   r   r   r   r   r   r     s    zPage.get_content_typec                 C  s   t | S r   )r   r  r   r   r   r     s    zPage.get_streamfield_namesc                 C  s$   |r|j | j d | _ nd| _ | j S )aw  
        Populate the url_path field based on this page's slug and the specified parent page.
        (We pass a parent in here, rather than retrieving it via get_parent, so that we can give
        new unsaved pages a meaningful URL when previewing them; at that point the page has not
        been assigned a position in the tree, as far as treebeard is concerned.
        rs  )r  r  r   parentr   r   r   set_url_path  s    zPage.set_url_pathc                 C  s4   |du rdS |  }|r"||}|j| d  S )z
        Determine whether the given slug is available for use on a child page of
        parent_page. If 'page' is passed, the slug is intended for use on that page
        (and so it will be excluded from the duplicate check).
        NTr  )get_childrenZnot_pager   r   )r  r   r~   Zsiblingsr   r   r   _slug_is_available  s    
zPage._slug_is_availablec                 C  s8   |}d}|   }t||| s4|d7 }d||f }q|S )Nr^   z%s-%d)
get_parentr   r  )r   	base_slugZcandidate_slugsuffixr   r   r   r   _get_autogenerated_slug  s    zPage._get_autogenerated_slugc                   s8   |   }|dur.|jj dj|jdjS t 	 S )z{
        Finds the default locale to use for this page.

        This will be called just before the initial save.
        Nr   r  )
r  specific_classr   r   r  r   r  r   r   get_default_localer  r   r   r   r  +  s    zPage.get_default_localec                 C  s   | j S )z
        Determine the default ordering for child pages in the admin index listing.
        Returns a string (e.g. 'latest_revision_created_at, title, ord' or 'live').
        )admin_default_orderingr   r   r   r   get_admin_default_ordering<  s    zPage.get_admin_default_orderingc                   sh   | j s0ttdd}t| j|d}|r0| || _ | js>| j| _| jd u rR|  | _	t
 j|i | d S )NZWAGTAIL_ALLOW_UNICODE_SLUGST)r  )r  r  r   r/   r  r  draft_title	locale_idr  r   r   r  )r   r  r   r  r  r   r   r   r  C  s    

zPage.full_cleanc                   sD   t    |  }t| j|| s@tdtd| j|jd id S )Nr  zZThe slug '%(page_slug)s' is already in use within the parent page at '%(parent_url_path)s')Z	page_slugZparent_url_path)	r   r  r  r   r  r  r   r"  r  )r   r   r   r   r   r  X  s    
z
Page.cleanc                 C  s$   t | dr| jS tjj| jd S )z
        Returns True if this page is the root of any site.

        This includes translations of site root pages as well.
        _is_site_root)Zroot_page__translation_key)rZ  r  ry   r   r   r   r   r   r   r   r   is_site_roote  s
    
zPage.is_site_rootc                   s2  |r   d}jdu }|r.  nPd|v rBd|d vs~tjjjdj  jjkr~  d} j	}j	}t
 jf i |}	|r|| t fdd  rt  |rt}
td	jj|
jj|
jj	 |dur.|rtd
|pjdd n|r.t||d |	S )ah  
        Overrides default method behavior to make additional updates unique to pages,
        such as updating the ``url_path`` value of descendant page to reflect changes
        to this page's slug.

        New pages should generally be saved via the :meth:`~treebeard.mp_tree.MP_Node.add_child`
        or :meth:`~treebeard.mp_tree.MP_Node.add_sibling`
        method of an existing page, which will correctly set the ``path`` and ``depth``
        fields on the new page before saving it.

        By default, pages are validated using ``full_clean()`` before attempting to
        save changes to the database, which helps to preserve validity when restoring
        pages from historic revisions (which might not necessarily reflect the current
        model state). This validation step can be bypassed by calling the method with
        ``clean=False``.
        FNr  r  r  Tc                     s   t jjpjj dS )N)r   r   Zinstance_before)rQ   sendr  r   r   r   Z
old_recordr   r   r   <lambda>  s   
zPage.save.<locals>.<lambda>z3Page created: "%s" id=%d content_type=%s.%s path=%szwagtail.create)r   r  r	  r  )r   r  r	  )r  r  r  r  r   r   r   r   r  r  r   r  _update_descendant_url_pathsr   Z	on_commitr  ry   Zclear_site_root_paths_cacher  r  r  r  r   r   r   rL   r  )r   r  r	  r  r   Zslug_changedis_newold_url_pathnew_url_pathr  r   r   r  r   r  t  sZ    

	

z	Page.savec                 O  s$   | dd }t| |dj|i |S )Nr	  r	  )popr9   rE  )r   r  r   r	  r   r   r   delete  s    zPage.deletec              
     sL  t  jf i |}dd | gt| j  D }| jjD ]@}t|tjr8|j	|vr8|j
jtjkr8|tjdd|dd q8t| jts|tjdd| d	d z|   W nF ttfy } z*|tjd
|  t|d	d W Y d }~n
d }~0 0 z|   W nH ttfyF } z*|tjd|  t|d	d W Y d }~n
d }~0 0 |S )Nc                 S  s(   g | ] }|j j D ]}|r|jqqS r   )r   r   r   r   )r   r   r   r   r   r   r     s   zPage.check.<locals>.<listcomp>z'Field hasn't specified on_delete actionzSet on_delete=models.SET_NULL and make sure the field is nullable or set on_delete=models.PROTECT. Wagtail does not allow simple database CASCADE because it will corrupt its tree storage.zwagtailcore.W001r3  z)Manager does not inherit from PageManagerzHEnsure that custom Page managers inherit from wagtail.models.PageManagerzwagtailcore.E002z$Invalid subpage_types setting for %s)r4  r  z(Invalid parent_page_types setting for %s)r   r0  r   r   r   fieldsr   r   r!  r   Zremote_fieldr   CASCADEr   r   Warningr   r  r6  clean_subpage_modelsr7  r   r  clean_parent_page_models)r   r   errorsZfield_exceptionsr   er   r   r   r0    s`    
		z
Page.checkc                 C  s>   t jj| jdj| jdjtt|t	dt
|d d d S )N)path__startswithr   r  r^   )r  )r   r   r   r   r   r   r   r   r   r   r   )r   r  r  r   r   r   r    s    z!Page._update_descendant_url_pathsc                 C  s    | j r|  rdS | j  S dS )z?
        A human-readable version of this page's type.
        r   N)r  Zis_rootget_verbose_namer   r   r   r   page_type_display_name!  s    zPage.page_type_display_namec                 C  sv   |r`|d }|dd  }z |   j|d}t|d|  W n tjyP   tY n0 |j||S | jrnt	| S td S )Nr   r^   r  Z_cached_parent_obj)
r  r   rc  r   r   r   r   r  r(  r[   )r   r\  r  Z
child_slugZremaining_componentsZsubpager   r   r   r  +  s    
z
Page.routec                 C  s   | j p
| jS )z
        Return the title for this page as it should appear in the admin backend;
        override this if you wish to display extra contextual information about the page,
        such as language. By default, returns ``draft_title``.
        )r  r  r   r   r   r   get_admin_display_titleD  s    zPage.get_admin_display_titlec                 C  sN  t | | jstd| jr"td|r.|   t| tjdd}|D ]}|  qDt	j
j| |  |||  t| d}	|D ]
}|	|_qz|	j| _| j| _|	| _tdddg}
|rd| _|
d	 |
r| j|
d
d td| j| j|	j |rJ|st| t |tr|nd||	|d n6t| t |tr(|nd|d|jt|jdi|	|d |	S )Nztpage.save_revision() must be called on the specific version of the page. Call page.specific.save_revision() instead.zpage.save_revision() was called on an alias page. Revisions are not required for alias pages as they are an exact copy of another page.T)Z
pk__isnullr  r  r  r   r@  F)r  r  z&Page edited: "%s" id=%d revision_id=%dr  r  r  r  r  r  )r   r  RuntimeErroralias_of_idr  r  COMMENTS_RELATION_NAMEr   r  r   r   r  r   r   r  revision_createdr  r  r  r  r   r@  r   r  r  r  rL   r]   )r   r	  r
  r  r  r  r  Znew_commentscommentr  r  r   r   r   r  N  s    

	

zPage.save_revisionc                 C  s*   | j s| jS |  }|r | S | jS d S r   )r@  r   r   r   r   r   r   r   r     s    z"Page.get_latest_revision_as_objectr  _content_updated_idsc                  s(  | j }|du r|  }|pg }| jjj| dj|dD ]}g d}||d_d_|j	|d}|r|j
| j
k  fdd	}| D ]0\\}	}
}|
du r|D ]}|| qq|| qt||d
 |j_  j_| j_jdd tjj|dd |j|||d q6dS )aN  
        Publishes all aliases that follow this page with the latest content from this page.

        This is called by Wagtail whenever a page with aliases is published.

        :param revision: The revision of the original page that we are updating to (used for logging purposes)
        :type revision: Revision, Optional
        N)alias_ofid__in)r  r   r   r  r  r   r  r  TF)targetr   c                   s$   t | tr j| _ s t | _d S r   )r   rj   r   uuiduuid4r   )child_objectZalias_is_translationZalias_updatedr   r   process_child_object  s    
z1Page.update_aliases.<locals>.process_child_objectexclude_fields)r  )r   r   r  aliasr  )r   r   r  r   r   r   r   r(  r@  Zcopy_all_child_relationsr   rb  rd   r  r  r  r  r  r  r  rP   r  update_aliases)r   r  r  r  Zspecific_selfr$  r#  child_object_mapr!  relZprevious_idZchild_objectsr  r   r   r   r%    sR    	


zPage.update_aliasesc                 C  s   t |||||dj|dS )NrB  rC  )r;   rE  rF  r   r   r   rG  8  s    	zPage.publishc                 C  s   t | ||||d S )NrH  )r>   rE  rK  r   r   r   rL  I  s    zPage.unpublishc                 O  s$   t | d| d|i}| jr | || j< |S )Nr   r\  )PAGE_TEMPLATE_VARcontext_object_name)r   r\  r  r   contextr   r   r   get_contextT  s    
zPage.get_contextc                 C  s
   |  |S r   )r+  r  r   r   r   r  `  s    zPage.get_preview_contextc                 O  s&   |j ddkr| jp| jS | jS d S )Nzx-requested-withZXMLHttpRequest)headersr   r   r   r   r\  r  r   r   r   r   get_templatec  s    zPage.get_templatec                 C  s
   |  |S r   )r.  r  r   r   r   r  i  s    zPage.get_preview_templatec                 O  s:   d|_ t|| j|g|R i || j|g|R i |S NF)rW  r#   r.  r+  r-  r   r   r   servel  s    z
Page.servec                 O  s:   |   }|j|vr6tjd|j|jd|dd t|S dS )a  
        Checks the ``method`` attribute of the request against those supported
        by the page (as defined by :attr:`allowed_http_methods`) and responds
        accordingly.

        If supported, ``None`` is returned, and the request is processed
        normally. If not, a warning is logged and an ``HttpResponseNotAllowed``
        is returned, and any further request handling is terminated.
        zMethod Not Allowed (%s): %si  )status_coder\  )extraN)r  r  r  warningr   r!   )r   r\  r  r   allowed_methodsr   r   r   check_request_methodu  s    

zPage.check_request_methodc                 O  s   t dd|  idS )a  
        Returns an ``HttpResponse`` with an ``"Allow"`` header containing the list of
        supported HTTP methods for this page. This method is used instead of
        :meth:`serve` to handle requests when the ``OPTIONS`` HTTP verb is
        detected (and :class:`HTTPMethod.OPTIONS <python:http.HTTPMethod>` is
        present in :attr:`allowed_http_methods` for this type of page).
        ZAllowz, )r,  )r    joinr  r-  r   r   r   handle_options_request  s    zPage.handle_options_requestc                 C  s   |    p| jdkS )z
        Return true if it's meaningful to browse subpages of this page -
        i.e. it currently has subpages,
        or it's at the top level (this rule necessary for empty out-of-the-box sites to have working navigation)
        r  )is_leafr   r   r   r   r   is_navigable  s    zPage.is_navigablec                 C  s<   |r|n| }z|j W S  ty6   t |_ |j  Y S 0 dS )zz
        Return ``Site.get_site_root_paths()``, using the cached copy on the
        request object if available.
        N)Z_wagtail_cached_site_root_pathsr   ry   Zget_site_root_paths)r   r\  cache_objectr   r   r   _get_site_root_paths  s    
zPage._get_site_root_pathsc                   s   t  fdd |D S )zS
        Returns a tuple of root paths for all sites this page belongs to.
        c                 3  s    | ]} j |jr|V  qd S r   )r  
startswith	root_path)r   Zsrpr   r   r   r     s   z5Page._get_relevant_site_root_paths.<locals>.<genexpr>)r   r;  )r   r:  r   r   r   _get_relevant_site_root_paths  s    z"Page._get_relevant_site_root_pathsc                 C  sp  |  |}|sdS |d \}}}}dd |D }t|dkr|t|tr|t|}|r||D ]"}	|	d |jkrX|	\}}}} q|qXttdd}
|
rzt	t
 |krt
 }W n ty   Y n0 zp|
rt
|, td| jt|d fd	}W d   n1 s0    Y  ntd| jt|d fd	}W n tyJ   |ddf Y S 0 tsf|d
krf|d
}|||fS )a  
        Determine the URL for this page and return it as a tuple of
        ``(site_id, site_root_url, page_url_relative_to_site_root)``.
        Return ``None`` if the page is not routable.

        This is used internally by the ``full_url``, ``url``, ``relative_url``
        and ``get_site`` properties and methods; pages with custom URL routing
        should override this method in order to have those operations return
        the custom URLs.

        Accepts an optional keyword argument ``request``, which may be used
        to avoid repeated database / cache lookups. Typically, a page model
        that overrides ``get_url_parts`` should not need to deal with
        ``request`` directly, and should just pass it to the original method
        when calling ``super``.
        Nr   c                 S  s   h | ]}|d  qS r   r   )r   r   r   r   r   r     r   z%Page.get_url_parts.<locals>.<setcomp>r^   ZWAGTAIL_I18N_ENABLEDFZwagtail_serve)r  rs  )r>  r   r   r   ry   r  r   r  r   rC   r'   Zget_languager   overrider%   r  r$   r@   rstrip)r   r\  Zpossible_sitessite_idr=  root_urlZlanguage_codeZunique_site_idsr  r   Zuse_wagtail_i18n	page_pathr   r   r   get_url_parts  sH    


(

zPage.get_url_partsc                 C  sB   | j |d}|du s,|d du r0|d du r0dS |\}}}|| S )zt
        Return the full URL (including protocol / domain) to this page, or ``None`` if it is not routable.
        r\  Nr^   r  )rE  )r   r\  	url_partsrB  rC  rD  r   r   r   r  	  s
     
zPage.get_full_urlc           	      C  s   |du r|durt |}|}| j|d}|du sJ|d du rN|d du rNdS |\}}}tdd | |D }|dur||jks|dkr|S || S dS )a   
        Return the 'most appropriate' URL for referring to this page from the pages we serve,
        within the Wagtail backend and actual website templates;
        this is the local URL (starting with '/') if we're only running a single site
        (i.e. we know that whatever the current page is being served from, this link will be on the
        same domain), and the full URL (with domain) if not.
        Return ``None`` if the page is not routable.

        Accepts an optional but recommended ``request`` keyword argument that, if provided, will
        be used to cache site-level URL information (thereby avoiding repeated database / cache
        lookups) and, via the ``Site.find_for_request()`` function, determine whether a relative
        or full URL is most appropriate.
        NrF  r^   r  c                 S  s   h | ]}|d  qS r?  r   )r   r=  r   r   r   r   2	  r   zPage.get_url.<locals>.<setcomp>)ry   r  rE  r   r;  r  )	r   r\  current_siter  rG  rB  rC  rD  Z	num_sitesr   r   r   get_url	  s    
 
zPage.get_urlc                 C  s   | j ||dS )a  
        Return the 'most appropriate' URL for this page taking into account the site we're currently on;
        a local URL if the site matches, or a fully qualified one otherwise.
        Return ``None`` if the page is not routable.

        Accepts an optional but recommended ``request`` keyword argument that, if provided, will
        be used to cache site-level URL information (thereby avoiding repeated database / cache
        lookups).
        )r\  rH  )rI  )r   rH  r\  r   r   r   relative_url=	  s    
zPage.relative_urlc                 C  s,   |   }|du rdS |\}}}tjj|dS )zC
        Return the Site object that this page belongs to.
        Nr  )rE  ry   r   r   )r   rG  rB  rC  rD  r   r   r   get_siteI	  s
    
zPage.get_sitec                   s   t j| }t  j|dS )Nr  )r   r   r   r   get_indexed_objectsr   )r   r   r   r   r   rL  X	  s    zPage.get_indexed_objectsc                 C  s&   z| j W S  | jjy    Y d S 0 d S r   )r   r  r   r   r   r   r   get_indexed_instance]	  s    zPage.get_indexed_instancec                   sd    j du r^t dd}|du r(t  _ n6 fdd|D  _  j D ]}t|tsBtd| qB j S )z
        Returns the list of subpage types, normalized as model classes.
        Throws ValueError if any entry in subpage_types cannot be recognized as a model name,
        or LookupError if a model does not exist (or is not a Page subclass).
        Nsubpage_typesc                   s   g | ]}t | jjqS r   rD   r   r   r   Zmodel_stringr  r   r   r   t	  s   z-Page.clean_subpage_models.<locals>.<listcomp>%s is not a Page subclass)r   r  r   
issubclassr   r   )r   rN  r   r   r  r   r	  g	  s    




zPage.clean_subpage_modelsc                   sd    j du r^t dd}|du r(t  _ n6 fdd|D  _  j D ]}t|tsBtd| qB j S )a  
        Returns the list of parent page types, normalized as model classes.
        Throws ValueError if any entry in parent_page_types cannot be recognized as a model name,
        or LookupError if a model does not exist (or is not a Page subclass).
        Nparent_page_typesc                   s   g | ]}t | jjqS r   rO  rP  r  r   r   r   	  s   z1Page.clean_parent_page_models.<locals>.<listcomp>rQ  )r   r  r   rR  r   r   )r   rS  r   r   r  r   r
  	  s    




zPage.clean_parent_page_modelsc                   s    fdd   D S )z}
        Returns the list of page types that this page type can be a subpage of,
        as a list of model classes.
        c                   s   g | ]} |  v r|qS r   r	  )r   Zparent_modelr  r   r   r   	  s   z3Page.allowed_parent_page_models.<locals>.<listcomp>r
  r  r   r  r   allowed_parent_page_models	  s    
zPage.allowed_parent_page_modelsc                   s    fdd   D S )z~
        Returns the list of page types that this page type can have as subpages,
        as a list of model classes.
        c                   s   g | ]} |  v r|qS r   rU  )r   Zsubpage_modelr  r   r   r   	  s   z/Page.allowed_subpage_models.<locals>.<listcomp>rT  r  r   r  r   allowed_subpage_models	  s    
zPage.allowed_subpage_modelsc                 C  s   dd |   D S )z~
        Returns the list of page types that may be created under this page type,
        as a list of model classes.
        c                 S  s   g | ]}|j r|qS r   )r   )r   Z
page_modelr   r   r   r   	  s   z1Page.creatable_subpage_models.<locals>.<listcomp>)rW  r  r   r   r   creatable_subpage_models	  s    zPage.creatable_subpage_modelsc                 C  s   | |j  v S )z
        Checks if this page type can exist as a subpage under a parent page
        instance.

        See also: :func:`Page.can_create_at` and :func:`Page.can_move_to`
        )r  rW  )r   r  r   r   r   can_exist_under	  s    zPage.can_exist_underc                 C  sX   | j o| |}| jdur.|o,| j | jk }| jdurT|oR| |  | jk }|S )zl
        Checks if this page type can be created as a subpage under a parent
        page instance.
        N)r   rY  	max_countr   countmax_count_per_parentr  r  )r   r  Z
can_creater   r   r   can_create_at	  s    

zPage.can_create_atc                 C  s(   |j dk}|s|j| jkrdS | |S )zn
        Checks if this page instance can be moved to be a subpage of a parent
        page instance.
        r^   F)r   r  rY  )r   r  Zparent_is_rootr   r   r   can_move_to	  s    
zPage.can_move_toc                 C  s   t | jjS )z_
        Returns the human-readable "verbose name" of this page model e.g "Blog page".
        r.   r   r   r  r   r   r   r  	  s    zPage.get_verbose_namec                 C  s4   t | dd}t|tr|S t|tr,t|S dS dS )za
        Returns a page description if it's set. For example "A multi-purpose web page".
        Zpage_descriptionNr   )r  r   r  r+   )r   descriptionr   r   r   get_page_description	  s    

zPage.get_page_descriptionc                 C  s   t | dr| jS | jduS )z
        ``_approved_schedule`` may be populated by ``annotate_approved_schedule`` on ``PageQuerySet`` as a
        performance optimization.
        _approved_scheduleN)rZ  rb  r:  r   r   r   r   r;  	  s    
zPage.approved_schedulec                 C  s   | j  o|  jdd  S )z
        An awkwardly-defined flag used in determining whether unprivileged editors have
        permission to delete this article. Returns true if and only if this page is non-live,
        and it has no live children.
        Tr(  )r(  get_descendantsr   r   r   r   r   r   has_unpublished_subtree

  s    zPage.has_unpublished_subtreec                 C  s   t | |||d S )z
        Extension to the treebeard 'move' method to ensure that url_path is updated,
        and to emit a 'pre_page_move' and 'post_page_move' signals.
        )posr	  )r:   rE  )r   r  rf  r	  r   r   r   move
  s    z	Page.movewagtail.copyc                 C  s&   t | |||||||||	|
djddS )z
        Copies a given page

        :param log_action: flag for logging the action. Pass None to skip logging. Can be passed an action string. Defaults to ``'wagtail.copy'``.
        )
toupdate_attrsr#  	recursivecopy_revisions	keep_liver	  r!  r  reset_translation_keyTrC  )r7   rE  )r   rk  ri  rj  rl  rm  r	  r!  r#  r  rn  r   r   r   r   
  s    z	Page.copyzwagtail.create_aliasrk  r  update_slugupdate_localer	  r  rn  _mpnode_attrsc          	      C  s   t | ||||||||d	 S )Nro  )r8   rE  )	r   rk  r  rp  rq  r	  r  rn  rr  r   r   r   create_alias=
  s    zPage.create_aliasc                 C  s   t | ||||d S )z4Creates a copy of this page in the specified locale.)copy_parentsr$  r#  )r6   rE  )r   r   rt  r$  r#  r   r   r   copy_for_translationW
  s    zPage.copy_for_translationc                 C  s>   | j o| j jt| jk}|r4t| | j s4| j|S t|| S )zp
        Return a PagePermissionsTester object defining what actions the user can perform on this page.
        )r  permissions_for_userr  r   r  PagePermissionTester)r   r	  Zis_overriddenr   r   r   rv  f
  s    	zPage.permissions_for_userc                 C  s&   | }|j jt|jkr|j}t|jS )z6Returns True if at least one preview mode is specified)r  r  r  r   r  r   r~   r   r   r   r  w
  s    zPage.is_previewablec                 C  s   dgS )aj  
        Returns a list of paths that this page can be viewed at.

        These values are combined with the dynamic portion of the page URL to
        automatically create redirects when the page's URL changes.

        .. note::

            If using ``RoutablePageMixin``, you may want to override this method
            to include the paths of popular routes.

        .. note::

            Redirect paths are 'normalized' to apply consistent ordering to GET parameters,
            so you don't need to include every variation. Fragment identifiers are discarded
            too, so should be avoided.
        rs  r   r   r   r   r   get_route_paths
  s    zPage.get_route_pathsc                 C  s   dgS )zP
        This returns a list of paths to invalidate in a frontend cache
        rs  r   r   r   r   r   get_cached_paths
  s    zPage.get_cached_pathsc                 C  s   | j | j| jr| j ndgS )z
        The components of a :class:`Page` which make up the :attr:`cache_key`. Any change to a
        page should be reflected in a change to at least one of these components.
        N)r  r  rR  	isoformatr   r   r   r   get_cache_key_components
  s    zPage.get_cache_key_componentsc                 C  s*   t  }|  D ]}|t| q| S )z
        A generic cache key to identify a page in its current state.
        Should the page change, so will the key.

        Customizations to the cache key should be made in :attr:`get_cache_key_components`.
        )rE   r|  r   r)   	hexdigest)r   Zhasherr  r   r   r   	cache_key
  s    	zPage.cache_keyc                 C  s   |  || jp| jdgS )N)locationZlastmod)r  rR  r  )r   r\  r   r   r   get_sitemap_urls
  s    
zPage.get_sitemap_urlsc                 C  s   t j| |S )z
        Returns a queryset of the current page's ancestors, starting at the root page
        and descending to the parent, or to the current page itself if ``inclusive`` is true.
        )r   r   Zancestor_ofr   	inclusiver   r   r   get_ancestors
  s    zPage.get_ancestorsc                 C  s   t j| |S )z
        Returns a queryset of all pages underneath the current page, any number of levels deep.
        If ``inclusive`` is true, the current page itself is included in the queryset.
        )r   r   descendant_ofr  r   r   r   rd  
  s    zPage.get_descendantsc                 C  s   t j| |S )z
        Returns a queryset of all other pages with the same parent as the current page.
        If ``inclusive`` is true, the current page itself is included in the queryset.
        )r   r   Z
sibling_ofr  r   r   r   get_siblings
  s    zPage.get_siblingsc                 C  s   |  |j| jddS )N)Z	path__gter   r  r   r   r   r  r   r   r   get_next_siblings
  s    zPage.get_next_siblingsc                 C  s   |  |j| jddS )N)Z	path__ltez-pathr  r  r   r   r   get_prev_siblings
  s    zPage.get_prev_siblingsc                   sF   t   fdd  |  |  dD ]} | q*tjjdS )a  
        Return a query set of all page view restrictions that apply to this page.

        This checks the current page and all ancestor pages for page view restrictions.

        If any of those pages are aliases, it will resolve them to their source pages
        before querying PageViewRestrictions so alias pages use the same view restrictions
        as their source page and they cannot have their own.
        c                   s"   | j r | j  n| j d S r   )r  addr  r~   add_page_to_check_listZpage_ids_to_checkr   r   r  
  s    z:Page.get_view_restrictions.<locals>.add_page_to_check_listr  Zpage_id__in)setr  onlyPageViewRestrictionr   r   rx  r   r  r   get_view_restrictions
  s    

zPage.get_view_restrictionsc                 C  s^   | j }|s8ttdd}ttdr8tdtd ttd|}| |}||d< ||d< t|||S )ah  
        Serve a response indicating that the user has been denied access to view this page,
        and must supply a password.
        ``form`` = a Django form object containing the password input
            (and zero or more hidden fields that also need to be output on the template)
        ``action_url`` = URL that this form should be POSTed to
        Z"WAGTAIL_PASSWORD_REQUIRED_TEMPLATEz"wagtailcore/password_required.htmlZPASSWORD_REQUIRED_TEMPLATEzjThe `PASSWORD_REQUIRED_TEMPLATE` setting is deprecated - use `WAGTAIL_PASSWORD_REQUIRED_TEMPLATE` instead.)categoryform
action_url)password_required_templater  r   rZ  r   r\   r+  r#   )r   r\  r  r  r  r*  r   r   r    serve_password_required_response   s*    	

z%Page.serve_password_required_responsec              	   C  s  t |vr^d|v r^t|d tr^t|d r^t|d d tr^d|d d v r^|d |t < |d= | j|}| j|_| j|_| j	|_	| j
|_
| j|_| j|_||   | j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_| j|_tt|t dd}t| t jddd}|D ],}z||j |_ W n t!yj   Y n0 qBt"|t | |S )a  
        Returns a new version of the page with field values updated to reflect changes
        in the provided ``content`` (which usually comes from a previously-saved
        page revision).

        Certain field values are preserved in order to prevent errors if the returned
        page is saved, such as ``id``, ``content_type`` and some tree-related values.
        The following field values are also preserved, as they are considered to be
        meaningful to the page as a whole, rather than to a specific revision:

        * ``draft_title``
        * ``live``
        * ``has_unpublished_changes``
        * ``owner``
        * ``locked``
        * ``locked_by``
        * ``locked_at``
        * ``latest_revision``
        * ``latest_revision_created_at``
        * ``first_published_at``
        * ``alias_of``
        * ``wagtail_admin_comments`` (COMMENTS_RELATION_NAME)
        commentsr   contentpathr  positionT)Zresolved_at__isnull)#r  r   r   r   dictr  r   r  r   r  r   r   r  r  r  r  r(  r@  owner_idr  locked_by_idr  latest_revision_idr  rM  r   r  r  r  values_listr   r   r  KeyErrorrc  )r   r   r   Zrevision_comment_positionsZpage_commentsr  r   r   r   r   #  sf    


zPage.with_content_jsonc                 C  s0   t tddsdS | jddjddjdd S )zs
        Returns ``True`` if the page or an ancestor has an active workflow assigned, otherwise ``False``.
        r  TFr  Zworkflowpage__isnullZworkflowpage__workflow__active)r  r   r  r   r   r   r   r   r   r  }  s    zPage.has_workflowc                 C  sv   t tddsdS t| dr,| jjjr,| jjS z*|  jddjddd	 jj}W n t
yl   d}Y n0 |S dS )	z[
        Returns the active workflow assigned to the page or its nearest ancestor.
        r  TNworkflowpageFr  r  z-depth)r  r   rZ  r  r  r  r  r   r   r   r   )r   r  r   r   r   r    s     

zPage.get_workflowc                   @  s0   e Zd ZedZedZdgZdd eD ZdS )z	Page.Metar~   r   )r   r   c                 C  s"   g | ]\}}}|d vr||fqS )>   Zdelete_pager  r  Z	view_pager   )r   codenamer"  r   r   r   r   r     s   zPage.Meta.<listcomp>N)	r   r   r   r"  r   verbose_name_pluralunique_togetherPAGE_PERMISSION_TYPESpermissionsr   r   r   r   r     s   r   )N)TNF)NNTFNT)NTTNF)FTNT)N)N)N)N)NN)N)NN)
FNNTTNNNrh  T)FFN)N)F)F)T)F)F)r   r   r   r   	CharFieldr"  r  r  Z	SlugFieldr  r!  r   ZSETr   r   r  	TextFieldr  r   r  r#  r  r  r  rP  r  r  rQ  r  r   r  Z_workflow_statesZ_specific_workflow_statesr  rO   ZSearchFieldZAutocompleteFieldZFilterFieldZsearch_fieldsr   rZ  r\  r  exclude_fields_in_copyr  r$  rw   Zcontent_panelsZpromote_panelsrv   Zsettings_panelsZprivate_page_optionsr?   DELETErv  HEADOPTIONSPATCHPOSTPUTr  staticmethodr  r  r&  r  r   r  r%  r   r   r   r   r  r  r  r  r  r  r  r  r   atomicr  r  r0  r  r  r  r  r  r   r%  Zalters_datarG  rL  r)  r+  r  r.  r  r0  r5  r7  r9  r;  r>  rE  r  r  rI  r  rJ  rK  rL  rM  r	  r
  rV  rW  rX  rY  r]  r^  r  ra  r;  re  rg  r   rs  ru  rv  r  ry  rz  r|  r~  r  r  rd  r  r  r  r  r  r  r   r  r  r   r   r   r   r   r   r     s8  		


	


[@
	      
`w     

		



O

(







	






	          
  







#Z
r   )	metaclassc                   @  s.   e Zd ZejddddZdZG dd dZdS )	OrderableTF)r   r   r   
sort_orderc                   @  s   e Zd ZdZdgZdS )zOrderable.MetaTr  N)r   r   r   r   orderingr   r   r   r   r     s   r   N)r   r   r   r   ZIntegerFieldr  Zsort_order_fieldr   r   r   r   r   r    s   r  c                   @  s,   e Zd Zdd Zdd Zdd Zdd Zd	S )
RevisionQuerySetc                 C  s   t t dS )N)r  )r   r   r   r   r   r   page_revisions_q  s    z!RevisionQuerySet.page_revisions_qc                 C  s   |  |  S r   )r   r  r   r   r   r   page_revisions  s    zRevisionQuerySet.page_revisionsc                 C  s   |  |  S r   )r   r  r   r   r   r   not_page_revisions  s    z#RevisionQuerySet.not_page_revisionsc                 C  sR   z| j | t|jdW S  tyL   | j tjj|ddt|jd Y S 0 d S )Nr  r   Fr   r   r   r   r  r   r   r   r   r   r   r   r   r   r   r    s    zRevisionQuerySet.for_instanceN)r   r   r   r  r  r  r  r   r   r   r   r    s   r  c                   @  s   e Zd ZdddZdS )RevisionsManagerr  c                 C  s|   |}t tjjt| dt| ddtt| dt| ddtt| ddB dd	jd
dddd S )a8  
        Returns a Subquery that can be used to annotate a queryset with the ID
        of the previous revision, based on the revision_fk_name field. Useful
        to avoid N+1 queries when generating comparison links between revisions.

        The logic is similar to ``Revision.get_previous().pk``.
        Z__base_content_type_idZ__object_idbase_content_type_idr   Z__created_atZ__pk)r  Zpk__lt)Zcreated_at__lt-created_atz-pkr   TZflatNr^   )r   r   r   r   r   r   r   r  )r   Zrevision_fk_nameZfkr   r   r   previous_revision_id_subquery  s(    z.RevisionsManager.previous_revision_id_subqueryN)r  )r   r   r   r  r   r   r   r   r    s   r  c                   @  s   e Zd Zdd ZdS )PageRevisionsManagerc                 C  s   t | j| jd S N)Zusing)r  r   _dbr  r   r   r   r   r     s    z!PageRevisionsManager.get_querysetN)r   r   r   r   r   r   r   r   r    s   r  c                      s.  e Zd ZejeejddZejeejddZej	de
ddZejde
ddZejeje
d	ddejd
dZejddZeje
dedZeje
dddddZe Ze ZeddddZdZedd Z d+ fdd	Z!dd Z"dd Z# fdd Z$d,d!d"Z%d#d$ Z&d%d& Z'd'd( Z(G d)d* d*Z)  Z*S )-r   r   r   r   r  	object idr  r   T
created at)r+  r   r	  Zwagtail_revisionsr   r   r   r   r   r   )r*  zcontent JSON)r   encoderzapproved go live at)r   r   r   r+  r   r   Fr   c                 C  s   | j j| jdS )Nr   )r  Zget_object_for_this_typer   r   r   r   r   base_content_object  s    zRevision.base_content_objectNc              	     s   | j d u rt | _ | jd u r&| j| _t j|i | | jd u rd|v rd|d v r|  }t	|dd| j
t| j |jrt|jnd |jdi|| d d S )Nr  r
  zwagtail.schedule.cancelr  )r  r  rS  Zhas_live_version)r   r  r   r	  r  )r  r&   nowr  r  r   r  r
  r   rL   r  r]   rS  r(  )r   r	  r  r   r  r   r   r   r    s6    




zRevision.savec                 C  s   | j | jS r   )r  r   r   r   r   r   r   r   D  s    zRevision.as_objectc                 C  sB   | j d u rdS tjj| j| jdddjddd }|| j kS )NTr  r  -idr  r  )	r  r   r   r   r  r   r   r  r   )r   r  r   r   r   is_latest_revisionG  s    
	zRevision.is_latest_revisionc                   sF   z|   }W n tjy$   d }Y n0 |r<| j j|d t  S )N)r  )get_nextr   r   created_commentsallr   r   r  )r   Znext_revisionr   r   r   r  X  s    
zRevision.deletec                 C  s   | j j| |||||dS )N)r	  r  r  r  rD  )r  rG  )r   r	  r  r  r  rD  r   r   r   rG  f  s    zRevision.publishc                 C  s   | j | j| jdS Nr  )Zget_previous_by_created_atr  r   r   r   r   r   get_previousw  s    zRevision.get_previousc                 C  s   | j | j| jdS r  )Zget_next_by_created_atr  r   r   r   r   r   r  }  s    zRevision.get_nextc                 C  s   dt | j d t | j S )N"z" at )r  r  r  r   r   r   r   r    s    zRevision.__str__c                   @  s@   e Zd ZedZedZejddgddejddgddgZd	S )
zRevision.Metar  r   r   r   Zcontent_object_idxr  r   r  Zbase_content_object_idxN)	r   r   r   r"  r   r  r   Indexindexesr   r   r   r   r     s   r   )N)NTTNF)+r   r   r   r   r!  r   r  r   r  r  r"  r   rQ  r  r   r  r#  r	  r  r  Z	JSONFieldr   r   r
  r  r   r  r  r
   r  r  r,   r  r  r   r  r  rG  r  r  r  r   r   r   r   r   r   r     s`   
(     
r   c                      s   e Zd Z fddZ  ZS )GroupPagePermissionManagerc                   sL   | d}|dd }|s:|r:tjj t | dd|d< t jf i |S )N
permissionpermission_typeZ_page)r   r  )r   r  r	   r   r   r   r  )r   r   r  r  r   r   r   r    s    

z!GroupPagePermissionManager.create)r   r   r   r  r   r   r   r   r   r    s   r  c                   @  sn   e Zd ZejeeddejdZejdeddejdZ	eje
edejdZe ZG d	d
 d
Zdd ZdS )GroupPagePermissiongroupZpage_permissionsr  r   r~   Zgroup_permissionsr  )r   r   c                   @  s,   e Zd ZejdddgZedZedZdS )zGroupPagePermission.Meta)r  r~   r  Zunique_permissionr  zgroup page permissionzgroup page permissionsN)	r   r   r   r   UniqueConstraintconstraintsr"  r   r  r   r   r   r   r     s   r   c                 C  s"   d| j j| j | jj| jj| jf S )Nz5Group %d ('%s') has permission '%s' on page %d ('%s'))r  r  r  r  r~   r   r   r   r   r    s    zGroupPagePermission.__str__N)r   r   r   r   r!  r   r"  r  r  r~   r	   r  r  r   r   r  r   r   r   r   r    s(   
r  c                   @  s   e Zd Zdd Zdd Zdd Zdd Zd	d
 Zd+ddZdd Z	dd Z
dd Zdd Zdd Zdd Zdd Zdd Zdd Zd d! Zd"d# Zd$d% Zd,d&d'Zd(d) Zd*S )-rw  c                   sZ   ddl m} | _| _| _|jdk _ jjrV jjsV fdd j	|D  _
d S )Nr   page_permission_policyr^   c                   s4   h | ],} j j|j jr|jjjd ddd qS )r"  r^   )maxsplitr   )r~   r   r<  r  r  rsplit)r   permr   r   r   r     s   z0PagePermissionTester.__init__.<locals>.<setcomp>)wagtail.permissionsr  r	  Zpermission_policyr~   r   page_is_root	is_activeis_superuserZget_cached_permissions_for_userr  )r   r	  r~   r  r   r   r   r     s    

zPagePermissionTester.__init__c                 C  s   | j j| jjkS r   )r~   r  r	  r   r   r   r   r   user_has_lock  s    z"PagePermissionTester.user_has_lockc                 C  s   | j  }|o|| jS r   )r~   rO  Zfor_userr	  )r   r  r   r   r   page_locked  s    
z PagePermissionTester.page_lockedc                 C  s:   | j jsdS | jj}|d u s$| s(dS | j jp8d| jv S )NFr  r	  r  r~   r  rX  r  r  r   r  r   r   r   can_add_subpage  s    z$PagePermissionTester.can_add_subpagec                 C  sr   | j jsdS | jrdS | j jr"dS d| jv r0dS d| jv rN| jj| j jkrNdS | jj}|rn|	| j| j rndS dS )NFTchanger  )
r	  r  r  r  r  r~   r  r   r  user_can_access_editorr   r  r   r   r   can_edit  s     
zPagePermissionTester.can_editFc                 C  s   | j jsdS | jrdS | j jr"dS d| jvr>| j s>|s>dS d| jv rtd| jvrp| jjdd}| 	 rpdS dS d| jv r| jjdd}d| jv r|j
| j d	  S |j
d| j d		  S ndS d S )
NFTZbulk_deleter  rG  r  r  )r  )r(  r  )r	  r  r  r  r  r~   r8  rd  r(  r   r   )r   ignore_bulkZpages_to_deleter   r   r   
can_delete  s4    



zPagePermissionTester.can_deletec                 C  s<   | j jsdS | jjr| jrdS |  r*dS | j jp:d| jv S NFrG  )r	  r  r~   r(  r  r  r  r  r   r   r   r   can_unpublish3  s    z"PagePermissionTester.can_unpublishc                 C  s(   | j jsdS | jrdS | j jp&d| jv S r  )r	  r  r  r  r  r   r   r   r   can_publish=  s
    z PagePermissionTester.can_publishc                 C  s   |    o| jjo| jj S r   )r  r~   r  r  r   r   r   r   can_submit_for_moderationE  s
    
z.PagePermissionTester.can_submit_for_moderationc                 C  s   |   S r   r  r   r   r   r   can_set_view_restrictionsL  s    z.PagePermissionTester.can_set_view_restrictionsc                 C  s   |   S r   r  r   r   r   r   can_unscheduleO  s    z#PagePermissionTester.can_unschedulec                 C  s:   | j jrdS | jj}|r(|| j| j S d| jv r6dS dS )NTr  F)r	  r  r~   r  user_can_lockr  r  r   r   r   can_lockR  s    
zPagePermissionTester.can_lockc                 C  sF   | j jrdS |  rdS | jj}|r4|| j| j S d| jv rBdS dS )NTZunlockF)r	  r  r  r~   r  user_can_unlockr  r  r   r   r   
can_unlock^  s    
zPagePermissionTester.can_unlockc                 C  s:   | j jsdS | jj}|du s$| s(dS | j jp8d| jv S )a=  
        Niggly special case for creating and publishing a page in one go.
        Differs from can_publish in that we want to be able to publish subpages of root, but not
        to be able to publish root itself. (Also, can_publish_subpage returns false if the page
        does not allow subpages at all.)
        FNrG  r  r  r   r   r   can_publish_subpagen  s    z(PagePermissionTester.can_publish_subpagec                 C  s   | j jsdS | j jpd| jv S )a  
        Reorder permission checking is similar to publishing a subpage, since it immediately
        affects published pages. However, it shouldn't care about the 'creatability' of
        page types, because the action only ever updates existing pages.
        FrG  )r	  r  r  r  r   r   r   r   can_reorder_children}  s    z)PagePermissionTester.can_reorder_childrenc                 C  s   | j ddS )a)  
        Moving a page should be logically equivalent to deleting and re-adding it (and all its children).
        As such, the permission test for 'can this be moved at all?' should be the same as for deletion.
        (Further constraints will then apply on where it can be moved *to*.)
        T)r  )r  r   r   r   r   can_move  s    zPagePermissionTester.can_movec                 C  s   | j  S r   r  r   r   r   r   can_copy  s    zPagePermissionTester.can_copyc                 C  s   | j |ks|| j rdS | j |s8| j j|s8dS | jjsDdS | jjrPdS |  s\dS |	| j}d|j
vrvdS | j js| j  jdd rd|j
v S dS d S )NFTr  rc  rG  )r~   is_descendant_ofZis_child_ofr   r^  r	  r  r  r  rv  r  r(  rd  r   r   )r   destinationdestination_permsr   r   r   r^    s$    

z PagePermissionTester.can_move_toc                 C  st   |r| j |ks|| j rdS | jjs*dS | j j|s<dS | jjrHdS || j}|j sbdS d|j	vrpdS dS )NFTr  )
r~   r  r	  r  r  r]  r  rv  rX  r  )r   r  rk  r  r   r   r   can_copy_to  s$    


z PagePermissionTester.can_copy_toc                 C  s   | j  S r   r  r   r   r   r   can_view_revisions  s    z'PagePermissionTester.can_view_revisionsN)F)F)r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r^  r	  r
  r   r   r   r   rw    s(   
-

,
 rw  c                      sV   e Zd ZejdeddejdZdZG dd dZ	d fd	d
	Z
d fdd	Z  ZS )r  r   r~   view_restrictionsr  Zpassed_page_view_restrictionsc                   @  s   e Zd ZedZedZdS )zPageViewRestriction.Metazpage view restrictionzpage view restrictionsNr   r   r   r"  r   r  r   r   r   r   r     s   r   Nc              
     sb   | j j}| jdu }t jf i | |r^t||r4dnd|d| jtt| j	
| jdid dS )z
        Custom save handler to include logging.
        :param user: the user add/updating the view restriction
        :param specific_instance: the specific model instance the restriction applies to
        Nzwagtail.view_restriction.createzwagtail.view_restriction.editrestrictionr  r  r   r  r	  r   )r~   r   r  r   r  rL   restriction_typer*   r  RESTRICTION_CHOICESr   )r   r	  r   specific_instancer   r   r   r   r    s$    
zPageViewRestriction.savec              
     sh   | j j}|rVtjj| jdjdddd }t|d|d| jt	t
| j|did	 t jf i |S )
zv
        Custom delete handler to aid in logging.
        :param user: the user removing the view restriction
        r  r  Tr  r   zwagtail.view_restriction.deleter  r  r  )r~   r   r  r   r   r  r  rL   r  r*   r  r  r   r   r  )r   r	  r   r  Zremoved_restriction_typer   r   r   r    s,    zPageViewRestriction.delete)N)N)r   r   r   r   r!  r"  r  r~   Z$passed_view_restrictions_session_keyr   r  r  r   r   r   r   r   r    s   r  c                   @  sT   e Zd ZejdedejdddZejddedejdZ	d	d
 Z
G dd dZdS )WorkflowPager   r~   T)r   r   primary_keyuniqueWorkflowworkflow_pagesr  r   r   r   c                 C  s\   t jj| jdd}tjj|jddddj| jd}|ddD ]\}}|j||d	}q@|S )
z
        Returns a queryset of pages that are affected by this ``WorkflowPage`` link.

        This includes all descendants of the page excluding any that have other ``WorkflowPage``(s).
        Tr  r  r  r  r   Z
page__pathZpage__depth)r  Z
depth__gte)	r   r   r  r~   r  r   r  r   r   )r   Zdescendant_pagesZdescendant_workflow_pagesr   r   r   r   r   	get_pages4  s    zWorkflowPage.get_pagesc                   @  s   e Zd ZedZedZdS )zWorkflowPage.Metazworkflow pagezworkflow pagesNr  r   r   r   r   r   H  s   r   N)r   r   r   r   OneToOneFieldr"  r  r~   r!  r  r  r   r   r   r   r   r  %  s   r  c                   @  sH   e Zd ZejededejdddZej	ddedejdZ
d	d
 ZdS )r  Zwagtail_workflow_content_typer  T)r   r   r   r  r  r  Zworkflow_content_typesr  r  c                 C  s   t | j}d| d| j S )NzWorkflowContentType: z - )rB   r   r  )r   Zcontent_type_labelr   r   r   r  ]  s    
zWorkflowContentType.__str__N)r   r   r   r   r  r   r"  r  r   r!  r  r  r   r   r   r   r  M  s   r  c                   @  sR   e Zd ZedejedddZejdejeddddidZ	G d	d
 d
e
jZdS )WorkflowTaskr  Zworkflow_tasksr   r   r   Taskr  r  T)r   r   r   Zlimit_choices_toc                   @  s"   e Zd ZdgZedZedZdS )zWorkflowTask.Meta)r  r  zworkflow task orderzworkflow task ordersN)r   r   r   r  r"  r   r  r   r   r   r   r   q  s   r   N)r   r   r   r1   r   r  r"  r  r!  r  r  r   r   r   r   r   r  b  s   r  c                   @  s   e Zd Zdd ZdS )TaskQuerySetc                 C  s   | j ddS NTr  r   r   r   r   r   r  x  s    zTaskQuerySet.activeNr   r   r   r  r   r   r   r   r  w  s   r  c                      s.  e Zd ZejdeddZejeeddej	dZ
ejedded	d
Ze ZdgZdgZ fddZdd Zedd Zedd Zedd ZdZedd Zd2ddZejdd Zdd Zdd Zd d! Z d"d# Z!d$d% Z"d&d' Z#d(d) Z$d*d+ Z%ed,d- Z&ejd3d.d/Z'G d0d1 d1Z(  Z)S )4r  r  r   r  r  Zwagtail_tasksr  r  TzgActive tasks can be added to workflows. Deactivating a task does not remove it from existing workflows.r  c                   s0   t  j|i | | js,| js,tj| | _d S r   r   r   r  r  r   r   r   r   r  r   r   r   r     s    zTask.__init__c                 C  s   | j S r   r   r   r   r   r   r    s    zTask.__str__c                 C  s   t jj| dS )zH
        Returns all ``Workflow`` instances that use this task.
        Zworkflow_tasks__task)r  r   r   r   r   r   r   	workflows  s    zTask.workflowsc                 C  s   t j j| dS )zW
        Return a ``QuerySet``` of active workflows that this task is part of.
        r%  )r  r   r  r   r   r   r   r   active_workflows  s    zTask.active_workflowsc                 C  s   t | jjS )zi
        Returns the human-readable "verbose name" of this task model e.g "Group approval task".
        r_  r  r   r   r   r    s    zTask.get_verbose_nameNc                 C  s
   | j ptS r   )task_state_class	TaskStater   r   r   r   get_task_state_class  s    zTask.get_task_state_classc                 C  sJ   |   |d}tj|_|j |_| |_|  t	j
|jj|j|d |S )zf
        Start this task on the provided workflow state by creating an instance of TaskState.
        workflow_stater   r   r	  )r*  r)  r  r  r  r   r  r  r  rV   r  r   r   )r   r,  r	  
task_stater   r   r   start  s    z
Task.startc                 K  s>   |dkr|j f d|i| n|dkr:|jf d|i| dS )ze
        Performs an action on a task state determined by the ``action_name`` string passed.
        approver	  rejectN)r0  r1  )r   r.  r	  action_namer   r   r   r   	on_action  s    zTask.on_actionc                 C  s   dS )a  
        Returns ``True`` if a user who would not normally be able to access the editor for the
        object should be able to if the object is currently on this task.
        Note that returning ``False`` does not remove permissions from users who would otherwise have them.
        Fr   r   r   r	  r   r   r   r    s    zTask.user_can_access_editorc                 C  s   dS )z
        Returns ``True`` if the object should be locked to a given user's edits.
        This can be used to prevent editing by non-reviewers.
        Fr   r4  r   r   r   locked_for_user  s    zTask.locked_for_userc                 C  s   dS )a  
        Returns ``True`` if a user who would not normally be able to lock the object should be able to
        if the object is currently on this task.
        Note that returning ``False`` does not remove permissions from users who would otherwise have them.
        Fr   r4  r   r   r   r    s    zTask.user_can_lockc                 C  s   dS )a  
        Returns ``True`` if a user who would not normally be able to unlock the object should be able to
        if the object is currently on this task.
        Note that returning ``False`` does not remove permissions from users who would otherwise have them.
        Fr   r4  r   r   r   r    s    zTask.user_can_unlockc                 C  s   g S )a:  
        Get the list of action strings (name, verbose_name, whether the action requires additional data - see
        ``get_form_for_action``) for actions the current user can perform for this task on the given object.
        These strings should be the same as those able to be passed to ``on_action``.
        r   r4  r   r   r   get_actions  s    zTask.get_actionsc                 C  s   t S r   rG   r   r  r   r   r   get_form_for_action  s    zTask.get_form_for_actionc                 C  s   dS )Nr   r   r7  r   r   r   get_template_for_action  s    zTask.get_template_for_actionc                 K  s
   t j S )zGReturns a ``QuerySet`` of the task states the current user can moderate)r)  r   noner   r	  r   r   r   r   !get_task_states_user_can_moderate  s    z&Task.get_task_states_user_can_moderatec                 C  s   dS )z/
        Returns the task description.
        r   r   r  r   r   r   get_description  s    zTask.get_descriptionc                 C  s:   d| _ |   tjj| tjd}|D ]}|j|d q$dS )ze
        Set ``active`` to False and cancel all in progress task states linked to this task.
        F)r  r  r  N)r  r  r)  r   r   r  cancelr   r	  Zin_progress_statesr  r   r   r   
deactivate  s    zTask.deactivatec                   @  s   e Zd ZedZedZdS )z	Task.Metar  tasksNr  r   r   r   r   r     s   r   )N)N)*r   r   r   r   r  r"  r   r!  r   r  r   rP  r  TaskManagerr   admin_form_fieldsZ"admin_form_readonly_on_edit_fieldsr   r  r%  r&  r'  r&  r  r(  r*  r/  r   r  r3  r  r5  r  r  r6  r8  r9  r<  r=  r@  r   r   r   r   r   r   r    sX   






	
r  c                   @  s   e Zd Zdd ZdS )WorkflowManagerc                 C  s   | j ddS r  r!  r   r   r   r   r  "  s    zWorkflowManager.activeNr"  r   r   r   r   rD  !  s   rD  c                   @  s   e Zd ZejdeddZejeddeddZe	 Z
dd	 Zed
d Zejdd ZejdddZdd ZG dd dZdS )AbstractWorkflowr  r   r  r  TzyActive workflows can be added to pages/snippets. Deactivating a workflow does not remove it from existing pages/snippets.r  c                 C  s   | j S r   r$  r   r   r   r   r  1  s    zAbstractWorkflow.__str__c                 C  s   t jj| ddS )zI
        Returns all ``Task`` instances linked to this workflow.
        )workflow_tasks__workflowworkflow_tasks__sort_order)r  r   r   r   r   r   r   r   rA  4  s    zAbstractWorkflow.tasksc              
   C  s   t | | t|j| t j|d}|  |j|d tj	|j
||d d}|jrj|jjj|jjjd}t|dd| j| j|j||jr|jjnddi| |d	 |S )
zT
        Initiates a workflow by creating an instance of ``WorkflowState``.
        )r   r  r   r  r  requested_byr  r-  Nr  r  zwagtail.workflow.startr  )r  r  r  nexttask_state_idr   r  r   r  r	  )r  r   r   r  r   r  r  r   rZ   r  r   r  r  r  r   rL   r  r   )r   r   r	  r  next_task_datar   r   r   r/  =  sB    
zAbstractWorkflow.startNc                 C  s^   d| _ tjj| tjd}|D ]}|j|d qtjj| d  tjj| d  | 	  dS )z
        Sets the workflow as inactive, and cancels all in progress instances of ``WorkflowState`` linked to this workflow.
        F)r  r  r  )r  N)
r  r  r   r   r  r>  r  r  r  r  r?  r   r   r   r@  h  s    zAbstractWorkflow.deactivatec                 C  s*   t j }| j D ]}|| O }q|S )zT
        Returns a queryset of all the pages that this Workflow applies to.
        )r   r   r:  r  r  r  )r   r   Zworkflow_pager   r   r   	all_pagesw  s    
zAbstractWorkflow.all_pagesc                   @  s    e Zd ZedZedZdZdS )zAbstractWorkflow.Metar  r&  TN)r   r   r   r"  r   r  r   r   r   r   r   r     s   r   )N)r   r   r   r   r  r"  r   rP  r  rD  r   r  r%  rA  r   r  r/  r@  rN  r   r   r   r   r   rE  &  s$   

*rE  c                   @  s   e Zd ZdS )r  Nr   r   r   r   r   r   r   r    s   r  c                      s   e Zd ZejeededdZej	dg Z	de
jiZd fdd	Zdd Zd	d
 Zdd Zdd Zdd Zdd Zdd Zedd ZG dd dZ  ZS )AbstractGroupApprovalTaskr  z`Pages/snippets at this step in a workflow will be moderated or approved by these groups of users)r   r  Nc                   sj   t |jtrZ|jjrZ|jjjj| j d sZd|j_d |j_d |j_	|jj
g dd t j||dS )Nr  F)r  r  r  r   r  )r   r  r  r  r  r   r  r   r  r  r  r   r/  )r   r,  r	  r   r   r   r/    s    


zAbstractGroupApprovalTask.startc                 C  sR   d}t ||i  }s t||| | j|vrH| jj|j d || j< || j S )NZ_group_approval_task_checksr  )r  rc  r   r  r   r  r   )r   r	  Z
cache_attrZchecks_cacher   r   r   _user_in_groups  s    
z)AbstractGroupApprovalTask._user_in_groupsc                 C  s   |j p| |S r   r  rQ  r4  r   r   r   r    s    z0AbstractGroupApprovalTask.user_can_access_editorc                 C  s   |j p| | S r   rR  r4  r   r   r   r5    s    z)AbstractGroupApprovalTask.locked_for_userc                 C  s
   |  |S r   )rQ  r4  r   r   r   r    s    z'AbstractGroupApprovalTask.user_can_lockc                 C  s   dS r/  r   r4  r   r   r   r    s    z)AbstractGroupApprovalTask.user_can_unlockc                 C  s<   |j s| |r8dtddfdtddfdtddfgS g S )Nr1  zRequest changesTr0  ZApproveFzApprove with comment)r  rQ  r"  r4  r   r   r   r6    s    z%AbstractGroupApprovalTask.get_actionsc                 K  s.   |j s| |r | jjtjdS tj S d S )Nr  )r  rQ  task_statesr   r)  r  r   r:  r;  r   r   r   r<    s    z;AbstractGroupApprovalTask.get_task_states_user_can_moderatec                 C  s   t dS )Nz:Members of the chosen Wagtail Groups can approve this task)r"  r  r   r   r   r=    s    z)AbstractGroupApprovalTask.get_descriptionc                   @  s    e Zd ZdZedZedZdS )zAbstractGroupApprovalTask.MetaTzGroup approval taskzGroup approval tasksN)r   r   r   r   r"  r   r  r   r   r   r   r     s   r   )N)r   r   r   r   ZManyToManyFieldr   r"  r  r  rC  r   ZCheckboxSelectMultipleZadmin_form_widgetsr/  rQ  r  r5  r  r  r6  r<  r&  r=  r   r   r   r   r   r   rP    s*   

rP  c                   @  s   e Zd ZdS )GroupApprovalTaskNrO  r   r   r   r   rT    s   rT  c                   @  s   e Zd Zdd Zdd ZdS )WorkflowStateQuerySetc                 C  s   |  ttjdttjdB S )ze
        Filters to only ``STATUS_IN_PROGRESS`` and ``STATUS_NEEDS_CHANGES`` WorkflowStates.
        r  )r   r   r  r  STATUS_NEEDS_CHANGESr   r   r   r   r    s
    

zWorkflowStateQuerySet.activec                 C  sR   z| j | t|jdW S  tyL   | j tjj|ddt|jd Y S 0 dS )zH
        Filters to only WorkflowStates for the given instance.
        r  Fr   r   Nr  r  r   r   r   r    s    z"WorkflowStateQuerySet.for_instanceN)r   r   r   r  r  r   r   r   r   rU    s   	rU  c                	      s  e Zd ZdZdZdZdZdZeedfeedfeedfeed	ffZ	e
jee
jd
dZe
jee
jd
dZe
jdeddZeddddZde_e
jde
jedddZe
jje	eddedZe
jdeddZe
jejedddde
jddZe
jd e
jdded!d"Ze e!ed#d$Z"e# Z$ fd%d&Z% fd'd(Z&d)d* Z'dJd,d-Z(d.d/ Z)dKd0d1Z*e+d2d3 Z,d4d5 Z-dLd6d7Z.e/j0dMd8d9Z1d:d; Z2d<d= Z3d>d? Z4d@dA Z5dBdC Z6e+dDdE Z7e+dFdG Z8G dHdI dIZ9  Z:S )Nr  z5Tracks the status of a started Workflow on an object.in_progressapprovedneeds_changes	cancelledIn progressApprovedzNeeds changes	Cancelledr   r  r  r  r  r  r   Fr   Tr  r  r  r  r  2   choicesr   r  r*  r  )auto_now_addr   zrequested byZrequested_workflowsr  r)  zcurrent task state)r   r   r   r   ZWAGTAIL_FINISH_WORKFLOW_ACTIONz(wagtail.workflows.publish_workflow_statec                   sR   t    | j| j| jfv rNtj j| j	| j
dj| jd rNttdd S )Nr  r   zSThere may only be one in progress or needs changes workflow state per page/snippet.)r   r  r  r  rV  r  r   r  r   r  r   r   r   r   r   r"  r   r   r   r   r  L  s    

	zWorkflowState.cleanc                   s   |    t j|i |S r   )r  r   r  r  r   r   r   r  `  s    zWorkflowState.savec                 C  s"   t d| j| jjj| j| jd S )NzFWorkflow '%(workflow_name)s' on %(model_name)s '%(title)s': %(status)s)Zworkflow_nameZ
model_namer  r  )r"  r  r  r   r   r  r   r   r   r   r  d  s    zWorkflowState.__str__Nc                 C  s   | j | jkrt| jj}| j}d| _| j| _ |   | j}t|t	rL| jj
}t|dd| j| jj| j |j|jj|jjddi||d | j||jdS )zdPut a STATUS_NEEDS_CHANGES workflow state back into STATUS_IN_PROGRESS, and restart the current taskNzwagtail.workflow.resumer  rI  r  r  r  rK  r  rL  r	  	next_task)r  rV  r   r  r  r  r  r  r   r   r   rL   workflow_idr  r   r  r  r   )r   r	  r  r  r   r   r   r   resumen  s6    
zWorkflowState.resumec                 C  sv   t | jtr$| jjr$| jj|kr$dS || jkpt|t| jdd kpt| jot| jj| jj	kotddd | jj
| j|D v S )NFr  r0  c                 S  s   g | ]}|d  qS r?  r   )r   r  r   r   r   r     s   z1WorkflowState.user_can_cancel.<locals>.<listcomp>)r   r  r  r  r  rH  r  r  r  r  r  r6  r   r	  r   r   r   user_can_cancel  s.    


zWorkflowState.user_can_cancelc                 C  s   | j | jkrdS z| jj }W n ty2   d}Y n0 |tjkrb| j| _ |   tj	| j
| |d nj|sn|  }|r| jr| jj | jjkr|jj| |d| _|   | jj | jjkr| j|d n| j|d dS )z
        Checks the status of the current task, and progresses (or ends) the workflow if appropriate.
        If the workflow progresses, next_task will be used to start a specific task next if provided.
        Nr-  r  )r  r  r  r   r)  STATUS_REJECTEDrV  r  rY   r  r   get_next_taskr   r/  r   finish)r   r	  rd  Zcurrent_statusr   r   r   r     s6    

zWorkflowState.updatec                 C  sB   | j ttjdttjdB }ttddr>|j| j	 d}|S )Nr  +WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDITF)r  )
rS  r   r   r)  STATUS_APPROVEDSTATUS_SKIPPEDr  r   r  r   )r   successful_task_statesr   r   r   ro    s    z$WorkflowState.successful_task_statesc                 C  s&   t jj| jddj| jdd S z^
        Returns the next active task, which has not been either approved or skipped.
        T)rF  r  )Ztask_states__inrG  )r  r   r   r  r   ro  r   r   r   r   r   r   rj    s    zWorkflowState.get_next_taskc                 C  s   | j | j| jfvrt| j| _ |   | j}t|tr>| jj	}t
|dd| j| jj| j | jj| jjj| jjjddi| jj|d | jjtjdD ]}|j	j|d qtj| j| |d d	S )
zCancels the workflow statezwagtail.workflow.cancelr  rI  rb  rL  r  r  r-  N)r  r  rV  r   STATUS_CANCELLEDr  r  r   r   r   rL   re  r  r   r  r  r  r  rS  r   r)  r>  rX   r  r   )r   r	  r   r  r   r   r   r>    s4    
zWorkflowState.cancelc                 C  sB   | j | jkrt| j| _ |   | j|d tj| j| |d dS )z}
        Finishes a successful in progress workflow, marking it as approved and performing the ``on_finish`` action.
        r  r-  N)	r  r  r   rm  r  	on_finishrW   r  r   rg  r   r   r   rk    s    zWorkflowState.finishc                 C  s0   t jj| t jd}|D ]}|jd|id qdS )zn
        Creates copies of previously approved task states with revision set to a different revision.
        )r,  r  r  )rj  N)r)  r   r   rm  r   )r   r  Zapproved_statesr  r   r   r   %copy_approved_task_states_to_revision  s
    z3WorkflowState.copy_approved_task_states_to_revisionc                 C  s(   t jj| j| j| jjdddddS )zi
        Returns all revisions associated with task states linked to the current workflow state.
        revision_idTr  )r  r   r  r   )r   r   r   r  r   rS  r  r   r   r   r   r   r   !  s    zWorkflowState.revisionsc                 C  sJ   t jj| jd}ttddrF|  ddjddd	 }|j|d	}|S )
z^
        Returns the set of task states whose status applies to the current revision.
        )Zworkflow_state_idrl  Fr  r  r  Tr  )rt  )
r)  r   r   r  r  r   r   r   r  r   )r   rS  r  r   r   r   _get_applicable_task_states+  s    z)WorkflowState._get_applicable_task_statesc                 C  sn   |   }t| jjjt|jtdddd	ddd d}t
tj}|D ]}||jtd	|_qP|S )
a  
        Returns a list of Task objects that are linked with this workflow state's
        workflow. The status of that task in this workflow state is annotated in the
        ``.status`` field. And a displayable version of that status is annotated in the
        ``.status_display`` field.

        This is different to querying TaskState as it also returns tasks that haven't
        been started yet (so won't have a TaskState).
        r  Ztask_id-started_atr  r  Nr^   r  zNot started)ru  r   r  rA  annotater   r   r   r   r   r  r)  STATUS_CHOICESr   r  r"  Zstatus_display)r   rS  rA  Zstatus_choicesr  r   r   r   all_tasks_with_status<  s(    
z#WorkflowState.all_tasks_with_statusc                 C  sl   |   }t| jjjt|jtdddd	ddd d}dd	 |D }|D ]}|
|j|_qT|S )
a6  
        Returns a list of Task objects that are linked with this WorkflowState's
        workflow, and have the latest task state.

        In a "Submit for moderation -> reject at step 1 -> resubmit -> accept" workflow, this ensures
        the task list reflects the accept, rather than the reject.
        r  rv  rw  r  Nr^   )rK  c                 S  s   i | ]}|j |qS r   r  )r   r.  r   r   r   r   r  r   z6WorkflowState.all_tasks_with_state.<locals>.<dictcomp>)ru  r   r  rA  rx  r   r   r   r   r   r   rK  r.  )r   rS  rA  r  r   r   r   all_tasks_with_state\  s(    z"WorkflowState.all_tasks_with_statec                 C  s   | j | j| jfvS r   )r  rm  rq  r   r   r   r   r  y  s    zWorkflowState.is_activec                 C  s2   t jj| jddj| jdd }|  |kS rp  )	r  r   r   r  r   ro  r   lastrj  )r   Z	last_taskr   r   r   is_at_final_task}  s    zWorkflowState.is_at_final_taskc                   @  s\   e Zd ZedZedZejddgeddddgZ	ej
d	dgd
dej
ddgddgZdS )zWorkflowState.MetazWorkflow statezWorkflow statesr  r   )rW  rY  )Z
status__inZunique_in_progress_workflow)r  	conditionr   r   Zworkflowstate_ct_id_idxr  Zworkflowstate_base_ct_id_idxN)r   r   r   r"  r   r  r   r  r   r  r  r  r   r   r   r   r     s"   r   )N)NN)N)N);r   r   r   r   r  rm  rV  rq  r"  ry  r   r!  r   r  r   r  r  r   r
   r  r  r  r  r  rQ  r  r   r  r#  rH  r  r  r-   r  rr  WorkflowStateManagerr   r  r  r  rf  rh  r   r%  ro  rj  r>  r   r  rk  rs  r   ru  rz  r{  r  r}  r   r   r   r   r   r   r    s   



		

"
(

#

 

r  c                   @  s   e Zd Zdd ZdS )BaseTaskStateManagerc                 C  s:   t jjdd }tj }|D ]}||j|dB }q |S )NTr   r  )r  r   r   r   r)  r:  r<  )r   r	  rA  Zstatesr  r   r   r   reviewable_by  s
    
z"BaseTaskStateManager.reviewable_byN)r   r   r   r  r   r   r   r   r    s   r  c                   @  s   e Zd Zdd ZdS )TaskStateQuerySetc                 C  sR   z| j | t|jdW S  tyL   | j tjj|ddt|jd Y S 0 dS )zC
        Filters to only TaskStates for the given instance
        )Z!workflow_state__base_content_typeworkflow_state__object_idFr   )Zworkflow_state__content_typer  Nr  r  r   r   r   r    s    zTaskStateQuerySet.for_instanceN)r   r   r   r  r   r   r   r   r    s   r  c                      s  e Zd ZdZdZdZdZdZdZee	dfee	dfee	d	fee	d
fee	dffZ
ejdeje	dddZejdeje	dddZejdeje	dddZejje
e	ddedZeje	dddZeje	ddddZejeje	dddejddZejddZejee	d d!ejd"Zg Zd#gZ e! Z" fd$d%Z#d&d' Z$e%j&d;d*d+Z'e%j&d<d,d-Z(e)d.d/ Z*e%j&d=d1d2Z+d>d3d4Z,d5d6 Z-d7d8 Z.G d9d: d:Z/  Z0S )?r)  z<Tracks the status of a given Task for a particular revision.rW  rX  ZrejectedskippedrZ  r[  r\  ZRejectedZSkippedr]  r  zworkflow staterS  r  r   r  r  r  r  r^  r_  z
started atT)r   ra  zfinished atr-  zfinished byZfinished_task_statesr  r   r  Zwagtail_task_statesr  r  c                   s0   t  j|i | | js,| js,tj| | _d S r   r#  r  r   r   r   r     s    zTaskState.__init__c                 C  s   t d| j| j| jd S )Nz@Task '%(task_name)s' on Revision '%(revision_info)s': %(status)s)Z	task_nameZrevision_infor  )r"  r  r  r  r   r   r   r   r    s
    zTaskState.__str__Nr   c                 C  sn   | j | jkrt| j| _ t | _|| _|| _| 	  | 
|d |rT| jj|d tj| jj| j|d | S )zG
        Approve the task state and update the workflow state.
        r0  r  r-  )r  r  r   rm  r&   r  finished_atfinished_byr  r  log_state_change_actionr,  r   rS   r  r   r   r   r	  r   r  r   r   r   r0    s    
zTaskState.approvec                 C  sn   | j | jkrt| j| _ t | _|| _|| _| 	  | 
|d |rT| jj|d tj| jj| j|d | S )zF
        Reject the task state and update the workflow state.
        r1  r  r-  )r  r  r   ri  r&   r  r  r  r  r  r  r,  r   rU   r  r   r   r  r   r   r   r1  *  s    
zTaskState.rejectc                 C  sJ   t jj| jddd}d}|D ] }|j| jkr<|j}q$|r$ qFq$|S )z
        Finds the first chronological started_at for successive TaskStates - ie started_at if the task had not been restarted.
        r+  rw  r  N)r)  r   r   r,  r   r  r  
started_at)r   rS  r  r.  r   r   r   task_type_started_at@  s    zTaskState.task_type_started_atFc                 C  sh   | j | _t | _|| _|| _|   |r@| jj	|| j
jd n| jj	|d tj| jj| j|d | S )a  
        Cancel the task state and update the workflow state.
        If ``resume`` is set to True, then upon update the workflow state is passed the current task as ``next_task``,
        causing it to start a new task state on the current task if possible.
        rc  r  r-  )rq  r  r&   r  r  r  r  r  r,  r   r  r   rT   r  r   )r   r	  rf  r  r   r   r   r>  R  s    
zTaskState.cancelc                 C  s@   | j | j |pg  }t| j||\}}|  t| ||d |S )z
        Copy this task state, excluding the attributes in the ``exclude_fields`` list and updating any attributes
        to values specified in the ``update_attrs`` dictionary of ``attribute``: ``new value`` pairs.
        r"  )r$  r  rc   r   r  rd   )r   rj  r#  r   r&  r   r   r   r   g  s    zTaskState.copyc                 C  s   | j S )z
        Returns a string that is displayed in workflow history.

        This could be a comment by the reviewer, or generated.
        Use mark_safe to return HTML.
        )r  r   r   r   r   get_commentv  s    zTaskState.get_commentc                 C  s~   | j  }| j }d}|r*|j|jd}t|d| || jjj| jjj| j| j| j	j| j	jd|d| 
 d| j d dS )z!Log the approval/rejection actionNrI  zwagtail.workflow.)r  r  r  rK  r  rJ  )r  r  )r   r  r	  r   r  )r  r   r,  rj  r  r   rL   r  r  r  r  )r   r	  r  r   rd  rM  r   r   r   r    s,    

z!TaskState.log_state_change_actionc                   @  s   e Zd ZedZedZdS )zTaskState.Metaz
Task statezTask statesNr  r   r   r   r   r     s   r   )NTr   )NTr   )NFr   )NN)1r   r   r   r   r  rm  ri  rn  rq  r"  ry  r   r!  r  r,  r  r  r  r  r  rQ  r  r  r   r  r#  r  r  r  r   r   r  r$  TaskStateManagerr   r   r  r   r  r0  r1  r,   r  r>  r   r  r  r   r   r   r   r   r   r)    s   








	r)  c                   @  s   e Zd Zdd Zdd ZdS )PageLogEntryQuerySetc                 C  s"   |   rtjtjhS t S d S r   )r   r   r   r   r   r   r  r   r   r   r   get_content_type_ids  s    z)PageLogEntryQuerySet.get_content_type_idsc                 C  s    |t jtkr| S |  S d S r   )r   r   r   r   r:  )r   r   r   r   r   filter_on_content_type  s    z+PageLogEntryQuerySet.filter_on_content_typeN)r   r   r   r  r  r   r   r   r   r    s   r  c                      s<   e Zd Zdd Zdd Z fddZdd Zd	d
 Z  ZS )PageLogEntryManagerc                 C  s   t | j| jdS r  )r  r   r  r   r   r   r   r     s    z PageLogEntryManager.get_querysetc                 C  s
   |j  S r   )r  r  r  r   r   r   get_instance_title  s    z&PageLogEntryManager.get_instance_titlec                   s"   |j |d t j||fi |S Nr  )r   r   r  )r   r   r  r   r   r   r   r    s    zPageLogEntryManager.log_actionc                 C  s|   ddl m} ||}t|jdddd}t |}|jsN|	 sN|
 rp|tttjjdddd	B }tj|S )
Nr   r  r   Tr  )Zpage__in)Zdeletedpage_idr  )r  r  explorable_instancesr   r  r   r   rv  r  r  r  r   PageLogEntryr   r   r   )r   r	  r  r  qZroot_page_permissionsr   r   r   viewable_by_user  s     
z$PageLogEntryManager.viewable_by_userc                 C  s   | j |dS r  r!  r  r   r   r   r    s    z PageLogEntryManager.for_instance)	r   r   r   r   r  r  r  r  r   r   r   r   r   r    s
   r  c                      s\   e Zd ZejdejdddZe ZG dd dZ	dd Z
ed	d
 Ze fddZ  ZS )r  zwagtailcore.PageFr   )r   Zdb_constraintr   c                   @  s$   e Zd ZddgZedZedZdS )zPageLogEntry.Metaz
-timestampr  zpage log entryzpage log entriesN)r   r   r   r  r"  r   r  r   r   r   r   r     s   r   c                 C  s   d| j | j|  | jf S )Nz(PageLogEntry %d: '%s' on '%s' with id %s)r   r  Zobject_verbose_namer  r   r   r   r   r    s    zPageLogEntry.__str__c                 C  s   | j S r   )r  r   r   r   r   r     s    zPageLogEntry.object_idc                   s   | j dkrtdS t jS d S )Nr  zDraft saved)r  r"  r   messager   r   r   r   r    s    
zPageLogEntry.message)r   r   r   r   r!  Z
DO_NOTHINGr~   r  r   r   r  r,   r   r  r   r   r   r   r   r    s   
r  c                      s   e Zd ZdZeeejedZ	ej
ejejedZe Ze ZejddZejddZejddZej
eejddddZejddd	Zej
ejejd
dddZG dd dZdd Zd fdd	Zd ddZdd Zdd Zdd Z dd Z!dd Z"  Z#S )!CommentzE
    A comment on a field, or a field within a streamfield block
    r  Tr  ra  Zauto_nowr  )r   r   r   r   )r   r   Zcomments_resolvedc                   @  s   e Zd ZedZedZdS )zComment.Metar  r  Nr  r   r   r   r   r   "  s   r   c                 C  s   d | j| j| jS )Nz&Comment on Page '{}', left by {}: '{}')r   r~   r	  textr   r   r   r   r  &  s    zComment.__str__Fc                   s|   | dd }|sf|rd|vrf| jrD|r*|n| j }dd |D }n"| j}t jf i |}|| _|S t jf d|i|S )Nr  r  c                 S  s   g | ]}|j d vr|j qS )>   r  r  r$  r   r   r   r   r   6  s   
z Comment.save.<locals>.<listcomp>)r  r  r   Z
get_fieldsr  r   r  )r   Zupdate_positionr   r  r  r  r   r   r   r  +  s"    zComment.saveNc              
   C  s*   t | j|||d| j| j| jdid d S )Nr  r  r  r  r   r  r	  r  r   )rL   r~   r   r  r  r   r  Zpage_revisionr	  r   r   r   _logC  s    zComment._logc                 K  s   | j di | d S )Nwagtail.comments.create)r  r  r   r   r   r   r   
log_createR  s    zComment.log_createc                 K  s   | j di | d S )Nwagtail.comments.edit)r  r  r  r   r   r   log_editU  s    zComment.log_editc                 K  s   | j di | d S )Nwagtail.comments.resolve)r  r  r  r   r   r   log_resolveX  s    zComment.log_resolvec                 K  s   | j di | d S )Nwagtail.comments.delete)r  r  r  r   r   r   
log_delete[  s    zComment.log_deletec                 C  sj   | j d^}}z|j|}W n ty4   Y dS 0 |s>dS t|tsLdS t||}|||}t	|S )z
        Return True if this comment's contentpath corresponds to a valid field or
        StreamField block on the given page object.
        rk  FT)
r  r  r   	get_fieldr   r   rF   r  Zget_block_by_content_pathr  )r   r~   
field_name	remainderr   Zstream_valueblockr   r   r   has_valid_contentpath^  s    

zComment.has_valid_contentpath)F)NN)$r   r   r   r   r1   r   r   r  r  r~   r!  r   r  r	  r  r  r  r  rQ  r  
updated_atr   r  Zresolved_atr#  Zresolved_byr   r  r  r  r  r  r  r  r  r   r   r   r   r   r    sL   
r  c                   @  s   e Zd ZeeejddZeje	j
ejddZe ZejddZejddZG dd dZd	d
 ZdddZdd Zdd Zdd ZdS )CommentReplyZrepliesr  Zcomment_repliesTr  r  c                   @  s   e Zd ZedZedZdS )zCommentReply.Metazcomment replyzcomment repliesNr  r   r   r   r   r     s   r   c                 C  s   d| j  d| j dS )NzCommentReply left by 'z': '')r	  r  r   r   r   r   r    s    zCommentReply.__str__Nc              	   C  s>   t | jj|||| jj| jj| jjd| j| jddd d S )Nr  )r  r  )r  Zreplyr  )rL   r  r~   r   r  r  r  r   r   r   r    s    zCommentReply._logc                 K  s   | j di | d S )Nwagtail.comments.create_reply)r  r  r  r   r   r   r    s    zCommentReply.log_createc                 K  s   | j di | d S )Nwagtail.comments.edit_reply)r  r  r  r   r   r   r    s    zCommentReply.log_editc                 K  s   | j di | d S )Nwagtail.comments.delete_reply)r  r  r  r   r   r   r    s    zCommentReply.log_delete)NN)r   r   r   r1   r  r   r  r  r!  r   r  r	  r  r  rQ  r  r  r   r  r  r  r  r  r   r   r   r   r  w  s   
r  c                   @  sL   e Zd ZejejejddZeje	ejddZ
e ZdZG dd dZdS )PageSubscriptionZpage_subscriptionsr  ZsubscribersTc                   @  s   e Zd ZdgZdS )zPageSubscription.Meta)r~   r	  N)r   r   r   r  r   r   r   r   r     s   r   N)r   r   r   r   r!  r   r  r  r	  r   r~   rP  Zcomment_notificationsr  r   r   r   r   r   r    s   r  )T(   r   
__future__r   	functoolsloggingr   r  ior   urllib.parser   warningsr   Zdjangor   Zdjango.confr   Zdjango.contrib.auth.modelsr   r	   Z"django.contrib.contenttypes.fieldsr
   r   Z"django.contrib.contenttypes.modelsr   Zdjango.corer   Zdjango.core.exceptionsr   r   r   r   Zdjango.core.handlers.baser   Zdjango.core.handlers.wsgir   Zdjango.core.serializers.jsonr   Z	django.dbr   r   Zdjango.db.modelsr   r   Zdjango.db.models.expressionsr   r   Zdjango.db.models.functionsr   r   Zdjango.dispatchr   Zdjango.httpr   r   r    r!   Zdjango.http.requestr"   Zdjango.template.responser#   Zdjango.urlsr$   r%   Zdjango.utilsr&   r'   Zdjango.utils.cacher(   Zdjango.utils.encodingr)   r*   Zdjango.utils.functionalr+   r,   Zdjango.utils.module_loadingr-   Zdjango.utils.textr.   r/   Zdjango.utils.translationr0   r"  Zmodelcluster.fieldsr1   Zmodelcluster.modelsr2   r3   r4   Ztreebeard.mp_treer5   Z$wagtail.actions.copy_for_translationr6   Zwagtail.actions.copy_pager7   Zwagtail.actions.create_aliasr8   Zwagtail.actions.delete_pager9   Zwagtail.actions.move_pager:   Z%wagtail.actions.publish_page_revisionr;   Z wagtail.actions.publish_revisionr<   Zwagtail.actions.unpublishr=   Zwagtail.actions.unpublish_pager>   Zwagtail.compatr?   Zwagtail.coreutilsr@   rA   rB   rC   rD   rE   Zwagtail.fieldsrF   Zwagtail.formsrH   Zwagtail.locksrI   rJ   rK   Zwagtail.log_actionsrL   Zwagtail.queryrM   rN   Zwagtail.searchrO   Zwagtail.signalsrP   rQ   rR   rS   rT   rU   rV   rW   rX   rY   rZ   Zwagtail.url_routingr[   Zwagtail.utils.deprecationr\   Zwagtail.utils.timestampsr]   Z	audit_logr_   r`   ra   rb   copyingrc   rd   re   Zi18nrf   rg   rh   ri   rj   rk   rl   Zmediarm   rn   ro   rp   rq   rr   rs   rt   ru   Zpanelsrv   rw   Zreference_indexrx   sitesry   rz   r{   r   r|   r  r}   	getLoggerr  r(  r  r  r   r   r   r   r   cacher   Managerr   Zfrom_querysetr  baseZ	ModelBaser   ZModelr   r'  rT  r  r  r  r  ZPAGE_PERMISSION_TYPE_CHOICESZPAGE_PERMISSION_CODENAMESZIndexedr   r  ZQuerySetr  r  r  r   r  r  rw  r  r  r  r  r  rB  r  rD  rE  r  rP  rT  rU  r  r  r  r  r  r)  r  r  r  r  r  r  r   r   r   r   <module>   sT   4$	,




@
! = <  J @
	             U	 #)  C( #bW   	
 ]$$/