1# -*- coding: utf-8 -*- 2import copy 3from collections import OrderedDict 4from logging import getLogger 5from os.path import join 6 7from django.contrib.sites.models import Site 8from django.urls import reverse 9from django.db import models 10from django.db.models.base import ModelState 11from django.db.models.functions import Concat 12from django.utils.encoding import force_text, python_2_unicode_compatible 13from django.utils.functional import cached_property 14from django.utils.timezone import now 15from django.utils.translation import ( 16 get_language, 17 override as force_language, 18 ugettext_lazy as _, 19) 20 21from cms import constants 22from cms.constants import PUBLISHER_STATE_DEFAULT, PUBLISHER_STATE_PENDING, PUBLISHER_STATE_DIRTY, TEMPLATE_INHERITANCE_MAGIC 23from cms.exceptions import PublicIsUnmodifiable, PublicVersionNeeded, LanguageError 24from cms.models.managers import PageManager, PageNodeManager 25from cms.utils import i18n 26from cms.utils.compat import DJANGO_1_11 27from cms.utils.conf import get_cms_setting 28from cms.utils.page import get_clean_username 29from cms.utils.i18n import get_current_language 30 31from menus.menu_pool import menu_pool 32 33from treebeard.mp_tree import MP_Node 34 35 36logger = getLogger(__name__) 37 38 39@python_2_unicode_compatible 40class TreeNode(MP_Node): 41 42 parent = models.ForeignKey( 43 'self', 44 on_delete=models.CASCADE, 45 blank=True, 46 null=True, 47 related_name='children', 48 db_index=True, 49 ) 50 site = models.ForeignKey( 51 Site, 52 on_delete=models.CASCADE, 53 verbose_name=_("site"), 54 related_name='djangocms_nodes', 55 db_index=True, 56 ) 57 58 objects = PageNodeManager() 59 60 class Meta: 61 app_label = 'cms' 62 ordering = ('path',) 63 default_permissions = [] 64 65 def __str__(self): 66 return self.path 67 68 @cached_property 69 def item(self): 70 return self.get_item() 71 72 def get_item(self): 73 # Paving the way... 74 return Page.objects.get(node=self, publisher_is_draft=True) 75 76 @property 77 def is_branch(self): 78 return bool(self.numchild) 79 80 def get_ancestor_paths(self): 81 paths = frozenset( 82 self.path[0:pos] 83 for pos in range(0, len(self.path), self.steplen)[1:] 84 ) 85 return paths 86 87 def add_child(self, **kwargs): 88 if len(kwargs) == 1 and 'instance' in kwargs: 89 kwargs['instance'].parent = self 90 else: 91 kwargs['parent'] = self 92 return super(TreeNode, self).add_child(**kwargs) 93 94 def add_sibling(self, pos=None, *args, **kwargs): 95 if len(kwargs) == 1 and 'instance' in kwargs: 96 kwargs['instance'].parent_id = self.parent_id 97 else: 98 kwargs['parent_id'] = self.parent_id 99 return super(TreeNode, self).add_sibling(pos, *args, **kwargs) 100 101 def update(self, **data): 102 cls = self.__class__ 103 cls.objects.filter(pk=self.pk).update(**data) 104 105 for field, value in data.items(): 106 setattr(self, field, value) 107 return 108 109 def get_cached_ancestors(self): 110 if self._has_cached_hierarchy(): 111 return self._ancestors 112 return [] 113 114 def get_cached_descendants(self): 115 if self._has_cached_hierarchy(): 116 return self._descendants 117 return [] 118 119 def _reload(self): 120 """ 121 Reload a page node from the database 122 """ 123 return self.__class__.objects.get(pk=self.pk) 124 125 def _has_cached_hierarchy(self): 126 return hasattr(self, '_descendants') and hasattr(self, '_ancestors') 127 128 def _set_hierarchy(self, nodes, ancestors=None): 129 if self.is_branch: 130 self._descendants = [node for node in nodes 131 if node.path.startswith(self.path) 132 and node.depth > self.depth] 133 else: 134 self._descendants = [] 135 136 if self.is_root(): 137 self._ancestors = [] 138 else: 139 self._ancestors = ancestors 140 141 children = (node for node in self._descendants 142 if node.depth == self.depth + 1) 143 144 for child in children: 145 child._set_hierarchy(self._descendants, ancestors=([self] + self._ancestors)) 146 147 148@python_2_unicode_compatible 149class Page(models.Model): 150 """ 151 A simple hierarchical page model 152 """ 153 LIMIT_VISIBILITY_IN_MENU_CHOICES = ( 154 (constants.VISIBILITY_USERS, _('for logged in users only')), 155 (constants.VISIBILITY_ANONYMOUS, _('for anonymous users only')), 156 ) 157 TEMPLATE_DEFAULT = TEMPLATE_INHERITANCE_MAGIC if get_cms_setting('TEMPLATE_INHERITANCE') else get_cms_setting('TEMPLATES')[0][0] 158 159 X_FRAME_OPTIONS_INHERIT = constants.X_FRAME_OPTIONS_INHERIT 160 X_FRAME_OPTIONS_DENY = constants.X_FRAME_OPTIONS_DENY 161 X_FRAME_OPTIONS_SAMEORIGIN = constants.X_FRAME_OPTIONS_SAMEORIGIN 162 X_FRAME_OPTIONS_ALLOW = constants.X_FRAME_OPTIONS_ALLOW 163 X_FRAME_OPTIONS_CHOICES = ( 164 (constants.X_FRAME_OPTIONS_INHERIT, _('Inherit from parent page')), 165 (constants.X_FRAME_OPTIONS_DENY, _('Deny')), 166 (constants.X_FRAME_OPTIONS_SAMEORIGIN, _('Only this website')), 167 (constants.X_FRAME_OPTIONS_ALLOW, _('Allow')) 168 ) 169 170 template_choices = [(x, _(y)) for x, y in get_cms_setting('TEMPLATES')] 171 172 created_by = models.CharField( 173 _("created by"), max_length=constants.PAGE_USERNAME_MAX_LENGTH, 174 editable=False) 175 changed_by = models.CharField( 176 _("changed by"), max_length=constants.PAGE_USERNAME_MAX_LENGTH, 177 editable=False) 178 creation_date = models.DateTimeField(auto_now_add=True) 179 changed_date = models.DateTimeField(auto_now=True) 180 181 publication_date = models.DateTimeField(_("publication date"), null=True, blank=True, help_text=_( 182 'When the page should go live. Status must be "Published" for page to go live.'), db_index=True) 183 publication_end_date = models.DateTimeField(_("publication end date"), null=True, blank=True, 184 help_text=_('When to expire the page. Leave empty to never expire.'), 185 db_index=True) 186 # 187 # Please use toggle_in_navigation() instead of affecting this property 188 # directly so that the cms page cache can be invalidated as appropriate. 189 # 190 in_navigation = models.BooleanField(_("in navigation"), default=True, db_index=True) 191 soft_root = models.BooleanField(_("soft root"), db_index=True, default=False, 192 help_text=_("All ancestors will not be displayed in the navigation")) 193 reverse_id = models.CharField(_("id"), max_length=40, db_index=True, blank=True, null=True, help_text=_( 194 "A unique identifier that is used with the page_url templatetag for linking to this page")) 195 navigation_extenders = models.CharField(_("attached menu"), max_length=80, db_index=True, blank=True, null=True) 196 template = models.CharField(_("template"), max_length=100, choices=template_choices, 197 help_text=_('The template used to render the content.'), 198 default=TEMPLATE_DEFAULT) 199 200 login_required = models.BooleanField(_("login required"), default=False) 201 limit_visibility_in_menu = models.SmallIntegerField(_("menu visibility"), default=None, null=True, blank=True, 202 choices=LIMIT_VISIBILITY_IN_MENU_CHOICES, db_index=True, 203 help_text=_("limit when this page is visible in the menu")) 204 is_home = models.BooleanField(editable=False, db_index=True, default=False) 205 application_urls = models.CharField(_('application'), max_length=200, blank=True, null=True, db_index=True) 206 application_namespace = models.CharField(_('application instance name'), max_length=200, blank=True, null=True) 207 208 # Placeholders (plugins) 209 placeholders = models.ManyToManyField('cms.Placeholder', editable=False) 210 211 # Publisher fields 212 publisher_is_draft = models.BooleanField(default=True, editable=False, db_index=True) 213 # This is misnamed - the one-to-one relation is populated on both ends 214 publisher_public = models.OneToOneField( 215 'self', 216 on_delete=models.CASCADE, 217 related_name='publisher_draft', 218 null=True, 219 editable=False, 220 ) 221 languages = models.CharField(max_length=255, editable=False, blank=True, null=True) 222 223 # X Frame Options for clickjacking protection 224 xframe_options = models.IntegerField( 225 choices=X_FRAME_OPTIONS_CHOICES, 226 default=get_cms_setting('DEFAULT_X_FRAME_OPTIONS'), 227 ) 228 229 # Flag that marks a page as page-type 230 is_page_type = models.BooleanField(default=False) 231 232 node = models.ForeignKey( 233 TreeNode, 234 related_name='cms_pages', 235 on_delete=models.CASCADE, 236 ) 237 238 # Managers 239 objects = PageManager() 240 241 class Meta: 242 default_permissions = ('add', 'change', 'delete') 243 permissions = ( 244 ('view_page', 'Can view page'), 245 ('publish_page', 'Can publish page'), 246 ('edit_static_placeholder', 'Can edit static placeholders'), 247 ) 248 unique_together = ('node', 'publisher_is_draft') 249 verbose_name = _('page') 250 verbose_name_plural = _('pages') 251 app_label = 'cms' 252 253 def __init__(self, *args, **kwargs): 254 super(Page, self).__init__(*args, **kwargs) 255 self.title_cache = {} 256 257 def __str__(self): 258 try: 259 title = self.get_menu_title(fallback=True) 260 except LanguageError: 261 try: 262 title = self.title_set.all()[0] 263 except IndexError: 264 title = None 265 if title is None: 266 title = u"" 267 return force_text(title) 268 269 def __repr__(self): 270 display = '<{module}.{class_name} id={id} is_draft={is_draft} object at {location}>'.format( 271 module=self.__module__, 272 class_name=self.__class__.__name__, 273 id=self.pk, 274 is_draft=self.publisher_is_draft, 275 location=hex(id(self)), 276 ) 277 return display 278 279 def _clear_node_cache(self): 280 if DJANGO_1_11: 281 if hasattr(self, '_node_cache'): 282 del self._node_cache 283 else: 284 if Page.node.is_cached(self): 285 Page.node.field.delete_cached_value(self) 286 287 def _clear_internal_cache(self): 288 self.title_cache = {} 289 self._clear_node_cache() 290 291 if hasattr(self, '_prefetched_objects_cache'): 292 del self._prefetched_objects_cache 293 294 @cached_property 295 def parent_page(self): 296 return self.get_parent_page() 297 298 def set_as_homepage(self, user=None): 299 """ 300 Sets the given page as the homepage. 301 Updates the title paths for all affected pages. 302 Returns the old home page (if any). 303 """ 304 assert self.publisher_is_draft 305 306 if user: 307 changed_by = get_clean_username(user) 308 else: 309 changed_by = constants.SCRIPT_USERNAME 310 311 changed_date = now() 312 313 try: 314 old_home = self.__class__.objects.get( 315 is_home=True, 316 node__site=self.node.site_id, 317 publisher_is_draft=True, 318 ) 319 except self.__class__.DoesNotExist: 320 old_home_tree = [] 321 else: 322 old_home.update( 323 draft_only=False, 324 is_home=False, 325 changed_by=changed_by, 326 changed_date=changed_date, 327 ) 328 old_home_tree = old_home._set_title_root_path() 329 330 self.update( 331 draft_only=False, 332 is_home=True, 333 changed_by=changed_by, 334 changed_date=changed_date, 335 ) 336 new_home_tree = self._remove_title_root_path() 337 return (new_home_tree, old_home_tree) 338 339 def _update_title_path(self, language): 340 parent_page = self.get_parent_page() 341 342 if parent_page: 343 base = parent_page.get_path(language, fallback=True) 344 else: 345 base = '' 346 347 title_obj = self.get_title_obj(language, fallback=False) 348 title_obj.path = title_obj.get_path_for_base(base) 349 title_obj.save() 350 351 def _update_title_path_recursive(self, language): 352 assert self.publisher_is_draft 353 from cms.models import Title 354 355 if self.node.is_leaf() or language not in self.get_languages(): 356 return 357 358 pages = self.get_child_pages() 359 base = self.get_path(language, fallback=True) 360 361 if base: 362 new_path = Concat(models.Value(base), models.Value('/'), models.F('slug')) 363 else: 364 # User is moving the homepage 365 new_path = models.F('slug') 366 367 (Title 368 .objects 369 .filter(language=language, page__in=pages) 370 .exclude(has_url_overwrite=True) 371 .update(path=new_path)) 372 373 for child in pages.filter(title_set__language=language).iterator(): 374 child._update_title_path_recursive(language) 375 376 def _set_title_root_path(self): 377 from cms.models import Title 378 379 node_tree = TreeNode.get_tree(self.node) 380 page_tree = self.__class__.objects.filter(node__in=node_tree) 381 translations = Title.objects.filter(page__in=page_tree, has_url_overwrite=False) 382 383 for language, slug in self.title_set.values_list('language', 'slug'): 384 # Update the translations for all descendants of this page 385 # to include this page's slug as its path prefix 386 (translations 387 .filter(language=language) 388 .update(path=Concat(models.Value(slug), models.Value('/'), 'path'))) 389 390 # Explicitly update this page's path to match its slug 391 # Doing this is cheaper than a TRIM call to remove the "/" characters 392 if self.publisher_public_id: 393 # include the public translation 394 current_translations = Title.objects.filter(page__in=[self.pk, self.publisher_public_id]) 395 else: 396 current_translations = self.title_set.all() 397 current_translations.filter(language=language).update(path=slug) 398 return page_tree 399 400 def _remove_title_root_path(self): 401 from cms.models import Title 402 403 node_tree = TreeNode.get_tree(self.node) 404 page_tree = self.__class__.objects.filter(node__in=node_tree) 405 translations = Title.objects.filter(page__in=page_tree, has_url_overwrite=False) 406 407 for language, slug in self.title_set.values_list('language', 'slug'): 408 # Use 2 because of 1 indexing plus the fact we need to trim 409 # the "/" character. 410 trim_count = len(slug) + 2 411 sql_func = models.Func( 412 models.F('path'), 413 models.Value(trim_count), 414 function='substr', 415 ) 416 (translations 417 .filter(language=language, path__startswith=slug) 418 .update(path=sql_func)) 419 return page_tree 420 421 def is_dirty(self, language): 422 state = self.get_publisher_state(language) 423 return state == PUBLISHER_STATE_DIRTY or state == PUBLISHER_STATE_PENDING 424 425 def is_potential_home(self): 426 """ 427 Encapsulates logic for determining if this page is eligible to be set 428 as `is_home`. This is a public method so that it can be accessed in the 429 admin for determining whether to enable the "Set as home" menu item. 430 :return: Boolean 431 """ 432 assert self.publisher_is_draft 433 # Only root nodes are eligible for homepage 434 return not self.is_home and bool(self.node.is_root()) 435 436 def get_absolute_url(self, language=None, fallback=True): 437 if not language: 438 language = get_current_language() 439 440 with force_language(language): 441 if self.is_home: 442 return reverse('pages-root') 443 path = self.get_path(language, fallback) or self.get_slug(language, fallback) 444 return reverse('pages-details-by-slug', kwargs={"slug": path}) 445 446 def get_public_url(self, language=None, fallback=True): 447 """ 448 Returns the URL of the published version of the current page. 449 Returns empty string if the page is not published. 450 """ 451 try: 452 return self.get_public_object().get_absolute_url(language, fallback) 453 except: 454 return '' 455 456 def get_draft_url(self, language=None, fallback=True): 457 """ 458 Returns the URL of the draft version of the current page. 459 Returns empty string if the draft page is not available. 460 """ 461 try: 462 return self.get_draft_object().get_absolute_url(language, fallback) 463 except: 464 return '' 465 466 def set_tree_node(self, site, target=None, position='first-child'): 467 assert self.publisher_is_draft 468 assert position in ('last-child', 'first-child', 'left', 'right') 469 470 new_node = TreeNode(site=site) 471 472 if target is None: 473 self.node = TreeNode.add_root(instance=new_node) 474 elif position == 'first-child' and target.is_branch: 475 self.node = target.get_first_child().add_sibling(pos='left', instance=new_node) 476 elif position in ('last-child', 'first-child'): 477 self.node = target.add_child(instance=new_node) 478 else: 479 self.node = target.add_sibling(pos=position, instance=new_node) 480 481 def move_page(self, target_node, position='first-child'): 482 """ 483 Called from admin interface when page is moved. Should be used on 484 all the places which are changing page position. Used like an interface 485 to django-treebeard, but after move is done page_moved signal is fired. 486 487 Note for issue #1166: url conflicts are handled by updated 488 check_title_slugs, overwrite_url on the moved page don't need any check 489 as it remains the same regardless of the page position in the tree 490 """ 491 assert self.publisher_is_draft 492 assert isinstance(target_node, TreeNode) 493 494 inherited_template = self.template == constants.TEMPLATE_INHERITANCE_MAGIC 495 496 if inherited_template and target_node.is_root() and position in ('left', 'right'): 497 # The page is being moved to a root position. 498 # Explicitly set the inherited template on the page 499 # to keep all plugins / placeholders. 500 self.update(refresh=False, template=self.get_template()) 501 502 # Don't use a cached node. Always get a fresh one. 503 self._clear_node_cache() 504 505 # Runs the SQL updates on the treebeard fields 506 self.node.move(target_node, position) 507 508 if position in ('first-child', 'last-child'): 509 parent_id = target_node.pk 510 else: 511 # moving relative to sibling 512 # or to the root of the tree 513 parent_id = target_node.parent_id 514 # Runs the SQL updates on the parent field 515 self.node.update(parent_id=parent_id) 516 517 # Clear the cached node once again to trigger a db query 518 # on access. 519 self._clear_node_cache() 520 521 # Update the descendants to "PENDING" 522 # If the target (parent) page is not published 523 # and the page being moved is published. 524 titles = ( 525 self 526 .title_set 527 .filter(language__in=self.get_languages()) 528 .values_list('language', 'published') 529 ) 530 parent_page = self.get_parent_page() 531 532 if parent_page: 533 parent_titles = ( 534 parent_page 535 .title_set 536 .exclude(publisher_state=PUBLISHER_STATE_PENDING) 537 .values_list('language', 'published') 538 ) 539 parent_titles_by_language = dict(parent_titles) 540 else: 541 parent_titles_by_language = {} 542 543 for language, published in titles: 544 parent_is_published = parent_titles_by_language.get(language) 545 546 # Update draft title path 547 self._update_title_path(language) 548 self._update_title_path_recursive(language) 549 550 if published and parent_is_published: 551 # this looks redundant but it's necessary 552 # for all the descendants of the page being 553 # moved to be set to the correct state. 554 self.publisher_public._update_title_path(language) 555 self.mark_as_published(language) 556 self.mark_descendants_as_published(language) 557 elif published and parent_page: 558 # page is published but it's parent is not 559 # mark the page being moved (source) as "pending" 560 self.mark_as_pending(language) 561 # mark all descendants of source as "pending" 562 self.mark_descendants_pending(language) 563 elif published: 564 self.publisher_public._update_title_path(language) 565 self.mark_as_published(language) 566 self.mark_descendants_as_published(language) 567 self.clear_cache(menu=True) 568 return self 569 570 def _copy_titles(self, target, language, published): 571 """ 572 Copy the title matching language to a new page (which must have a pk). 573 :param target: The page where the new title should be stored 574 """ 575 source_title = self.title_set.get(language=language) 576 577 try: 578 target_title_id = ( 579 target 580 .title_set 581 .filter(language=language) 582 .values_list('pk', flat=True)[0] 583 ) 584 except IndexError: 585 target_title_id = None 586 587 source_title_id = source_title.pk 588 589 # If an old title exists, overwrite. Otherwise create new 590 source_title.pk = target_title_id 591 source_title.page = target 592 source_title.publisher_is_draft = target.publisher_is_draft 593 source_title.publisher_public_id = source_title_id 594 source_title.published = published 595 source_title._publisher_keep_state = True 596 597 if published: 598 source_title.publisher_state = PUBLISHER_STATE_DEFAULT 599 else: 600 source_title.publisher_state = PUBLISHER_STATE_PENDING 601 source_title.save() 602 return source_title 603 604 def _clear_placeholders(self, language=None): 605 from cms.models import CMSPlugin 606 607 placeholders = list(self.get_placeholders()) 608 placeholder_ids = (placeholder.pk for placeholder in placeholders) 609 plugins = CMSPlugin.objects.filter(placeholder__in=placeholder_ids) 610 611 if language: 612 plugins = plugins.filter(language=language) 613 models.query.QuerySet.delete(plugins) 614 return placeholders 615 616 def _copy_contents(self, target, language): 617 """ 618 Copy all the plugins to a new page. 619 :param target: The page where the new content should be stored 620 """ 621 cleared_placeholders = target._clear_placeholders(language) 622 cleared_placeholders_by_slot = {pl.slot: pl for pl in cleared_placeholders} 623 624 for placeholder in self.get_placeholders(): 625 try: 626 target_placeholder = cleared_placeholders_by_slot[placeholder.slot] 627 except KeyError: 628 target_placeholder = target.placeholders.create( 629 slot=placeholder.slot, 630 default_width=placeholder.default_width, 631 ) 632 633 placeholder.copy_plugins(target_placeholder, language=language) 634 635 def _copy_attributes(self, target, clean=False): 636 """ 637 Copy all page data to the target. This excludes parent and other values 638 that are specific to an exact instance. 639 :param target: The Page to copy the attributes to 640 """ 641 if not clean: 642 target.publication_date = self.publication_date 643 target.publication_end_date = self.publication_end_date 644 target.reverse_id = self.reverse_id 645 target.changed_by = self.changed_by 646 target.login_required = self.login_required 647 target.in_navigation = self.in_navigation 648 target.soft_root = self.soft_root 649 target.limit_visibility_in_menu = self.limit_visibility_in_menu 650 target.navigation_extenders = self.navigation_extenders 651 target.application_urls = self.application_urls 652 target.application_namespace = self.application_namespace 653 target.template = self.template 654 target.xframe_options = self.xframe_options 655 target.is_page_type = self.is_page_type 656 657 def copy(self, site, parent_node=None, language=None, 658 translations=True, permissions=False, extensions=True): 659 from cms.utils.page import get_available_slug 660 661 if parent_node: 662 new_node = parent_node.add_child(site=site) 663 parent_page = parent_node.item 664 else: 665 new_node = TreeNode.add_root(site=site) 666 parent_page = None 667 668 new_page = copy.copy(self) 669 new_page._state = ModelState() 670 new_page._clear_internal_cache() 671 new_page.pk = None 672 new_page.node = new_node 673 new_page.publisher_public_id = None 674 new_page.is_home = False 675 new_page.reverse_id = None 676 new_page.publication_date = None 677 new_page.publication_end_date = None 678 new_page.languages = '' 679 new_page.save() 680 681 # Have the node remember its page. 682 # This is done to save some queries 683 # when the node's descendants are copied. 684 new_page.node.__dict__['item'] = new_page 685 686 if language and translations: 687 translations = self.title_set.filter(language=language) 688 elif translations: 689 translations = self.title_set.all() 690 else: 691 translations = self.title_set.none() 692 693 # copy titles of this page 694 for title in translations: 695 title = copy.copy(title) 696 title.pk = None 697 title.page = new_page 698 title.published = False 699 title.publisher_public = None 700 701 if parent_page: 702 base = parent_page.get_path(title.language) 703 path = '%s/%s' % (base, title.slug) if base else title.slug 704 else: 705 base = '' 706 path = title.slug 707 708 title.slug = get_available_slug(site, path, title.language) 709 title.path = '%s/%s' % (base, title.slug) if base else title.slug 710 title.save() 711 712 new_page.title_cache[title.language] = title 713 new_page.update_languages([trans.language for trans in translations]) 714 715 # copy the placeholders (and plugins on those placeholders!) 716 for placeholder in self.placeholders.iterator(): 717 new_placeholder = copy.copy(placeholder) 718 new_placeholder.pk = None 719 new_placeholder.save() 720 new_page.placeholders.add(new_placeholder) 721 placeholder.copy_plugins(new_placeholder, language=language) 722 723 if extensions: 724 from cms.extensions import extension_pool 725 extension_pool.copy_extensions(self, new_page) 726 727 # copy permissions if requested 728 if permissions and get_cms_setting('PERMISSION'): 729 permissions = self.pagepermission_set.iterator() 730 permissions_new = [] 731 732 for permission in permissions: 733 permission.pk = None 734 permission.page = new_page 735 permissions_new.append(permission) 736 737 if permissions_new: 738 new_page.pagepermission_set.bulk_create(permissions_new) 739 return new_page 740 741 def copy_with_descendants(self, target_node=None, position=None, 742 copy_permissions=True, target_site=None): 743 """ 744 Copy a page [ and all its descendants to a new location ] 745 """ 746 if not self.publisher_is_draft: 747 raise PublicIsUnmodifiable("copy page is not allowed for public pages") 748 749 if position in ('first-child', 'last-child'): 750 parent_node = target_node 751 elif target_node: 752 parent_node = target_node.parent 753 else: 754 parent_node = None 755 756 if target_site is None: 757 target_site = parent_node.site if parent_node else self.node.site 758 759 # Evaluate the descendants queryset BEFORE copying the page. 760 # Otherwise, if the page is copied and pasted on itself, it will duplicate. 761 descendants = list( 762 self.get_descendant_pages() 763 .select_related('node') 764 .prefetch_related('title_set') 765 ) 766 new_root_page = self.copy(target_site, parent_node=parent_node) 767 new_root_node = new_root_page.node 768 769 if target_node and position in ('first-child'): 770 # target node is a parent and user has requested to 771 # insert the new page as its first child 772 new_root_node.move(target_node, position) 773 new_root_node.refresh_from_db(fields=('path', 'depth')) 774 775 if target_node and position in ('left', 'last-child'): 776 # target node is a sibling 777 new_root_node.move(target_node, position) 778 new_root_node.refresh_from_db(fields=('path', 'depth')) 779 780 nodes_by_id = {self.node.pk: new_root_node} 781 782 for page in descendants: 783 parent = nodes_by_id[page.node.parent_id] 784 new_page = page.copy( 785 target_site, 786 parent_node=parent, 787 translations=True, 788 permissions=copy_permissions, 789 ) 790 nodes_by_id[page.node_id] = new_page.node 791 return new_root_page 792 793 def delete(self, *args, **kwargs): 794 TreeNode.get_tree(self.node).delete_fast() 795 796 if self.node.parent_id: 797 (TreeNode 798 .objects 799 .filter(pk=self.node.parent_id) 800 .update(numchild=models.F('numchild') - 1)) 801 self.clear_cache(menu=True) 802 803 def delete_translations(self, language=None): 804 if language is None: 805 languages = self.get_languages() 806 else: 807 languages = [language] 808 809 self.title_set.filter(language__in=languages).delete() 810 811 for language in languages: 812 self.mark_descendants_pending(language) 813 814 def save(self, **kwargs): 815 # delete template cache 816 if hasattr(self, '_template_cache'): 817 delattr(self, '_template_cache') 818 819 created = not bool(self.pk) 820 if self.reverse_id == "": 821 self.reverse_id = None 822 if self.application_namespace == "": 823 self.application_namespace = None 824 from cms.utils.permissions import get_current_user_name 825 826 self.changed_by = get_current_user_name() 827 828 if created: 829 self.created_by = self.changed_by 830 super(Page, self).save(**kwargs) 831 832 def save_base(self, *args, **kwargs): 833 """Overridden save_base. If an instance is draft, and was changed, mark 834 it as dirty. 835 836 Dirty flag is used for changed nodes identification when publish method 837 takes place. After current changes are published, state is set back to 838 PUBLISHER_STATE_DEFAULT (in publish method). 839 """ 840 keep_state = getattr(self, '_publisher_keep_state', None) 841 if self.publisher_is_draft and not keep_state and self.is_new_dirty(): 842 self.title_set.all().update(publisher_state=PUBLISHER_STATE_DIRTY) 843 if keep_state: 844 delattr(self, '_publisher_keep_state') 845 return super(Page, self).save_base(*args, **kwargs) 846 847 def update(self, refresh=False, draft_only=True, **data): 848 assert self.publisher_is_draft 849 850 cls = self.__class__ 851 852 if not draft_only and self.publisher_public_id: 853 ids = [self.pk, self.publisher_public_id] 854 cls.objects.filter(pk__in=ids).update(**data) 855 else: 856 cls.objects.filter(pk=self.pk).update(**data) 857 858 if refresh: 859 return self.reload() 860 else: 861 for field, value in data.items(): 862 setattr(self, field, value) 863 return 864 865 def update_translations(self, language=None, **data): 866 if language: 867 translations = self.title_set.filter(language=language) 868 else: 869 translations = self.title_set.all() 870 return translations.update(**data) 871 872 def has_translation(self, language): 873 return self.title_set.filter(language=language).exists() 874 875 def is_new_dirty(self): 876 if self.pk: 877 fields = [ 878 'publication_date', 'publication_end_date', 'in_navigation', 'soft_root', 'reverse_id', 879 'navigation_extenders', 'template', 'login_required', 'limit_visibility_in_menu' 880 ] 881 try: 882 old_page = Page.objects.get(pk=self.pk) 883 except Page.DoesNotExist: 884 return True 885 for field in fields: 886 old_val = getattr(old_page, field) 887 new_val = getattr(self, field) 888 if not old_val == new_val: 889 return True 890 return False 891 return True 892 893 def is_published(self, language, force_reload=False): 894 title_obj = self.get_title_obj(language, fallback=False, force_reload=force_reload) 895 return title_obj.published and title_obj.publisher_state != PUBLISHER_STATE_PENDING 896 897 def toggle_in_navigation(self, set_to=None): 898 ''' 899 Toggles (or sets) in_navigation and invalidates the cms page cache 900 ''' 901 old = self.in_navigation 902 if set_to in [True, False]: 903 self.in_navigation = set_to 904 else: 905 self.in_navigation = not self.in_navigation 906 self.save() 907 908 # If there was a change, invalidate the cms page cache 909 if self.in_navigation != old: 910 self.clear_cache() 911 return self.in_navigation 912 913 def get_publisher_state(self, language, force_reload=False): 914 try: 915 return self.get_title_obj(language, False, force_reload=force_reload).publisher_state 916 except AttributeError: 917 return None 918 919 def set_publisher_state(self, language, state, published=None): 920 title = self.title_set.get(language=language) 921 title.publisher_state = state 922 if published is not None: 923 title.published = published 924 title._publisher_keep_state = True 925 title.save() 926 if language in self.title_cache: 927 self.title_cache[language].publisher_state = state 928 return title 929 930 def publish(self, language): 931 """ 932 :returns: True if page was successfully published. 933 """ 934 from cms.utils.permissions import get_current_user_name 935 936 # Publish can only be called on draft pages 937 if not self.publisher_is_draft: 938 raise PublicIsUnmodifiable('The public instance cannot be published. Use draft.') 939 940 if not self._publisher_can_publish(language): 941 return False 942 943 if self.publisher_public_id: 944 public_page = Page.objects.get(pk=self.publisher_public_id) 945 public_languages = public_page.get_languages() 946 else: 947 public_page = Page(created_by=self.created_by) 948 public_languages = [language] 949 950 self._copy_attributes(public_page, clean=False) 951 952 if language not in public_languages: 953 public_languages.append(language) 954 955 # TODO: Get rid of the current user thread hack 956 public_page.changed_by = get_current_user_name() 957 public_page.is_home = self.is_home 958 public_page.publication_date = self.publication_date or now() 959 public_page.publisher_public = self 960 public_page.publisher_is_draft = False 961 public_page.languages = ','.join(public_languages) 962 public_page.node = self.node 963 public_page.save() 964 965 # Copy the page translation (title) matching language 966 # into a "public" version. 967 public_title = self._copy_titles(public_page, language, published=True) 968 969 # Ensure this draft page points to its public version 970 self.update( 971 draft_only=True, 972 changed_by=public_page.changed_by, 973 publisher_public=public_page, 974 publication_date=public_page.publication_date, 975 ) 976 977 # Set the draft page translation matching language 978 # to point to its public version. 979 # Its important for draft to be published even if its state 980 # is pending. 981 self.update_translations( 982 language, 983 published=True, 984 publisher_public=public_title, 985 publisher_state=PUBLISHER_STATE_DEFAULT, 986 ) 987 self._copy_contents(public_page, language) 988 989 if self.node.is_branch: 990 self.mark_descendants_as_published(language) 991 992 if language in self.title_cache: 993 del self.title_cache[language] 994 995 # fire signal after publishing is done 996 import cms.signals as cms_signals 997 998 cms_signals.post_publish.send(sender=Page, instance=self, language=language) 999 1000 public_page.clear_cache( 1001 language, 1002 menu=True, 1003 placeholder=True, 1004 ) 1005 return True 1006 1007 def clear_cache(self, language=None, menu=False, placeholder=False): 1008 from cms.cache import invalidate_cms_page_cache 1009 1010 if get_cms_setting('PAGE_CACHE'): 1011 # Clears all the page caches 1012 invalidate_cms_page_cache() 1013 1014 if placeholder and get_cms_setting('PLACEHOLDER_CACHE'): 1015 assert language, 'language is required when clearing placeholder cache' 1016 1017 placeholders = self.get_placeholders() 1018 1019 for placeholder in placeholders: 1020 placeholder.clear_cache(language, site_id=self.node.site_id) 1021 1022 if menu: 1023 # Clears all menu caches for this page's site 1024 menu_pool.clear(site_id=self.node.site_id) 1025 1026 def unpublish(self, language, site=None): 1027 """ 1028 Removes this page from the public site 1029 :returns: True if this page was successfully unpublished 1030 """ 1031 # Publish can only be called on draft pages 1032 if not self.publisher_is_draft: 1033 raise PublicIsUnmodifiable('The public instance cannot be unpublished. Use draft.') 1034 1035 self.update_translations( 1036 language, 1037 published=False, 1038 publisher_state=PUBLISHER_STATE_DIRTY, 1039 ) 1040 1041 public_page = self.get_public_object() 1042 public_page.update_translations(language, published=False) 1043 public_page._clear_placeholders(language) 1044 public_page.clear_cache(language) 1045 1046 self.mark_descendants_pending(language) 1047 1048 from cms.signals import post_unpublish 1049 post_unpublish.send(sender=Page, instance=self, language=language) 1050 1051 return True 1052 1053 def get_child_pages(self): 1054 nodes = self.node.get_children() 1055 pages = ( 1056 self 1057 .__class__ 1058 .objects 1059 .filter( 1060 node__in=nodes, 1061 publisher_is_draft=self.publisher_is_draft, 1062 ) 1063 .order_by('node__path') 1064 ) 1065 return pages 1066 1067 def get_ancestor_pages(self): 1068 nodes = self.node.get_ancestors() 1069 pages = ( 1070 self 1071 .__class__ 1072 .objects 1073 .filter( 1074 node__in=nodes, 1075 publisher_is_draft=self.publisher_is_draft, 1076 ) 1077 .order_by('node__path') 1078 ) 1079 return pages 1080 1081 def get_descendant_pages(self): 1082 nodes = self.node.get_descendants() 1083 pages = ( 1084 self 1085 .__class__ 1086 .objects 1087 .filter( 1088 node__in=nodes, 1089 publisher_is_draft=self.publisher_is_draft, 1090 ) 1091 .order_by('node__path') 1092 ) 1093 return pages 1094 1095 def get_root(self): 1096 node = self.node 1097 return self.__class__.objects.get( 1098 node__path=node.path[0:node.steplen], 1099 publisher_is_draft=self.publisher_is_draft, 1100 ) 1101 1102 def get_parent_page(self): 1103 if not self.node.parent_id: 1104 return None 1105 1106 pages = Page.objects.filter( 1107 node=self.node.parent_id, 1108 publisher_is_draft=self.publisher_is_draft, 1109 ) 1110 return pages.select_related('node').first() 1111 1112 def mark_as_pending(self, language): 1113 assert self.publisher_is_draft 1114 assert self.publisher_public_id 1115 1116 self.get_public_object().title_set.filter(language=language).update(published=False) 1117 1118 if self.get_publisher_state(language) == PUBLISHER_STATE_DEFAULT: 1119 # Only change the state if the draft page is published 1120 # and it's state is the default (0), to avoid overriding a dirty state. 1121 self.set_publisher_state(language, state=PUBLISHER_STATE_PENDING) 1122 1123 def mark_descendants_pending(self, language): 1124 from cms.models import Title 1125 1126 if not self.publisher_is_draft: 1127 raise PublicIsUnmodifiable('The public instance cannot be altered. Use draft.') 1128 1129 node_descendants = self.node.get_descendants() 1130 page_descendants = self.__class__.objects.filter(node__in=node_descendants) 1131 1132 if page_descendants.filter(publisher_is_draft=True).exists(): 1133 # Only change the state if the draft page is not dirty 1134 # to avoid overriding a dirty state. 1135 Title.objects.filter( 1136 published=True, 1137 language=language, 1138 page__in=page_descendants.filter(publisher_is_draft=True), 1139 publisher_state=PUBLISHER_STATE_DEFAULT, 1140 ).update(publisher_state=PUBLISHER_STATE_PENDING) 1141 1142 if page_descendants.filter(publisher_is_draft=False).exists(): 1143 Title.objects.filter( 1144 published=True, 1145 language=language, 1146 page__in=page_descendants.filter(publisher_is_draft=False), 1147 ).update(published=False) 1148 1149 def mark_as_published(self, language): 1150 from cms.models import Title 1151 1152 (Title 1153 .objects 1154 .filter(page=self.publisher_public_id, language=language) 1155 .update(publisher_state=PUBLISHER_STATE_DEFAULT, published=True)) 1156 1157 draft = self.get_draft_object() 1158 1159 if draft.get_publisher_state(language) == PUBLISHER_STATE_PENDING: 1160 # A check for pending is necessary because the page might have 1161 # been modified after it was marked as pending. 1162 draft.set_publisher_state(language, PUBLISHER_STATE_DEFAULT) 1163 1164 def mark_descendants_as_published(self, language): 1165 from cms.models import Title 1166 1167 if not self.publisher_is_draft: 1168 raise PublicIsUnmodifiable('The public instance cannot be published. Use draft.') 1169 1170 base = self.get_path(language, fallback=True) 1171 node_children = self.node.get_children() 1172 page_children = self.__class__.objects.filter(node__in=node_children) 1173 page_children_draft = page_children.filter(publisher_is_draft=True) 1174 page_children_public = page_children.filter(publisher_is_draft=False) 1175 1176 # Set public pending titles as published 1177 unpublished_public = Title.objects.filter( 1178 language=language, 1179 page__in=page_children_public, 1180 publisher_public__published=True, 1181 ) 1182 1183 if base: 1184 new_path = Concat(models.Value(base), models.Value('/'), models.F('slug')) 1185 else: 1186 # User is moving the homepage 1187 new_path = models.F('slug') 1188 1189 # Update public title paths 1190 unpublished_public.exclude(has_url_overwrite=True).update(path=new_path) 1191 1192 # Set unpublished pending titles to published 1193 unpublished_public.filter(published=False).update(published=True) 1194 1195 # Update drafts 1196 Title.objects.filter( 1197 published=True, 1198 language=language, 1199 page__in=page_children_draft, 1200 publisher_state=PUBLISHER_STATE_PENDING 1201 ).update(publisher_state=PUBLISHER_STATE_DEFAULT) 1202 1203 # Continue publishing descendants, one branch at a time. 1204 published_children = page_children_draft.filter( 1205 title_set__published=True, 1206 title_set__language=language, 1207 ) 1208 1209 for child in published_children.iterator(): 1210 child.mark_descendants_as_published(language) 1211 1212 def revert_to_live(self, language): 1213 """Revert the draft version to the same state as the public version 1214 """ 1215 if not self.publisher_is_draft: 1216 # Revert can only be called on draft pages 1217 raise PublicIsUnmodifiable('The public instance cannot be reverted. Use draft.') 1218 1219 public = self.get_public_object() 1220 1221 if not public: 1222 raise PublicVersionNeeded('A public version of this page is needed') 1223 1224 public._copy_attributes(self) 1225 public._copy_contents(self, language) 1226 public._copy_titles(self, language, public.is_published(language)) 1227 1228 self.update_translations( 1229 language, 1230 published=True, 1231 publisher_state=PUBLISHER_STATE_DEFAULT, 1232 ) 1233 self._publisher_keep_state = True 1234 self.save() 1235 1236 def get_draft_object(self): 1237 if not self.publisher_is_draft: 1238 return self.publisher_draft 1239 return self 1240 1241 def get_public_object(self): 1242 if not self.publisher_is_draft: 1243 return self 1244 return self.publisher_public 1245 1246 def get_languages(self): 1247 if self.languages: 1248 return sorted(self.languages.split(',')) 1249 else: 1250 return [] 1251 1252 def remove_language(self, language): 1253 page_languages = self.get_languages() 1254 1255 if language in page_languages: 1256 page_languages.remove(language) 1257 self.update_languages(page_languages) 1258 1259 def update_languages(self, languages): 1260 languages = ",".join(languages) 1261 # Update current instance 1262 self.languages = languages 1263 # Commit. It's important to not call save() 1264 # we'd like to commit only the languages field and without 1265 # any kind of signals. 1266 self.update(draft_only=False, languages=languages) 1267 1268 def get_published_languages(self): 1269 if self.publisher_is_draft: 1270 return self.get_languages() 1271 return sorted([language for language in self.get_languages() if self.is_published(language)]) 1272 1273 def set_translations_cache(self): 1274 for translation in self.title_set.all(): 1275 self.title_cache.setdefault(translation.language, translation) 1276 1277 def get_path_for_slug(self, slug, language): 1278 if self.is_home: 1279 return '' 1280 1281 if self.parent_page: 1282 base = self.parent_page.get_path(language, fallback=True) 1283 # base can be empty when the parent is a home-page 1284 path = u'%s/%s' % (base, slug) if base else slug 1285 else: 1286 path = slug 1287 return path 1288 1289 # ## Title object access 1290 1291 def get_title_obj(self, language=None, fallback=True, force_reload=False): 1292 """Helper function for accessing wanted / current title. 1293 If wanted title doesn't exists, EmptyTitle instance will be returned. 1294 """ 1295 language = self._get_title_cache(language, fallback, force_reload) 1296 if language in self.title_cache: 1297 return self.title_cache[language] 1298 from cms.models.titlemodels import EmptyTitle 1299 1300 return EmptyTitle(language) 1301 1302 def get_title_obj_attribute(self, attrname, language=None, fallback=True, force_reload=False): 1303 """Helper function for getting attribute or None from wanted/current title. 1304 """ 1305 try: 1306 attribute = getattr(self.get_title_obj(language, fallback, force_reload), attrname) 1307 return attribute 1308 except AttributeError: 1309 return None 1310 1311 def get_path(self, language=None, fallback=True, force_reload=False): 1312 """ 1313 get the path of the page depending on the given language 1314 """ 1315 return self.get_title_obj_attribute("path", language, fallback, force_reload) 1316 1317 def get_slug(self, language=None, fallback=True, force_reload=False): 1318 """ 1319 get the slug of the page depending on the given language 1320 """ 1321 return self.get_title_obj_attribute("slug", language, fallback, force_reload) 1322 1323 def get_title(self, language=None, fallback=True, force_reload=False): 1324 """ 1325 get the title of the page depending on the given language 1326 """ 1327 return self.get_title_obj_attribute("title", language, fallback, force_reload) 1328 1329 def get_menu_title(self, language=None, fallback=True, force_reload=False): 1330 """ 1331 get the menu title of the page depending on the given language 1332 """ 1333 menu_title = self.get_title_obj_attribute("menu_title", language, fallback, force_reload) 1334 if not menu_title: 1335 return self.get_title(language, True, force_reload) 1336 return menu_title 1337 1338 def get_placeholders(self): 1339 if not hasattr(self, '_placeholder_cache'): 1340 self._placeholder_cache = self.placeholders.all() 1341 return self._placeholder_cache 1342 1343 def _validate_title(self, title): 1344 from cms.models.titlemodels import EmptyTitle 1345 if isinstance(title, EmptyTitle): 1346 return False 1347 if not title.title or not title.slug: 1348 return False 1349 return True 1350 1351 def get_admin_tree_title(self): 1352 from cms.models.titlemodels import EmptyTitle 1353 language = get_language() 1354 1355 if not self.title_cache: 1356 self.set_translations_cache() 1357 1358 if language not in self.title_cache or not self._validate_title(self.title_cache.get(language, EmptyTitle(language))): 1359 fallback_langs = i18n.get_fallback_languages(language) 1360 found = False 1361 for lang in fallback_langs: 1362 if lang in self.title_cache and self._validate_title(self.title_cache.get(lang, EmptyTitle(lang))): 1363 found = True 1364 language = lang 1365 if not found: 1366 language = None 1367 for lang, item in self.title_cache.items(): 1368 if not isinstance(item, EmptyTitle): 1369 language = lang 1370 if not language: 1371 return _("Empty") 1372 title = self.title_cache[language] 1373 if title.title: 1374 return title.title 1375 if title.page_title: 1376 return title.page_title 1377 if title.menu_title: 1378 return title.menu_title 1379 return title.slug 1380 1381 def get_changed_date(self, language=None, fallback=True, force_reload=False): 1382 """ 1383 get when this page was last updated 1384 """ 1385 return self.changed_date 1386 1387 def get_changed_by(self, language=None, fallback=True, force_reload=False): 1388 """ 1389 get user who last changed this page 1390 """ 1391 return self.changed_by 1392 1393 def get_page_title(self, language=None, fallback=True, force_reload=False): 1394 """ 1395 get the page title of the page depending on the given language 1396 """ 1397 page_title = self.get_title_obj_attribute("page_title", language, fallback, force_reload) 1398 1399 if not page_title: 1400 return self.get_title(language, True, force_reload) 1401 return page_title 1402 1403 def get_meta_description(self, language=None, fallback=True, force_reload=False): 1404 """ 1405 get content for the description meta tag for the page depending on the given language 1406 """ 1407 return self.get_title_obj_attribute("meta_description", language, fallback, force_reload) 1408 1409 def get_application_urls(self, language=None, fallback=True, force_reload=False): 1410 """ 1411 get application urls conf for application hook 1412 """ 1413 return self.application_urls 1414 1415 def get_redirect(self, language=None, fallback=True, force_reload=False): 1416 """ 1417 get redirect 1418 """ 1419 return self.get_title_obj_attribute("redirect", language, fallback, force_reload) 1420 1421 def _get_title_cache(self, language, fallback, force_reload): 1422 if not language: 1423 language = get_language() 1424 1425 force_reload = (force_reload or language not in self.title_cache) 1426 1427 if fallback and not self.title_cache.get(language): 1428 # language can be in the cache but might be an EmptyTitle instance 1429 fallback_langs = i18n.get_fallback_languages(language) 1430 for lang in fallback_langs: 1431 if self.title_cache.get(lang): 1432 return lang 1433 1434 if force_reload: 1435 from cms.models.titlemodels import Title 1436 1437 titles = Title.objects.filter(page=self) 1438 for title in titles: 1439 self.title_cache[title.language] = title 1440 if self.title_cache.get(language): 1441 return language 1442 else: 1443 if fallback: 1444 fallback_langs = i18n.get_fallback_languages(language) 1445 for lang in fallback_langs: 1446 if self.title_cache.get(lang): 1447 return lang 1448 return language 1449 1450 def get_template(self): 1451 """ 1452 get the template of this page if defined or if closer parent if 1453 defined or DEFAULT_PAGE_TEMPLATE otherwise 1454 """ 1455 if hasattr(self, '_template_cache'): 1456 return self._template_cache 1457 1458 if self.template != constants.TEMPLATE_INHERITANCE_MAGIC: 1459 self._template_cache = self.template or get_cms_setting('TEMPLATES')[0][0] 1460 return self._template_cache 1461 1462 templates = ( 1463 self 1464 .get_ancestor_pages() 1465 .exclude(template=constants.TEMPLATE_INHERITANCE_MAGIC) 1466 .order_by('-node__path') 1467 .values_list('template', flat=True) 1468 ) 1469 1470 try: 1471 self._template_cache = templates[0] 1472 except IndexError: 1473 self._template_cache = get_cms_setting('TEMPLATES')[0][0] 1474 return self._template_cache 1475 1476 def get_template_name(self): 1477 """ 1478 get the textual name (2nd parameter in get_cms_setting('TEMPLATES')) 1479 of the template of this page or of the nearest 1480 ancestor. failing to find that, return the name of the default template. 1481 """ 1482 template = self.get_template() 1483 for t in get_cms_setting('TEMPLATES'): 1484 if t[0] == template: 1485 return t[1] 1486 return _("default") 1487 1488 def has_view_permission(self, user): 1489 from cms.utils.page_permissions import user_can_view_page 1490 return user_can_view_page(user, page=self) 1491 1492 def has_view_restrictions(self, site): 1493 from cms.models import PagePermission 1494 1495 if get_cms_setting('PERMISSION'): 1496 page = self.get_draft_object() 1497 restrictions = ( 1498 PagePermission 1499 .objects 1500 .for_page(page) 1501 .filter(can_view=True) 1502 ) 1503 return restrictions.exists() 1504 return False 1505 1506 def has_add_permission(self, user): 1507 """ 1508 Has user ability to add page under current page? 1509 """ 1510 from cms.utils.page_permissions import user_can_add_subpage 1511 return user_can_add_subpage(user, self) 1512 1513 def has_change_permission(self, user): 1514 from cms.utils.page_permissions import user_can_change_page 1515 return user_can_change_page(user, page=self) 1516 1517 def has_delete_permission(self, user): 1518 from cms.utils.page_permissions import user_can_delete_page 1519 return user_can_delete_page(user, page=self) 1520 1521 def has_delete_translation_permission(self, user, language): 1522 from cms.utils.page_permissions import user_can_delete_page_translation 1523 return user_can_delete_page_translation(user, page=self, language=language) 1524 1525 def has_publish_permission(self, user): 1526 from cms.utils.page_permissions import user_can_publish_page 1527 return user_can_publish_page(user, page=self) 1528 1529 def has_advanced_settings_permission(self, user): 1530 from cms.utils.page_permissions import user_can_change_page_advanced_settings 1531 return user_can_change_page_advanced_settings(user, page=self) 1532 1533 def has_change_permissions_permission(self, user): 1534 """ 1535 Has user ability to change permissions for current page? 1536 """ 1537 from cms.utils.page_permissions import user_can_change_page_permissions 1538 return user_can_change_page_permissions(user, page=self) 1539 1540 def has_move_page_permission(self, user): 1541 """Has user ability to move current page? 1542 """ 1543 from cms.utils.page_permissions import user_can_move_page 1544 return user_can_move_page(user, page=self) 1545 1546 def has_placeholder_change_permission(self, user): 1547 if not self.publisher_is_draft: 1548 return False 1549 return self.has_change_permission(user) 1550 1551 def get_media_path(self, filename): 1552 """ 1553 Returns path (relative to MEDIA_ROOT/MEDIA_URL) to directory for storing 1554 page-scope files. This allows multiple pages to contain files with 1555 identical names without namespace issues. Plugins such as Picture can 1556 use this method to initialise the 'upload_to' parameter for File-based 1557 fields. For example: 1558 image = models.ImageField( 1559 _("image"), upload_to=CMSPlugin.get_media_path) 1560 1561 where CMSPlugin.get_media_path calls self.page.get_media_path 1562 1563 This location can be customised using the CMS_PAGE_MEDIA_PATH setting 1564 """ 1565 return join(get_cms_setting('PAGE_MEDIA_PATH'), "%d" % self.pk, filename) 1566 1567 def reload(self): 1568 """ 1569 Reload a page from the database 1570 """ 1571 return self.__class__.objects.get(pk=self.pk) 1572 1573 def _publisher_can_publish(self, language): 1574 """Is parent of this object already published? 1575 """ 1576 if self.is_page_type: 1577 return False 1578 1579 if not self.parent_page: 1580 return True 1581 1582 if self.parent_page.publisher_public_id: 1583 return self.parent_page.get_public_object().is_published(language) 1584 return False 1585 1586 def rescan_placeholders(self): 1587 """ 1588 Rescan and if necessary create placeholders in the current template. 1589 """ 1590 existing = OrderedDict() 1591 placeholders = [pl.slot for pl in self.get_declared_placeholders()] 1592 1593 for placeholder in self.placeholders.all(): 1594 if placeholder.slot in placeholders: 1595 existing[placeholder.slot] = placeholder 1596 1597 for placeholder in placeholders: 1598 if placeholder not in existing: 1599 existing[placeholder] = self.placeholders.create(slot=placeholder) 1600 return existing 1601 1602 def get_declared_placeholders(self): 1603 # inline import to prevent circular imports 1604 from cms.utils.placeholder import get_placeholders 1605 1606 return get_placeholders(self.get_template()) 1607 1608 def get_declared_static_placeholders(self, context): 1609 # inline import to prevent circular imports 1610 from cms.utils.placeholder import get_static_placeholders 1611 1612 return get_static_placeholders(self.get_template(), context) 1613 1614 def get_xframe_options(self): 1615 """ Finds X_FRAME_OPTION from tree if inherited """ 1616 xframe_options = self.xframe_options or self.X_FRAME_OPTIONS_INHERIT 1617 1618 if xframe_options != self.X_FRAME_OPTIONS_INHERIT: 1619 return xframe_options 1620 1621 # Ignore those pages which just inherit their value 1622 ancestors = self.get_ancestor_pages().order_by('-node__path') 1623 ancestors = ancestors.exclude(xframe_options=self.X_FRAME_OPTIONS_INHERIT) 1624 1625 # Now just give me the clickjacking setting (not anything else) 1626 xframe_options = ancestors.values_list('xframe_options', flat=True) 1627 1628 try: 1629 return xframe_options[0] 1630 except IndexError: 1631 return None 1632 1633 1634class PageType(Page): 1635 1636 class Meta: 1637 proxy = True 1638 default_permissions = [] 1639 1640 @classmethod 1641 def get_root_page(cls, site): 1642 pages = Page.objects.on_site(site).filter( 1643 node__depth=1, 1644 is_page_type=True, 1645 ) 1646 return pages.first() 1647 1648 def is_potential_home(self): 1649 return False 1650