cms.models.pagemodel: 613 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/cms/models/pagemodel.py

Stats: 0 executed, 569 missed, 44 excluded, 479 ignored

  1. # -*- coding: utf-8 -*-
  2. from cms.exceptions import NoHomeFound
  3. from cms.models.managers import PageManager, PagePermissionsPermissionManager
  4. from cms.models.metaclasses import PageMetaClass
  5. from cms.models.placeholdermodel import Placeholder
  6. from cms.models.pluginmodel import CMSPlugin
  7. from cms.publisher.errors import MpttPublisherCantPublish
  8. from cms.utils import i18n, urlutils, page as page_utils
  9. from cms.utils import timezone
  10. from cms.utils.copy_plugins import copy_plugins_to
  11. from cms.utils.helpers import reversion_register
  12. from django.conf import settings
  13. from django.contrib.sites.models import Site
  14. from django.core.exceptions import ObjectDoesNotExist
  15. from django.core.urlresolvers import reverse
  16. from django.db import models
  17. from django.db.models import Q
  18. from django.shortcuts import get_object_or_404
  19. from django.utils.translation import get_language, ugettext_lazy as _
  20. from menus.menu_pool import menu_pool
  21. from mptt.models import MPTTModel
  22. from os.path import join
  23. import copy
  24. class Page(MPTTModel):
  25. """
  26. A simple hierarchical page model
  27. """
  28. __metaclass__ = PageMetaClass
  29. MODERATOR_CHANGED = 0
  30. MODERATOR_NEED_APPROVEMENT = 1
  31. MODERATOR_NEED_DELETE_APPROVEMENT = 2
  32. MODERATOR_APPROVED = 10
  33. # special case - page was approved, but some of page parents are not approved yet
  34. MODERATOR_APPROVED_WAITING_FOR_PARENTS = 11
  35. moderator_state_choices = (
  36. (MODERATOR_CHANGED, _('changed')),
  37. (MODERATOR_NEED_APPROVEMENT, _('req. app.')),
  38. (MODERATOR_NEED_DELETE_APPROVEMENT, _('delete')),
  39. (MODERATOR_APPROVED, _('approved')),
  40. (MODERATOR_APPROVED_WAITING_FOR_PARENTS, _('app. par.')),
  41. )
  42. LIMIT_VISIBILITY_IN_MENU_CHOICES = (
  43. (1, _('for logged in users only')),
  44. (2, _('for anonymous users only')),
  45. )
  46. PUBLISHER_STATE_DEFAULT = 0
  47. PUBLISHER_STATE_DIRTY = 1
  48. PUBLISHER_STATE_DELETE = 2
  49. template_choices = [(x, _(y)) for x, y in settings.CMS_TEMPLATES]
  50. created_by = models.CharField(_("created by"), max_length=70, editable=False)
  51. changed_by = models.CharField(_("changed by"), max_length=70, editable=False)
  52. parent = models.ForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
  53. creation_date = models.DateTimeField(auto_now_add=True)
  54. changed_date = models.DateTimeField(auto_now=True)
  55. publication_date = models.DateTimeField(_("publication date"), null=True, blank=True, help_text=_('When the page should go live. Status must be "Published" for page to go live.'), db_index=True)
  56. publication_end_date = models.DateTimeField(_("publication end date"), null=True, blank=True, help_text=_('When to expire the page. Leave empty to never expire.'), db_index=True)
  57. in_navigation = models.BooleanField(_("in navigation"), default=True, db_index=True)
  58. soft_root = models.BooleanField(_("soft root"), db_index=True, default=False, help_text=_("All ancestors will not be displayed in the navigation"))
  59. reverse_id = models.CharField(_("id"), max_length=40, db_index=True, blank=True, null=True, help_text=_("An unique identifier that is used with the page_url templatetag for linking to this page"))
  60. navigation_extenders = models.CharField(_("attached menu"), max_length=80, db_index=True, blank=True, null=True)
  61. published = models.BooleanField(_("is published"), blank=True)
  62. template = models.CharField(_("template"), max_length=100, choices=template_choices, help_text=_('The template used to render the content.'))
  63. site = models.ForeignKey(Site, help_text=_('The site the page is accessible at.'), verbose_name=_("site"))
  64. moderator_state = models.SmallIntegerField(_('moderator state'), choices=moderator_state_choices, default=MODERATOR_NEED_APPROVEMENT, blank=True)
  65. level = models.PositiveIntegerField(db_index=True, editable=False)
  66. lft = models.PositiveIntegerField(db_index=True, editable=False)
  67. rght = models.PositiveIntegerField(db_index=True, editable=False)
  68. tree_id = models.PositiveIntegerField(db_index=True, editable=False)
  69. login_required = models.BooleanField(_("login required"), default=False)
  70. limit_visibility_in_menu = models.SmallIntegerField(_("menu visibility"), default=None, null=True, blank=True, choices=LIMIT_VISIBILITY_IN_MENU_CHOICES, db_index=True, help_text=_("limit when this page is visible in the menu"))
  71. # Placeholders (plugins)
  72. placeholders = models.ManyToManyField(Placeholder, editable=False)
  73. # Publisher fields
  74. publisher_is_draft = models.BooleanField(default=1, editable=False, db_index=True)
  75. publisher_public = models.OneToOneField('self', related_name='publisher_draft', null=True, editable=False)
  76. publisher_state = models.SmallIntegerField(default=0, editable=False, db_index=True)
  77. # Managers
  78. objects = PageManager()
  79. permissions = PagePermissionsPermissionManager()
  80. class Meta:
  81. permissions = (
  82. ('view_page', 'Can view page'),
  83. )
  84. verbose_name = _('page')
  85. verbose_name_plural = _('pages')
  86. ordering = ('site', 'tree_id', 'lft')
  87. app_label = 'cms'
  88. class PublisherMeta:
  89. exclude_fields_append = ['id', 'publisher_is_draft', 'publisher_public',
  90. 'publisher_state', 'moderator_state',
  91. 'placeholders', 'lft', 'rght', 'tree_id',
  92. 'parent']
  93. def __unicode__(self):
  94. title = self.get_menu_title(fallback=True)
  95. if title is None:
  96. title = u""
  97. return u'%s' % (title,)
  98. def get_absolute_url(self, language=None, fallback=True):
  99. if self.is_home():
  100. return reverse('pages-root')
  101. if settings.CMS_FLAT_URLS:
  102. path = self.get_slug(language, fallback)
  103. return urlutils.urljoin(reverse('pages-root'), path)
  104. # else
  105. path = self.get_path(language, fallback)
  106. return urlutils.urljoin(reverse('pages-root'), path)
  107. def move_page(self, target, position='first-child'):
  108. """
  109. Called from admin interface when page is moved. Should be used on
  110. all the places which are changing page position. Used like an interface
  111. to mptt, but after move is done page_moved signal is fired.
  112. Note for issue #1166: url conflicts are handled by updated
  113. check_title_slugs, overwrite_url on the moved page don't need any check
  114. as it remains the same regardless of the page position in the tree
  115. """
  116. # make sure move_page does not break when using INHERIT template
  117. if (position in ('left', 'right')
  118. and not target.parent
  119. and self.template == settings.CMS_TEMPLATE_INHERITANCE_MAGIC):
  120. self.template = self.get_template()
  121. self.move_to(target, position)
  122. # fire signal
  123. from cms.models.moderatormodels import PageModeratorState
  124. self.force_moderation_action = PageModeratorState.ACTION_MOVE
  125. import cms.signals as cms_signals
  126. cms_signals.page_moved.send(sender=Page, instance=self) # titles get saved before moderation
  127. self.save(change_state=True) # always save the page after move, because of publisher
  128. # check the slugs
  129. page_utils.check_title_slugs(self)
  130. def copy_page(self, target, site, position='first-child',
  131. copy_permissions=True, copy_moderation=True,
  132. public_copy=False):
  133. """
  134. copy a page [ and all its descendants to a new location ]
  135. Doesn't checks for add page permissions anymore, this is done in PageAdmin.
  136. Note: public_copy was added in order to enable the creation of a copy for creating the public page during
  137. the publish operation as it sets the publisher_is_draft=False.
  138. Note for issue #1166: when copying pages there is no need to check for conflicting
  139. URLs as pages as copied unplublished.
  140. """
  141. from cms.utils.moderator import update_moderation_message
  142. page_copy = None
  143. if public_copy:
  144. # create a copy of the draft page - existing code loops through pages so added it to a list
  145. pages = [copy.copy(self)]
  146. else:
  147. pages = [self] + list(self.get_descendants().order_by('-rght'))
  148. if not public_copy:
  149. site_reverse_ids = Page.objects.filter(site=site, reverse_id__isnull=False).values_list('reverse_id', flat=True)
  150. if target:
  151. target.old_pk = -1
  152. if position == "first-child":
  153. tree = [target]
  154. elif target.parent_id:
  155. tree = [target.parent]
  156. else:
  157. tree = []
  158. else:
  159. tree = []
  160. if tree:
  161. tree[0].old_pk = tree[0].pk
  162. first = True
  163. # loop over all affected pages (self is included in descendants)
  164. for page in pages:
  165. titles = list(page.title_set.all())
  166. # get all current placeholders (->plugins)
  167. placeholders = list(page.placeholders.all())
  168. origin_id = page.id
  169. # create a copy of this page by setting pk = None (=new instance)
  170. page.old_pk = page.pk
  171. page.pk = None
  172. page.level = None
  173. page.rght = None
  174. page.lft = None
  175. page.tree_id = None
  176. page.published = False
  177. page.moderator_state = Page.MODERATOR_CHANGED
  178. page.publisher_public_id = None
  179. # only set reverse_id on standard copy
  180. if not public_copy:
  181. if page.reverse_id in site_reverse_ids:
  182. page.reverse_id = None
  183. if first:
  184. first = False
  185. if tree:
  186. page.parent = tree[0]
  187. else:
  188. page.parent = None
  189. page.insert_at(target, position)
  190. else:
  191. count = 1
  192. found = False
  193. for prnt in tree:
  194. if prnt.old_pk == page.parent_id:
  195. page.parent = prnt
  196. tree = tree[0:count]
  197. found = True
  198. break
  199. count += 1
  200. if not found:
  201. page.parent = None
  202. tree.append(page)
  203. page.site = site
  204. # override default page settings specific for public copy
  205. if public_copy:
  206. page.published = True
  207. page.publisher_is_draft = False
  208. page.moderator_state = Page.MODERATOR_APPROVED
  209. # we need to set relate this new public copy to its draft page (self)
  210. page.publisher_public = self
  211. # code taken from Publisher publish() overridden here as we need to save the page
  212. # before we are able to use the page object for titles, placeholders etc.. below
  213. # the method has been modified to return the object after saving the instance variable
  214. page = self._publisher_save_public(page)
  215. page_copy = page # create a copy used in the return
  216. else:
  217. # only need to save the page if it isn't public since it is saved above otherwise
  218. page.save()
  219. # copy moderation, permissions if necessary
  220. if settings.CMS_PERMISSION and copy_permissions:
  221. from cms.models.permissionmodels import PagePermission
  222. for permission in PagePermission.objects.filter(page__id=origin_id):
  223. permission.pk = None
  224. permission.page = page
  225. permission.save()
  226. if settings.CMS_MODERATOR and copy_moderation:
  227. from cms.models.moderatormodels import PageModerator
  228. for moderator in PageModerator.objects.filter(page__id=origin_id):
  229. moderator.pk = None
  230. moderator.page = page
  231. moderator.save()
  232. # update moderation message for standard copy
  233. if not public_copy:
  234. update_moderation_message(page, unicode(_('Page was copied.')))
  235. # copy titles of this page
  236. for title in titles:
  237. title.pk = None # setting pk = None creates a new instance
  238. title.publisher_public_id = None
  239. title.published = False
  240. title.page = page
  241. # create slug-copy for standard copy
  242. if not public_copy:
  243. title.save() # We need to save the title in order to
  244. # retrieve it in get_available_slug
  245. title.slug = page_utils.get_available_slug(title)
  246. title.save()
  247. # copy the placeholders (and plugins on those placeholders!)
  248. for ph in placeholders:
  249. plugins = list(ph.cmsplugin_set.all().order_by('tree_id', '-rght'))
  250. try:
  251. ph = page.placeholders.get(slot=ph.slot)
  252. except Placeholder.DoesNotExist:
  253. ph.pk = None # make a new instance
  254. ph.save()
  255. page.placeholders.add(ph)
  256. # update the page copy
  257. page_copy = page
  258. if plugins:
  259. copy_plugins_to(plugins, ph)
  260. # invalidate the menu for this site
  261. menu_pool.clear(site_id=site.pk)
  262. return page_copy # return the page_copy or None
  263. def save(self, no_signals=False, change_state=True, commit=True,
  264. force_with_moderation=False, force_state=None, **kwargs):
  265. """
  266. Args:
  267. commit: True if model should be really saved
  268. force_with_moderation: can be true when new object gets added under
  269. some existing page and this new page will require moderation;
  270. this is because of how this adding works - first save, then move
  271. """
  272. # delete template cache
  273. if hasattr(self, '_template_cache'):
  274. delattr(self, '_template_cache')
  275. # Published pages should always have a publication date
  276. publish_directly, under_moderation = False, False
  277. if self.publisher_is_draft:
  278. # publisher specific stuff, but only on draft model, this is here
  279. # because page initializes publish process
  280. if settings.CMS_MODERATOR:
  281. under_moderation = force_with_moderation or self.pk and bool(self.get_moderator_queryset().count())
  282. created = not bool(self.pk)
  283. if settings.CMS_MODERATOR:
  284. if change_state:
  285. if created:
  286. # new page....
  287. self.moderator_state = Page.MODERATOR_CHANGED
  288. elif not self.requires_approvement():
  289. # always change state to need approvement when there is some change
  290. self.moderator_state = Page.MODERATOR_NEED_APPROVEMENT
  291. if not under_moderation and (self.published or self.publisher_public):
  292. # existing page without moderator - publish it directly if
  293. # published is True
  294. publish_directly = True
  295. elif change_state:
  296. self.moderator_state = Page.MODERATOR_CHANGED
  297. #publish_directly = True - no publisher, no publishing!! - we just
  298. # use draft models in this case
  299. if force_state is not None:
  300. self.moderator_state = force_state
  301. # if the page is published we set the publish date if not set yet.
  302. if self.publication_date is None and self.published:
  303. self.publication_date = timezone.now()
  304. if self.reverse_id == "":
  305. self.reverse_id = None
  306. from cms.utils.permissions import _thread_locals
  307. user = getattr(_thread_locals, "user", None)
  308. if user:
  309. self.changed_by = user.username
  310. else:
  311. self.changed_by = "script"
  312. if not self.pk:
  313. self.created_by = self.changed_by
  314. if commit:
  315. if no_signals: # ugly hack because of mptt
  316. self.save_base(cls=self.__class__, **kwargs)
  317. else:
  318. super(Page, self).save(**kwargs)
  319. #if commit and (publish_directly or created and not under_moderation):
  320. if self.publisher_is_draft:
  321. if self.published:
  322. if commit and publish_directly:
  323. self.publish()
  324. def save_base(self, *args, **kwargs):
  325. """Overriden save_base. If an instance is draft, and was changed, mark
  326. it as dirty.
  327. Dirty flag is used for changed nodes identification when publish method
  328. takes place. After current changes are published, state is set back to
  329. PUBLISHER_STATE_DEFAULT (in publish method).
  330. """
  331. keep_state = getattr(self, '_publisher_keep_state', None)
  332. if self.publisher_is_draft and not keep_state:
  333. self.publisher_state = self.PUBLISHER_STATE_DIRTY
  334. if keep_state:
  335. delattr(self, '_publisher_keep_state')
  336. ret = super(Page, self).save_base(*args, **kwargs)
  337. return ret
  338. def publish(self):
  339. """Overrides Publisher method, because there may be some descendants, which
  340. are waiting for parent to publish, so publish them if possible.
  341. IMPORTANT: @See utils.moderator.approve_page for publishing permissions
  342. Returns: True if page was successfully published.
  343. """
  344. # Publish can only be called on moderated and draft pages
  345. if not self.publisher_is_draft:
  346. return
  347. # publish, but only if all parents are published!!
  348. published = None
  349. if not self.pk:
  350. self.save()
  351. if self._publisher_can_publish():
  352. ########################################################################
  353. # Assign the existing public page in old_public and mark it as
  354. # PUBLISHER_STATE_DELETE
  355. # the draft version was being deleted if I replaced the save()
  356. # below with a delete() directly so the deletion is handle at the end
  357. old_public = self.get_public_object()
  358. if old_public:
  359. old_public.publisher_state = self.PUBLISHER_STATE_DELETE
  360. # store old public on self, pass around instead
  361. self.old_public = old_public
  362. old_public.publisher_public = None # remove the reference to the publisher_draft version of the page so it does not get deleted
  363. old_public.save()
  364. # we hook into the modified copy_page routing to do the heavy lifting of copying the draft page to a new public page
  365. new_public = self.copy_page(target=None, site=self.site,
  366. copy_moderation=False, position=None,
  367. copy_permissions=False, public_copy=True)
  368. # taken from Publisher - copy_page needs to call self._publisher_save_public(copy) for mptt insertion
  369. # insert_at() was maybe calling _create_tree_space() method, in this
  370. # case may tree_id change, so we must update tree_id from db first
  371. # before save
  372. if getattr(self, 'tree_id', None):
  373. me = self._default_manager.get(pk=self.pk)
  374. self.tree_id = me.tree_id
  375. self.published = True
  376. self.publisher_public = new_public
  377. self.moderator_state = Page.MODERATOR_APPROVED
  378. self.publisher_state = self.PUBLISHER_STATE_DEFAULT
  379. self._publisher_keep_state = True
  380. published = True
  381. else:
  382. self.moderator_state = Page.MODERATOR_APPROVED_WAITING_FOR_PARENTS
  383. self.save(change_state=False)
  384. if not published:
  385. # was not published, escape
  386. return
  387. # clean moderation log
  388. self.pagemoderatorstate_set.all().delete()
  389. # we delete the old public page - this only deletes the public page as we
  390. # have removed the old_public.publisher_public=None relationship to the draft page above
  391. if old_public:
  392. # reparent public child pages before delete so they don't get purged as well
  393. for child_page in old_public.children.order_by('lft'):
  394. child_page.move_to(new_public, 'last-child')
  395. child_page.save(change_state=False)
  396. # reload old_public to get correct tree attrs
  397. old_public = Page.objects.get(pk=old_public.pk)
  398. old_public.move_to(None, 'last-child')
  399. # moving the object out of the way berore deleting works, but why?
  400. # finally delete the old public page
  401. old_public.delete()
  402. # page was published, check if there are some childs, which are waiting
  403. # for publishing (because of the parent)
  404. publish_set = self.children.filter(moderator_state = Page.MODERATOR_APPROVED_WAITING_FOR_PARENTS)
  405. for page in publish_set:
  406. # recursive call to all childrens....
  407. page.moderator_state = Page.MODERATOR_APPROVED
  408. page.save(change_state=False)
  409. page.publish()
  410. # fire signal after publishing is done
  411. import cms.signals as cms_signals
  412. cms_signals.post_publish.send(sender=Page, instance=self)
  413. return published
  414. def delete(self):
  415. """Mark public instance for deletion and delete draft.
  416. """
  417. placeholders = self.placeholders.all()
  418. for ph in placeholders:
  419. plugin = CMSPlugin.objects.filter(placeholder=ph)
  420. plugin.delete()
  421. ph.delete()
  422. if self.publisher_public_id:
  423. # mark the public instance for deletion
  424. self.publisher_public.publisher_state = self.PUBLISHER_STATE_DELETE
  425. self.publisher_public.save()
  426. super(Page, self).delete()
  427. def delete_with_public(self):
  428. placeholders = list(self.placeholders.all())
  429. if self.publisher_public_id:
  430. placeholders = placeholders + list(self.publisher_public.placeholders.all())
  431. for ph in placeholders:
  432. plugin = CMSPlugin.objects.filter(placeholder=ph)
  433. plugin.delete()
  434. ph.delete()
  435. if self.publisher_public_id:
  436. self.publisher_public.delete()
  437. super(Page, self).delete()
  438. def get_draft_object(self):
  439. return self
  440. def get_public_object(self):
  441. return self.publisher_public
  442. def get_languages(self):
  443. """
  444. get the list of all existing languages for this page
  445. """
  446. from cms.models.titlemodels import Title
  447. if not hasattr(self, "all_languages"):
  448. self.all_languages = Title.objects.filter(page=self).values_list("language", flat=True).distinct()
  449. self.all_languages = list(self.all_languages)
  450. self.all_languages.sort()
  451. return self.all_languages
  452. def get_cached_ancestors(self, ascending=True):
  453. if ascending:
  454. if not hasattr(self, "ancestors_ascending"):
  455. self.ancestors_ascending = list(self.get_ancestors(ascending))
  456. return self.ancestors_ascending
  457. else:
  458. if not hasattr(self, "ancestors_descending"):
  459. self.ancestors_descending = list(self.get_ancestors(ascending))
  460. return self.ancestors_descending
  461. def get_title_obj(self, language=None, fallback=True, version_id=None, force_reload=False):
  462. """Helper function for accessing wanted / current title.
  463. If wanted title doesn't exists, EmptyTitle instance will be returned.
  464. """
  465. language = self._get_title_cache(language, fallback, version_id, force_reload)
  466. if language in self.title_cache:
  467. return self.title_cache[language]
  468. from cms.models.titlemodels import EmptyTitle
  469. return EmptyTitle()
  470. def get_title_obj_attribute(self, attrname, language=None, fallback=True, version_id=None, force_reload=False):
  471. """Helper function for getting attribute or None from wanted/current title.
  472. """
  473. try:
  474. attribute = getattr(self.get_title_obj(
  475. language, fallback, version_id, force_reload), attrname)
  476. return attribute
  477. except AttributeError:
  478. return None
  479. def get_path(self, language=None, fallback=True, version_id=None, force_reload=False):
  480. """
  481. get the path of the page depending on the given language
  482. """
  483. return self.get_title_obj_attribute("path", language, fallback, version_id, force_reload)
  484. def get_slug(self, language=None, fallback=True, version_id=None, force_reload=False):
  485. """
  486. get the slug of the page depending on the given language
  487. """
  488. return self.get_title_obj_attribute("slug", language, fallback, version_id, force_reload)
  489. def get_title(self, language=None, fallback=True, version_id=None, force_reload=False):
  490. """
  491. get the title of the page depending on the given language
  492. """
  493. return self.get_title_obj_attribute("title", language, fallback, version_id, force_reload)
  494. def get_menu_title(self, language=None, fallback=True, version_id=None, force_reload=False):
  495. """
  496. get the menu title of the page depending on the given language
  497. """
  498. menu_title = self.get_title_obj_attribute("menu_title", language, fallback, version_id, force_reload)
  499. if not menu_title:
  500. return self.get_title(language, True, version_id, force_reload)
  501. return menu_title
  502. def get_page_title(self, language=None, fallback=True, version_id=None, force_reload=False):
  503. """
  504. get the page title of the page depending on the given language
  505. """
  506. page_title = self.get_title_obj_attribute("page_title", language, fallback, version_id, force_reload)
  507. if not page_title:
  508. return self.get_title(language, True, version_id, force_reload)
  509. return page_title
  510. def get_meta_description(self, language=None, fallback=True, version_id=None, force_reload=False):
  511. """
  512. get content for the description meta tag for the page depending on the given language
  513. """
  514. return self.get_title_obj_attribute("meta_description", language, fallback, version_id, force_reload)
  515. def get_meta_keywords(self, language=None, fallback=True, version_id=None, force_reload=False):
  516. """
  517. get content for the keywords meta tag for the page depending on the given language
  518. """
  519. return self.get_title_obj_attribute("meta_keywords", language, fallback, version_id, force_reload)
  520. def get_application_urls(self, language=None, fallback=True, version_id=None, force_reload=False):
  521. """
  522. get application urls conf for application hook
  523. """
  524. return self.get_title_obj_attribute("application_urls", language, fallback, version_id, force_reload)
  525. def get_redirect(self, language=None, fallback=True, version_id=None, force_reload=False):
  526. """
  527. get redirect
  528. """
  529. return self.get_title_obj_attribute("redirect", language, fallback, version_id, force_reload)
  530. def _get_title_cache(self, language, fallback, version_id, force_reload):
  531. if not language:
  532. language = get_language()
  533. load = False
  534. if not hasattr(self, "title_cache") or force_reload:
  535. load = True
  536. self.title_cache = {}
  537. elif not language in self.title_cache:
  538. if fallback:
  539. fallback_langs = i18n.get_fallback_languages(language)
  540. for lang in fallback_langs:
  541. if lang in self.title_cache:
  542. return lang
  543. load = True
  544. if load:
  545. from cms.models.titlemodels import Title
  546. if version_id:
  547. from reversion.models import Version
  548. version = get_object_or_404(Version, pk=version_id)
  549. revs = [related_version.object_version for related_version in version.revision.version_set.all()]
  550. for rev in revs:
  551. obj = rev.object
  552. if obj.__class__ == Title:
  553. self.title_cache[obj.language] = obj
  554. else:
  555. title = Title.objects.get_title(self, language, language_fallback=fallback)
  556. if title:
  557. self.title_cache[title.language] = title
  558. language = title.language
  559. return language
  560. def get_template(self):
  561. """
  562. get the template of this page if defined or if closer parent if
  563. defined or DEFAULT_PAGE_TEMPLATE otherwise
  564. """
  565. if hasattr(self, '_template_cache'):
  566. return self._template_cache
  567. template = None
  568. if self.template:
  569. if self.template != settings.CMS_TEMPLATE_INHERITANCE_MAGIC:
  570. template = self.template
  571. else:
  572. try:
  573. template = self.get_ancestors(ascending=True).exclude(
  574. template=settings.CMS_TEMPLATE_INHERITANCE_MAGIC).values_list('template', flat=True)[0]
  575. except IndexError:
  576. pass
  577. if not template:
  578. template = settings.CMS_TEMPLATES[0][0]
  579. self._template_cache = template
  580. return template
  581. def get_template_name(self):
  582. """
  583. get the textual name (2nd parameter in settings.CMS_TEMPLATES)
  584. of the template of this page or of the nearest
  585. ancestor. failing to find that, return the name of the default template.
  586. """
  587. template = self.get_template()
  588. for t in settings.CMS_TEMPLATES:
  589. if t[0] == template:
  590. return t[1]
  591. return _("default")
  592. def has_view_permission(self, request):
  593. from cms.models.permissionmodels import PagePermission, GlobalPagePermission
  594. from cms.utils.plugins import current_site
  595. if not self.publisher_is_draft and self.publisher_public:
  596. return self.publisher_public.has_view_permission(request)
  597. # does any restriction exist?
  598. # inherited and direct
  599. is_restricted = PagePermission.objects.for_page(page=self).filter(can_view=True).exists()
  600. if request.user.is_authenticated():
  601. site = current_site(request)
  602. global_perms_q = Q(can_view=True) & Q(
  603. Q(sites__in=[site]) | Q(sites__isnull=True)
  604. )
  605. global_view_perms = GlobalPagePermission.objects.with_user(
  606. request.user).filter(global_perms_q).exists()
  607. # a global permission was given to the request's user
  608. if global_view_perms:
  609. return True
  610. elif not is_restricted:
  611. if ((settings.CMS_PUBLIC_FOR == 'all') or
  612. (settings.CMS_PUBLIC_FOR == 'staff' and
  613. request.user.is_staff)):
  614. return True
  615. # a restricted page and an authenticated user
  616. elif is_restricted:
  617. opts = self._meta
  618. codename = '%s.view_%s' % (opts.app_label, opts.object_name.lower())
  619. user_perm = request.user.has_perm(codename)
  620. generic_perm = self.has_generic_permission(request, "view")
  621. return (user_perm or generic_perm)
  622. else:
  623. #anonymous user
  624. if is_restricted or not settings.CMS_PUBLIC_FOR == 'all':
  625. # anyonymous user, page has restriction and global access is permitted
  626. return False
  627. else:
  628. # anonymous user, no restriction saved in database
  629. return True
  630. # Authenticated user
  631. # Django wide auth perms "can_view" or cms auth perms "can_view"
  632. opts = self._meta
  633. codename = '%s.view_%s' % (opts.app_label, opts.object_name.lower())
  634. return (request.user.has_perm(codename) or
  635. self.has_generic_permission(request, "view"))
  636. def has_change_permission(self, request):
  637. opts = self._meta
  638. if request.user.is_superuser:
  639. return True
  640. return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) and \
  641. self.has_generic_permission(request, "change")
  642. def has_delete_permission(self, request):
  643. opts = self._meta
  644. if request.user.is_superuser:
  645. return True
  646. return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) and \
  647. self.has_generic_permission(request, "delete")
  648. def has_publish_permission(self, request):
  649. return self.has_generic_permission(request, "publish")
  650. def has_advanced_settings_permission(self, request):
  651. return self.has_generic_permission(request, "advanced_settings")
  652. def has_change_permissions_permission(self, request):
  653. """
  654. Has user ability to change permissions for current page?
  655. """
  656. return self.has_generic_permission(request, "change_permissions")
  657. def has_add_permission(self, request):
  658. """
  659. Has user ability to add page under current page?
  660. """
  661. return self.has_generic_permission(request, "add")
  662. def has_move_page_permission(self, request):
  663. """Has user ability to move current page?
  664. """
  665. return self.has_generic_permission(request, "move_page")
  666. def has_moderate_permission(self, request):
  667. """
  668. Has user ability to moderate current page? If moderation isn't
  669. installed, nobody can moderate.
  670. """
  671. if not settings.CMS_MODERATOR:
  672. return False
  673. return self.has_generic_permission(request, "moderate")
  674. def has_generic_permission(self, request, perm_type):
  675. """
  676. Return true if the current user has permission on the page.
  677. Return the string 'All' if the user has all rights.
  678. """
  679. att_name = "permission_%s_cache" % perm_type
  680. if not hasattr(self, "permission_user_cache") or not hasattr(self, att_name) \
  681. or request.user.pk != self.permission_user_cache.pk:
  682. from cms.utils.permissions import has_generic_permission
  683. self.permission_user_cache = request.user
  684. setattr(self, att_name, has_generic_permission(
  685. self.id, request.user, perm_type, self.site_id))
  686. if getattr(self, att_name):
  687. self.permission_edit_cache = True
  688. return getattr(self, att_name)
  689. def is_home(self):
  690. if self.parent_id:
  691. return False
  692. else:
  693. try:
  694. return self.home_pk_cache == self.pk
  695. except NoHomeFound:
  696. pass
  697. return False
  698. def get_home_pk_cache(self):
  699. attr = "%s_home_pk_cache_%s" % (self.publisher_is_draft and "draft" or "public", self.site_id)
  700. if not hasattr(self, attr):
  701. setattr(self, attr, self.get_object_queryset().get_home(self.site).pk)
  702. return getattr(self, attr)
  703. def set_home_pk_cache(self, value):
  704. attr = "%s_home_pk_cache_%s" % (self.publisher_is_draft and "draft" or "public", self.site_id)
  705. setattr(self, attr, value)
  706. home_pk_cache = property(get_home_pk_cache, set_home_pk_cache)
  707. def get_media_path(self, filename):
  708. """
  709. Returns path (relative to MEDIA_ROOT/MEDIA_URL) to directory for storing page-scope files.
  710. This allows multiple pages to contain files with identical names without namespace issues.
  711. Plugins such as Picture can use this method to initialise the 'upload_to' parameter for
  712. File-based fields. For example:
  713. image = models.ImageField(_("image"), upload_to=CMSPlugin.get_media_path)
  714. where CMSPlugin.get_media_path calls self.page.get_media_path
  715. This location can be customised using the CMS_PAGE_MEDIA_PATH setting
  716. """
  717. return join(settings.CMS_PAGE_MEDIA_PATH, "%d" % self.id, filename)
  718. def last_page_states(self):
  719. """Returns last five page states, if they exist, optimized, calls sql
  720. query only if some states available
  721. """
  722. # TODO: optimize SQL... 1 query per page
  723. if settings.CMS_MODERATOR:
  724. has_moderator_state = getattr(self, '_has_moderator_state_cache', None)
  725. if has_moderator_state == False:
  726. return self.pagemoderatorstate_set.none()
  727. return self.pagemoderatorstate_set.all().order_by('created',)[:5]
  728. return self.pagemoderatorstate_set.none()
  729. def get_moderator_queryset(self):
  730. """Returns ordered set of all PageModerator instances, which should
  731. moderate this page
  732. """
  733. from cms.models.moderatormodels import PageModerator
  734. if not settings.CMS_MODERATOR or not self.tree_id:
  735. return PageModerator.objects.get_empty_query_set()
  736. query = Q(page__tree_id=self.tree_id, page__level__lt=self.level, moderate_descendants=True) | \
  737. Q(page__tree_id=self.tree_id, page__level=self.level - 1, moderate_children=True) | \
  738. Q(page__pk=self.pk, moderate_page=True)
  739. return PageModerator.objects.distinct().filter(query).order_by('page__level')
  740. def is_under_moderation(self):
  741. return bool(self.get_moderator_queryset().count())
  742. def is_approved(self):
  743. """Returns true, if page is approved and published, or approved, but
  744. parents are missing..
  745. """
  746. return self.moderator_state in (Page.MODERATOR_APPROVED, Page.MODERATOR_APPROVED_WAITING_FOR_PARENTS)
  747. def is_public_published(self):
  748. """Returns true if public model is published.
  749. """
  750. if hasattr(self, 'public_published_cache'):
  751. # if it was cached in change list, return cached value
  752. return self.public_published_cache
  753. # othervise make db lookup
  754. if self.publisher_public_id:
  755. return self.publisher_public.published
  756. #return is_public_published(self)
  757. return False
  758. def reload(self):
  759. """
  760. Reload a page from the database
  761. """
  762. return Page.objects.get(pk=self.pk)
  763. def requires_approvement(self):
  764. return self.moderator_state in (Page.MODERATOR_NEED_APPROVEMENT, Page.MODERATOR_NEED_DELETE_APPROVEMENT)
  765. def get_moderation_value(self, user):
  766. """Returns page moderation value for given user, moderation value is
  767. sum of moderations.
  768. """
  769. moderation_value = getattr(self, '_moderation_value_cache', None)
  770. if moderation_value is not None and self._moderation_value_cache_for_user_id == user.pk:
  771. return moderation_value
  772. try:
  773. page_moderator = self.pagemoderator_set.get(user=user)
  774. except ObjectDoesNotExist:
  775. return 0
  776. moderation_value = page_moderator.get_decimal()
  777. self._moderation_value_cache = moderation_value
  778. self._moderation_value_cache_for_user_id = user
  779. return moderation_value
  780. def get_object_queryset(self):
  781. """Returns smart queryset depending on object type - draft / public
  782. """
  783. qs = self.__class__.objects
  784. return self.publisher_is_draft and qs.drafts() or qs.public()
  785. def _publisher_can_publish(self):
  786. """Is parent of this object already published?
  787. """
  788. if self.parent_id:
  789. try:
  790. return bool(self.parent.publisher_public_id)
  791. except AttributeError:
  792. raise MpttPublisherCantPublish
  793. return True
  794. def _publisher_get_public_copy(self):
  795. """This is here because of the relation between CMSPlugins - model
  796. inheritance.
  797. eg. Text.objects.get(pk=1).publisher_public returns instance of CMSPlugin
  798. instead of instance of Text, thats why this method must be overriden in
  799. CMSPlugin.
  800. """
  801. return self.publisher_public
  802. def get_next_filtered_sibling(self, **filters):
  803. """Very simillar to original mptt method, but adds support for filters.
  804. Returns this model instance's next sibling in the tree, or
  805. ``None`` if it doesn't have a next sibling.
  806. """
  807. opts = self._mptt_meta
  808. if self.is_root_node():
  809. filters.update({
  810. '%s__isnull' % opts.parent_attr: True,
  811. '%s__gt' % opts.tree_id_attr: getattr(self, opts.tree_id_attr),
  812. })
  813. else:
  814. filters.update({
  815. opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
  816. '%s__gt' % opts.left_attr: getattr(self, opts.right_attr),
  817. })
  818. # publisher stuff
  819. filters.update({
  820. 'publisher_is_draft': self.publisher_is_draft
  821. })
  822. # multisite
  823. filters.update({
  824. 'site__id': self.site_id
  825. })
  826. sibling = None
  827. try:
  828. sibling = self._tree_manager.filter(**filters)[0]
  829. except IndexError:
  830. pass
  831. return sibling
  832. def get_previous_filtered_sibling(self, **filters):
  833. """Very simillar to original mptt method, but adds support for filters.
  834. Returns this model instance's previous sibling in the tree, or
  835. ``None`` if it doesn't have a previous sibling.
  836. """
  837. opts = self._mptt_meta
  838. if self.is_root_node():
  839. filters.update({
  840. '%s__isnull' % opts.parent_attr: True,
  841. '%s__lt' % opts.tree_id_attr: getattr(self, opts.tree_id_attr),
  842. })
  843. order_by = '-%s' % opts.tree_id_attr
  844. else:
  845. filters.update({
  846. opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
  847. '%s__lt' % opts.right_attr: getattr(self, opts.left_attr),
  848. })
  849. order_by = '-%s' % opts.right_attr
  850. # publisher stuff
  851. filters.update({
  852. 'publisher_is_draft': self.publisher_is_draft
  853. })
  854. # multisite
  855. filters.update({
  856. 'site__id': self.site_id
  857. })
  858. sibling = None
  859. try:
  860. sibling = self._tree_manager.filter(**filters).order_by(order_by)[0]
  861. except IndexError:
  862. pass
  863. return sibling
  864. def _publisher_save_public(self, obj):
  865. """Mptt specific stuff before the object can be saved, overrides original
  866. publisher method.
  867. Args:
  868. obj - public variant of `self` to be saved.
  869. """
  870. prev_sibling = self.get_previous_filtered_sibling(publisher_public__isnull=False)
  871. if not self.publisher_public_id:
  872. # is there anybody on left side?
  873. if prev_sibling:
  874. obj.insert_at(prev_sibling.publisher_public, position='right', save=False)
  875. else:
  876. # it is a first time published object, perform insert_at:
  877. parent, public_parent = self.parent, None
  878. if parent:
  879. public_parent = parent.publisher_public
  880. if public_parent:
  881. obj.insert_at(public_parent, save=False)
  882. else:
  883. # check if object was moved / structural tree change
  884. prev_public_sibling = self.old_public.get_previous_filtered_sibling()
  885. if not self.level == self.old_public.level or \
  886. not (self.level > 0 and self.parent.publisher_public == self.old_public.parent) or \
  887. not prev_sibling == prev_public_sibling == None or \
  888. (prev_sibling and prev_sibling.publisher_public_id == prev_public_sibling.id):
  889. if prev_sibling:
  890. obj.insert_at(prev_sibling.publisher_public, position="right")
  891. elif self.parent:
  892. # move as a first child to parent
  893. target = self.parent.publisher_public
  894. obj.insert_at(target, position='first-child')
  895. else:
  896. # it is a move from the right side or just save
  897. next_sibling = self.get_next_filtered_sibling()
  898. if next_sibling and next_sibling.publisher_public_id:
  899. obj.insert_at(next_sibling.publisher_public, position="left")
  900. else:
  901. # insert at last public position
  902. prev_sibling = self.old_public.get_previous_filtered_sibling()
  903. if prev_sibling:
  904. obj.insert_at(prev_sibling, position="right")
  905. elif self.old_public.parent:
  906. # move as a first child to parent
  907. target = self.old_public.parent
  908. obj.insert_at(target, position='first-child')
  909. else:
  910. # it is a move from the right side or just save
  911. next_sibling = self.old_public.get_next_filtered_sibling()
  912. if next_sibling and next_sibling.publisher_public_id:
  913. obj.insert_at(next_sibling, position="left")
  914. # or none structural change, just save
  915. obj.save()
  916. return obj
  917. def rescan_placeholders(self):
  918. """
  919. Rescan and if necessary create placeholders in the current template.
  920. """
  921. # inline import to prevent circular imports
  922. from cms.utils.plugins import get_placeholders
  923. placeholders = get_placeholders(self.get_template())
  924. found = {}
  925. for placeholder in self.placeholders.all():
  926. if placeholder.slot in placeholders:
  927. found[placeholder.slot] = placeholder
  928. for placeholder_name in placeholders:
  929. if not placeholder_name in found:
  930. placeholder = Placeholder.objects.create(slot=placeholder_name)
  931. self.placeholders.add(placeholder)
  932. found[placeholder_name] = placeholder
  933. def _reversion():
  934. exclude_fields = ['publisher_is_draft', 'publisher_public', 'publisher_state']
  935. reversion_register(
  936. Page,
  937. follow=["title_set", "placeholders", "pagepermission_set"],
  938. exclude_fields=exclude_fields
  939. )
  940. _reversion()