Skip to content

Commit

Permalink
Merge branch 'develop' into fix_delete_page
Browse files Browse the repository at this point in the history
Conflicts:
	cms/admin/pageadmin.py
	cms/models/pagemodel.py
  • Loading branch information
digi604 committed Apr 5, 2013
2 parents 0a40dd6 + 2ae88a4 commit 642dde8
Show file tree
Hide file tree
Showing 17 changed files with 360 additions and 197 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.txt
Expand Up @@ -124,6 +124,8 @@ Please see Install/2.4 release notes *before* attempting to upgrade to version 2

- Compatibility with Django 1.4 and 1.5 (1.3 support dropped)
- Support for Python 2.5 dropped
- CMS_MAX_PAGE_PUBLISH_REVERSIONS has been added
- Reversion integration has changed to limit DB size
- CMS_LANGUAGE setting has changed
- CMS_HIDE_UNTRANSLATED setting removed
- CMS_LANGUAGE_FALLBACK setting removed
Expand All @@ -133,5 +135,6 @@ Please see Install/2.4 release notes *before* attempting to upgrade to version 2
- MultilingualMiddleware has been removed
- CMS_FLAT_URLS has been removed


==== NEXT ====
- Fixed #1543: Nasty empty line in cms/plugins/link.html
254 changes: 147 additions & 107 deletions cms/admin/pageadmin.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cms/cms_toolbar.py
Expand Up @@ -91,7 +91,7 @@ def can_change(self):

@property
def edit_mode(self):
return self.is_staff and self.edit_mode_switcher.get_state(self.request)
return self.is_staff and self.edit_mode_switcher.get_state(self.request) and self.can_change

@property
def show_toolbar(self):
Expand Down
95 changes: 65 additions & 30 deletions cms/models/pagemodel.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from datetime import timedelta

from cms import constants
from cms.utils.conf import get_cms_setting
from django.core.exceptions import PermissionDenied
Expand All @@ -8,10 +10,9 @@
from cms.models.placeholdermodel import Placeholder
from cms.models.pluginmodel import CMSPlugin
from cms.publisher.errors import MpttPublisherCantPublish
from cms.utils import i18n, urlutils, page as page_utils
from cms.utils import i18n, page as page_utils
from cms.utils.copy_plugins import copy_plugins_to
from cms.utils.helpers import reversion_register
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.db import models
Expand All @@ -22,8 +23,6 @@
from menus.menu_pool import menu_pool
from mptt.models import MPTTModel
from os.path import join
from datetime import timedelta
import copy


class Page(MPTTModel):
Expand All @@ -49,19 +48,27 @@ class Page(MPTTModel):
creation_date = models.DateTimeField(auto_now_add=True)
changed_date = models.DateTimeField(auto_now=True)

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)
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)
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)
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)
in_navigation = models.BooleanField(_("in navigation"), default=True, db_index=True)
soft_root = models.BooleanField(_("soft root"), db_index=True, default=False, help_text=_("All ancestors will not be displayed in the navigation"))
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"))
soft_root = models.BooleanField(_("soft root"), db_index=True, default=False,
help_text=_("All ancestors will not be displayed in the navigation"))
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"))
navigation_extenders = models.CharField(_("attached menu"), max_length=80, db_index=True, blank=True, null=True)
published = models.BooleanField(_("is published"), blank=True)

template = models.CharField(_("template"), max_length=100, choices=template_choices, help_text=_('The template used to render the content.'))
template = models.CharField(_("template"), max_length=100, choices=template_choices,
help_text=_('The template used to render the content.'))
site = models.ForeignKey(Site, help_text=_('The site the page is accessible at.'), verbose_name=_("site"))

login_required = models.BooleanField(_("login required"), default=False)
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"))
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"))

level = models.PositiveIntegerField(db_index=True, editable=False)
lft = models.PositiveIntegerField(db_index=True, editable=False)
Expand Down Expand Up @@ -93,9 +100,9 @@ class Meta:

class PublisherMeta:
exclude_fields_append = ['id', 'publisher_is_draft', 'publisher_public',
'publisher_state', 'moderator_state',
'placeholders', 'lft', 'rght', 'tree_id',
'parent']
'publisher_state', 'moderator_state',
'placeholders', 'lft', 'rght', 'tree_id',
'parent']

def __unicode__(self):
title = self.get_menu_title(fallback=True)
Expand Down Expand Up @@ -134,13 +141,14 @@ def move_page(self, target, position='first-child'):
# and moving to a top level position

if (position in ('left', 'right')
and not target.parent
and self.template == constants.TEMPLATE_INHERITANCE_MAGIC):
and not target.parent
and self.template == constants.TEMPLATE_INHERITANCE_MAGIC):
self.template = self.get_template()
self.move_to(target, position)

# fire signal
import cms.signals as cms_signals

cms_signals.page_moved.send(sender=Page, instance=self) # titles get saved before moderation
self.save() # always save the page after move, because of publisher
# check the slugs
Expand All @@ -167,6 +175,7 @@ def _copy_titles(self, target):
title.save()
if old_titles:
from titlemodels import Title

Title.objects.filter(id__in=old_titles.values()).delete()

def _copy_contents(self, target):
Expand Down Expand Up @@ -286,6 +295,7 @@ def copy_page(self, target, site, position='first-child',
# copy permissions if necessary
if get_cms_setting('PERMISSION') and copy_permissions:
from cms.models.permissionmodels import PagePermission

for permission in PagePermission.objects.filter(page__id=origin_id):
permission.pk = None
permission.page = page
Expand Down Expand Up @@ -340,6 +350,7 @@ def save(self, no_signals=False, commit=True, **kwargs):
self.reverse_id = None

from cms.utils.permissions import _thread_locals

user = getattr(_thread_locals, "user", None)
if user:
self.changed_by = user.username
Expand Down Expand Up @@ -467,6 +478,7 @@ def publish(self):

# fire signal after publishing is done
import cms.signals as cms_signals

cms_signals.post_publish.send(sender=Page, instance=self)

return published
Expand Down Expand Up @@ -515,7 +527,7 @@ def revert(self):
public = self.publisher_public
public._copy_titles(self)
if self.parent != (self.publisher_public.parent_id and
self.publisher_public.parent.publisher_draft):
self.publisher_public.parent.publisher_draft):
# We don't send the signals here
self.move_to(public.parent.publisher_draft)
public._copy_contents(self)
Expand Down Expand Up @@ -619,14 +631,15 @@ def get_title_obj(self, language=None, fallback=True, version_id=None, force_rel
if language in self.title_cache:
return self.title_cache[language]
from cms.models.titlemodels import EmptyTitle

return EmptyTitle()

def get_title_obj_attribute(self, attrname, language=None, fallback=True, version_id=None, force_reload=False):
"""Helper function for getting attribute or None from wanted/current title.
"""
try:
attribute = getattr(self.get_title_obj(
language, fallback, version_id, force_reload), attrname)
language, fallback, version_id, force_reload), attrname)
return attribute
except AttributeError:
return None
Expand Down Expand Up @@ -707,8 +720,10 @@ def _get_title_cache(self, language, fallback, version_id, force_reload):
load = True
if load:
from cms.models.titlemodels import Title

if version_id:
from reversion.models import Version

version = get_object_or_404(Version, pk=version_id)
revs = [related_version.object_version for related_version in version.revision.version_set.all()]
for rev in revs:
Expand Down Expand Up @@ -762,7 +777,7 @@ def has_view_permission(self, request):

if not self.publisher_is_draft:
return self.publisher_draft.has_view_permission(request)
# does any restriction exist?
# does any restriction exist?
# inherited and direct
is_restricted = PagePermission.objects.for_page(page=self).filter(can_view=True).exists()
if request.user.is_authenticated():
Expand All @@ -780,7 +795,7 @@ def has_view_permission(self, request):
elif not is_restricted:
if ((get_cms_setting('PUBLIC_FOR') == 'all') or
(get_cms_setting('PUBLIC_FOR') == 'staff' and
request.user.is_staff)):
request.user.is_staff)):
return True

# a restricted page and an authenticated user
Expand All @@ -799,7 +814,7 @@ def has_view_permission(self, request):
else:
# anonymous user, no restriction saved in database
return True
# Authenticated user
# Authenticated user
# Django wide auth perms "can_view" or cms auth perms "can_view"
opts = self._meta
codename = '%s.view_%s' % (opts.app_label, opts.object_name.lower())
Expand All @@ -811,21 +826,22 @@ def has_change_permission(self, request):
if request.user.is_superuser:
return True
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) and \
self.has_generic_permission(request, "change")
self.has_generic_permission(request, "change")

def has_delete_permission(self, request):
opts = self._meta
if request.user.is_superuser:
return True
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) and \
self.has_generic_permission(request, "delete")
self.has_generic_permission(request, "delete")

def has_publish_permission(self, request):
if request.user.is_superuser:
return True
opts = self._meta
return request.user.has_perm(opts.app_label + '.' + "publish_page") and \
self.has_generic_permission(request, "publish")
self.has_generic_permission(request, "publish")

has_moderate_permission = has_publish_permission

def has_advanced_settings_permission(self, request):
Expand Down Expand Up @@ -855,11 +871,12 @@ def has_generic_permission(self, request, perm_type):
"""
att_name = "permission_%s_cache" % perm_type
if not hasattr(self, "permission_user_cache") or not hasattr(self, att_name) \
or request.user.pk != self.permission_user_cache.pk:
or request.user.pk != self.permission_user_cache.pk:
from cms.utils.permissions import has_generic_permission

self.permission_user_cache = request.user
setattr(self, att_name, has_generic_permission(
self.id, request.user, perm_type, self.site_id))
self.id, request.user, perm_type, self.site_id))
if getattr(self, att_name):
self.permission_edit_cache = True
return getattr(self, att_name)
Expand All @@ -884,6 +901,7 @@ def set_home_pk_cache(self, value):

attr = "%s_home_pk_cache_%s" % (self.publisher_is_draft and "draft" or "public", self.site_id)
setattr(self, attr, value)

home_pk_cache = property(get_home_pk_cache, set_home_pk_cache)

def clear_home_pk_cache(self):
Expand Down Expand Up @@ -912,13 +930,27 @@ def last_page_states(self):
self._moderator_state_cache = result
return result[:5]

def delete_requested(self):
""" Checks whether there are any delete requests for this page.
Uses the same cache as last_page_states to minimize DB requests
"""
from cms.models import PageModeratorState

result = getattr(self, '_moderator_state_cache', None)
if result is None:
return self.pagemoderatorstate_set.get_delete_actions().exists()
for state in result:
if state.action == PageModeratorState.ACTION_DELETE:
return True
return False

def is_public_published(self):
"""Returns true if public model is published.
"""
if hasattr(self, '_public_published_cache'):
# if it was cached in change list, return cached value
return self._public_published_cache
# If we have a public version it will be published as well.
# If we have a public version it will be published as well.
# If it isn't published, it should be deleted.
return self.published and self.publisher_public_id and self.publisher_public.published

Expand Down Expand Up @@ -957,7 +989,7 @@ def get_next_filtered_sibling(self, **filters):
})
else:
filters.update({
opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
'%s__gt' % opts.left_attr: getattr(self, opts.right_attr),
})

Expand Down Expand Up @@ -991,7 +1023,7 @@ def get_previous_filtered_sibling(self, **filters):
order_by = '-%s' % opts.tree_id_attr
else:
filters.update({
opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
opts.parent_attr: getattr(self, '%s_id' % opts.parent_attr),
'%s__lt' % opts.right_attr: getattr(self, opts.left_attr),
})
order_by = '-%s' % opts.right_attr
Expand Down Expand Up @@ -1040,8 +1072,8 @@ def _publisher_save_public(self, obj):
# check if object was moved / structural tree change
prev_public_sibling = obj.get_previous_filtered_sibling()
if self.level != obj.level or \
public_parent != obj.parent or \
public_prev_sib != prev_public_sibling:
public_parent != obj.parent or \
public_prev_sib != prev_public_sibling:
if public_prev_sib:
obj.move_to(public_prev_sib, position="right")
elif public_parent:
Expand All @@ -1061,6 +1093,7 @@ def rescan_placeholders(self):
"""
# inline import to prevent circular imports
from cms.utils.plugins import get_placeholders

placeholders = get_placeholders(self.get_template())
found = {}
for placeholder in self.placeholders.all():
Expand All @@ -1081,4 +1114,6 @@ def _reversion():
follow=["title_set", "placeholders", "pagepermission_set"],
exclude_fields=exclude_fields
)


_reversion()
6 changes: 6 additions & 0 deletions cms/tests/admin.py
Expand Up @@ -373,6 +373,12 @@ def test_changelist_tree(self):
# but not any further down the tree
self.assertFalse('id="page_%s"' % third_level_page.pk in response.content)

def test_unihandecode_doesnt_break_404_in_admin(self):
admin = self.get_superuser()
self.client.login(username='admin', password='admin')
response = self.client.get('/en/admin/cms/page/1/?language=en')
self.assertEqual(response.status_code, 404)


class AdminFieldsetTests(CMSTestCase):
def validate_attributes(self, a, b, ignore=None):
Expand Down
2 changes: 2 additions & 0 deletions cms/tests/multilingual.py
Expand Up @@ -193,6 +193,7 @@ def test_detail_view_404_when_no_language_is_found(self):
method='GET',
COOKIES={},
META={},
user=User(),
)
self.assertRaises(Http404, details, request, '')

Expand Down Expand Up @@ -222,6 +223,7 @@ def test_detail_view_fallback_language(self):
method='GET',
COOKIES={},
META={},
user=User(),
)

response = details(request, '')
Expand Down

0 comments on commit 642dde8

Please sign in to comment.