Skip to content

Commit

Permalink
Merge pull request #3927 from yakky/merge/3.0.11
Browse files Browse the repository at this point in the history
Merge 3.0.12 back
  • Loading branch information
yakky committed Mar 6, 2015
2 parents 8a09f92 + 4ef6c13 commit 95a5926
Show file tree
Hide file tree
Showing 46 changed files with 913 additions and 301 deletions.
2 changes: 0 additions & 2 deletions .coverage.rc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ branch = False
omit =
cms/migrations/*
cms/south_migrations/*
cms/plugins/*/migrations/*
cms/stacks/migrations/*
cms/tests/*
cms/test_utils/*
menus/migrations/*
Expand Down
2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Current and previous core designers:

* Christian Bertschy

Contributors (based on gitlog) (440):
Contributors (based on gitlog) (441):

* A. Bram Neijt
* aaloy
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,31 @@ Please see Install/2.4 release notes *before* attempting to upgrade to version 2
- Fixed an issue where extra slashed would appear in apphooked URLs when APPEND_SLASH=False
- Fixed issues relating to the logout function

=== 3.0.11 (2015-03-05) ===

- Core support for multiple instances of the same apphook'ed application
- Fixed the template tag `render_model_add`
- Fixed an issue with reverting to Live
- Fixed a missing migration issue
- Fixed an issue when using the PageField widget
- Fixed an issue where duplicate page slugs is not prevented in some cases
- Fixed an issue where copying a page didn't copy its extensions
- Fixed an issue where translations where broken when operating on a page
- Fixed an edge-case SQLite issue under Django 1.7
- Fixed an issue with confirmation dialog
- Fixed an issue with deprecated 'mimetype'
- Fixed an issue where `cms check`
- Documentation updates

=== 3.0.12 (2015-03-06) ===

- Fixed a typo in JavaScript which prevents page tree from working

==== 3.1 (unreleased) ===

- Remove django-mptt in favor of django-treebeard
- Remove compatibility with Django 1.4 / 1.5
- General code cleanup
- Simplify loading of view restrictions in the menu
- south is not marked as optional; to use south on Django 1.6 install django-cms[south]

2 changes: 1 addition & 1 deletion cms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
__version__ = '3.1.0.dev1'
__version__ = '3.1.0.dev2'

default_app_config = 'cms.apps.CMSConfig'
117 changes: 82 additions & 35 deletions cms/admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db.models.fields import BooleanField
from django.forms.util import ErrorList
from django.forms.widgets import HiddenInput
Expand All @@ -14,9 +14,9 @@

from cms.apphook_pool import apphook_pool
from cms.constants import PAGE_TYPES_ID
from cms.forms.widgets import UserSelectAdminWidget, AppHookSelect
from cms.models import Page, PagePermission, PageUser, ACCESS_PAGE, PageUserGroup, Title, EmptyTitle, \
GlobalPagePermission
from cms.forms.widgets import UserSelectAdminWidget, AppHookSelect, ApplicationConfigSelect
from cms.models import (Page, PagePermission, PageUser, ACCESS_PAGE, PageUserGroup, Title,
EmptyTitle, GlobalPagePermission)
from cms.utils.compat.forms import UserCreationForm
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_language_tuple
Expand Down Expand Up @@ -197,20 +197,26 @@ class AdvancedSettingsForm(forms.ModelForm):
)

redirect = PageSmartLinkField(label=_('Redirect'), required=False,
help_text=_('Redirects to this URL.'), placeholder_text=_('Start typing...'),
ajax_view='admin:cms_page_get_published_pagelist'
help_text=_('Redirects to this URL.'),
placeholder_text=_('Start typing...'),
ajax_view='admin:cms_page_get_published_pagelist'
)

language = forms.ChoiceField(label=_("Language"), choices=get_language_tuple(),
help_text=_('The current language of the content fields.'))

# This is really a 'fake' field which does not correspond to any Page attribute
# But creates a stub field to be populate by js
application_configs = forms.ChoiceField(label=_('Application configurations'),
choices=(), required=False,)
fieldsets = (
(None, {
'fields': ('overwrite_url','redirect'),
'fields': ('overwrite_url', 'redirect'),
}),
('Language independent options', {
'fields': ('site', 'template', 'reverse_id', 'soft_root', 'navigation_extenders',
'application_urls', 'application_namespace', "xframe_options",)
'application_urls', 'application_namespace', 'application_configs',
'xframe_options',)
})
)

Expand All @@ -225,23 +231,52 @@ def __init__(self, *args, **kwargs):
if not self.fields['language'].initial:
self.fields['language'].initial = get_language()
if 'navigation_extenders' in self.fields:
self.fields['navigation_extenders'].widget = forms.Select({},
[('', "---------")] + menu_pool.get_menus_by_attribute("cms_enabled", True))
self.fields['navigation_extenders'].widget = forms.Select(
{}, [('', "---------")] + menu_pool.get_menus_by_attribute("cms_enabled", True))
if 'application_urls' in self.fields:
# Prepare a dict mapping the apps by class name ('PollApp') to
# their app_name attribute ('polls'), if any.
app_namespaces = {}
app_configs = {}
for hook in apphook_pool.get_apphooks():
app = apphook_pool.get_apphook(hook[0])
if app.app_name:
app_namespaces[hook[0]] = app.app_name
if app.app_config:
app_configs[hook[0]] = app

self.fields['application_urls'].widget = AppHookSelect(
attrs={'id':'application_urls'},
app_namespaces=app_namespaces,
attrs={'id': 'application_urls'},
app_namespaces=app_namespaces
)
self.fields['application_urls'].choices = [('', "---------")] + apphook_pool.get_apphooks()

if app_configs:
self.fields['application_configs'].widget = ApplicationConfigSelect(
attrs={'id': 'application_configs'},
app_configs=app_configs)

if self.data.get('application_urls', False) and self.data['application_urls'] in app_configs:
self.fields['application_configs'].choices = [(config.pk, force_text(config)) for config in app_configs[self.data['application_urls']].get_configs()]

apphook = self.data.get('application_urls', False)
try:
config = apphook_pool.get_apphook(apphook).get_configs().get(namespace=self.initial['application_namespace'])
self.fields['application_configs'].initial = config.pk
except ObjectDoesNotExist:
# Provided apphook configuration doesn't exist (anymore),
# just skip it
# The user will choose another value anyway
pass
else:
# If app_config apphook is not selected, drop any value
# for application_configs do avoid the field dato for
# being validated by the field itself
try:
del self.data['application_configs']
except KeyError:
pass

if 'redirect' in self.fields:
self.fields['redirect'].widget.language = self.fields['language'].initial

Expand All @@ -259,34 +294,46 @@ def clean(self):
# The field 'application_namespace' is a misnomer. It should be
# 'instance_namespace'.
instance_namespace = cleaned_data.get('application_namespace', None)
application_config = cleaned_data.get('application_configs', None)
if apphook:
# The attribute on the apps 'app_name' is a misnomer, it should be
# 'application_namespace'.
application_namespace = apphook_pool.get_apphook(apphook).app_name
if application_namespace and not instance_namespace:
if Page.objects.filter(
publisher_is_draft=True,
application_urls=apphook,
application_namespace=application_namespace
).exclude(pk=self.instance.pk).count():
# Looks like there's already one with the default instance
# namespace defined.
self._errors['application_urls'] = ErrorList([
_('''You selected an apphook with an "app_name".
You must enter a unique instance name.''')
])
else:
# OK, there are zero instances of THIS app that use the
# default instance namespace, so, since the user didn't
# provide one, we'll use the default. NOTE: The following
# line is really setting the "instance namespace" of the
# new app to the app’s "application namespace", which is
# the default instance namespace.
self.cleaned_data['application_namespace'] = application_namespace
# application_config wins over application_namespace
if application_config:
# the value of the application config namespace is saved in
# the 'usual' namespace field to be backward compatible
# with existing apphooks
config = apphook_pool.get_apphook(apphook).get_configs().get(pk=int(application_config))
self.cleaned_data['application_namespace'] = config.namespace
else:
# The attribute on the apps 'app_name' is a misnomer, it should be
# 'application_namespace'.
application_namespace = apphook_pool.get_apphook(apphook).app_name
if application_namespace and not instance_namespace:
if Page.objects.filter(
publisher_is_draft=True,
application_urls=apphook,
application_namespace=application_namespace
).exclude(pk=self.instance.pk).count():
# Looks like there's already one with the default instance
# namespace defined.
self._errors['application_urls'] = ErrorList([
_('''You selected an apphook with an "app_name".
You must enter a unique instance name.''')
])
else:
# OK, there are zero instances of THIS app that use the
# default instance namespace, so, since the user didn't
# provide one, we'll use the default. NOTE: The following
# line is really setting the "instance namespace" of the
# new app to the app’s "application namespace", which is
# the default instance namespace.
self.cleaned_data['application_namespace'] = application_namespace

if instance_namespace and not apphook:
self.cleaned_data['application_namespace'] = None

if application_config and not apphook:
self.cleaned_data['application_configs'] = None

return cleaned_data

def clean_application_namespace(self):
Expand Down
5 changes: 3 additions & 2 deletions cms/admin/settingsadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ def session_store(self, request):
POST should have a settings parameter
"""
if not request.user.is_staff:
return HttpResponse(json.dumps(""), mimetype="application/json")
return HttpResponse(json.dumps(""),
content_type="application/json")
if request.method == "POST":
request.session['cms_settings'] = request.POST['settings']
request.session.save()
return HttpResponse(
json.dumps(request.session.get('cms_settings', '')),
mimetype="application/json"
content_type="application/json"
)

def save_model(self, request, obj, form, change):
Expand Down
6 changes: 4 additions & 2 deletions cms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ def _verify_apphook(apphook, namespace):
Verifies the apphook given is valid and returns the normalized form (name)
"""
apphook_pool.discover_apps()
if hasattr(apphook, '__module__') and issubclass(apphook, CMSApp):
if isinstance(apphook, CMSApp):
try:
assert apphook in apphook_pool.apps.values()
assert apphook.__class__ in [app.__class__ for app in apphook_pool.apps.values()]
except AssertionError:
print(apphook_pool.apps.values())
raise
apphook_name = apphook.__class__.__name__
elif hasattr(apphook, '__module__') and issubclass(apphook, CMSApp):
return apphook.__name__
elif isinstance(apphook, six.string_types):
try:
Expand Down
26 changes: 26 additions & 0 deletions cms/app_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,31 @@ class CMSApp(object):
urls = None
menus = []
app_name = None
app_config = None
permissions = True
exclude_permissions = []

def __new__(cls):
"""
We want to bind the CMSapp class to a specific AppHookConfig, but only one at a time
Checking for the runtime attribute should be a sane fix
"""
if cls.app_config:
if getattr(cls.app_config, 'cmsapp', None) and cls.app_config.cmsapp != cls:
raise RuntimeError(
'Only one AppHook per AppHookConfiguration must exists.\n'
'AppHook %s already defined for %s AppHookConfig' % (
cls.app_config.cmsapp.__name__, cls.app_config.__name__
)
)
cls.app_config.cmsapp = cls
return super(CMSApp, cls).__new__(cls)

def get_configs(self):
raise NotImplemented('Configurable AppHooks must implement this method')

def get_config(self, namespace):
raise NotImplemented('Configurable AppHooks must implement this method')

def get_config_add_url(self):
raise NotImplemented('Configurable AppHooks must implement this method')
14 changes: 9 additions & 5 deletions cms/apphook_pool.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-

import warnings

from django.core.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -40,10 +41,11 @@ def register(self, app=None, discovering_apps=False):
'but %r does not' % app.__name__)

if not hasattr(app, 'menus') and hasattr(app, 'menu'):
warnings.warn("You define a 'menu' attribute on CMS application %r, "
"but the 'menus' attribute is empty, did you make a typo?" % app.__name__)
warnings.warn("You define a 'menu' attribute on CMS application "
"%r, but the 'menus' attribute is empty, "
"did you make a typo?" % app.__name__)

self.apps[app.__name__] = app
self.apps[app.__name__] = app()
return app

def discover_apps(self):
Expand Down Expand Up @@ -73,7 +75,8 @@ def get_apphooks(self):
if app.urls:
hooks.append((app_name, app.name))

# Unfortunately, we loose the ordering since we now have a list of tuples. Let's reorder by app_name:
# Unfortunately, we lose the ordering since we now have a list of
# tuples. Let's reorder by app_name:
hooks = sorted(hooks, key=lambda hook: hook[1])

return hooks
Expand All @@ -85,7 +88,8 @@ def get_apphook(self, app_name):
try:
return self.apps[app_name]
except KeyError:
# deprecated: return apphooks registered in db with urlconf name instead of apphook class name
# deprecated: return apphooks registered in db with urlconf name
# instead of apphook class name
for app in self.apps.values():
if app_name in app.urls:
return app
Expand Down
3 changes: 2 additions & 1 deletion cms/cms_toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,5 +528,6 @@ def add_history_menu(self):
revert_action = admin_reverse('cms_page_revert_page', args=(self.page.pk, self.current_lang))
revert_question = _('Are you sure you want to revert to live?')
history_menu.add_ajax_item(_('Revert to live'), action=revert_action, question=revert_question,
disabled=not self.page.is_dirty(self.current_lang), on_success=refresh)
disabled=not self.page.is_dirty(self.current_lang),
on_success=refresh, extra_classes=('cms_toolbar-revert',))
history_menu.add_modal_item(_('View history'), url=admin_reverse('cms_page_history', args=(self.page.pk,)))

0 comments on commit 95a5926

Please sign in to comment.