Skip to content

Commit

Permalink
Removes magical slotname generation in favor of explicit callables.
Browse files Browse the repository at this point in the history
  • Loading branch information
czpython committed Jul 22, 2013
1 parent 9e6bc25 commit 5f0bf32
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 75 deletions.
33 changes: 4 additions & 29 deletions cms/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,12 @@
from cms.utils.placeholder import PlaceholderNoAction, validate_placeholder_name
from django.db import models
from django.utils.text import capfirst
from django.core.exceptions import ImproperlyConfigured
from django.db.models.signals import class_prepared


def validate_placeholder_field(cls, field):
dynamic_name_callable = field.dynamic_slot_callable % field.name
try:
slotname = getattr(cls, dynamic_name_callable)
except AttributeError:
msg = "Please provide slotname or define a %s callable in your model %s." % (dynamic_name_callable, cls.__name__)
raise ImproperlyConfigured(msg)
else:
if not callable(slotname):
msg = "Please make sure %s is a callable in your model %s." % (dynamic_name_callable, cls.__name__)
raise ImproperlyConfigured(msg)


def validate_placeholder_fields(sender, **kwargs):
opts = sender._meta
for field in opts.local_fields:
if isinstance(field, PlaceholderField) and not field.slotname:
validate_placeholder_field(sender, field)


class PlaceholderField(models.ForeignKey):
dynamic_slot_callable = 'get_%s_slot'

def __init__(self, slotname=None, default_width=None, actions=PlaceholderNoAction, **kwargs):
def __init__(self, slotname, default_width=None, actions=PlaceholderNoAction, **kwargs):
if kwargs.get('related_name', None) == '+':
raise ValueError("PlaceholderField does not support disabling of related names via '+'.")
self.slotname = slotname
Expand All @@ -46,9 +24,8 @@ def _get_new_placeholder(self, instance):
return Placeholder.objects.create(slot=self._get_placeholder_slot(instance), default_width=self.default_width)

def _get_placeholder_slot(self, model_instance):
if self.slotname is None:
slot_callable = self.dynamic_slot_callable % self.name
slotname = getattr(model_instance, slot_callable)()
if callable(self.slotname):
slotname = self.slotname(model_instance)
validate_placeholder_name(slotname)
else:
slotname = self.slotname
Expand Down Expand Up @@ -86,9 +63,7 @@ def contribute_to_class(self, cls, name):
cls._meta.placeholder_field_names = []
if not hasattr(cls._meta, 'placeholder_fields'):
cls._meta.placeholder_fields = {}
if self.slotname is None:
class_prepared.connect(validate_placeholder_fields, sender=cls, dispatch_uid='%s_placeholder_validation' % cls.__name__)
else:
if not callable(self.slotname):
validate_placeholder_name(self.slotname)
cls._meta.placeholder_field_names.append(name)
cls._meta.placeholder_fields[self] = name
Expand Down
14 changes: 10 additions & 4 deletions cms/stacks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
from cms.models.pluginmodel import CMSPlugin



def stack_slotname(instance):
"""
Returns a string to be used as the slot
for the stack's content placeholder field.
"""
return instance.code


@python_2_unicode_compatible
class Stack(models.Model):
CREATION_BY_TEMPLATE = 'template'
Expand All @@ -22,7 +31,7 @@ class Stack(models.Model):
code = models.CharField(
verbose_name=_(u'stack code'), max_length=255, unique=True, blank=True,
help_text=_(u'To render the stack in templates.'))
content = PlaceholderField(verbose_name=_(u'stack content'), related_name='stacks_contents')
content = PlaceholderField(stack_slotname, verbose_name=_(u'stack content'), related_name='stacks_contents')

creation_method = models.CharField(
verbose_name=('creation_method'), choices=CREATION_METHODS, default=CREATION_BY_CODE,
Expand All @@ -41,9 +50,6 @@ def clean(self):
if not self.code:
self.code = u'stack-%s' % uuid.uuid4()

def get_content_slot(self):
return self.code


@python_2_unicode_compatible
class StackLink(CMSPlugin):
Expand Down
18 changes: 10 additions & 8 deletions cms/test_utils/project/placeholderapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
from hvad.models import TranslatableModel, TranslatedFields


def dynamic_placeholder_1(instance):
return instance.char_1


def dynamic_placeholder_2(instance):
return instance.char_2


class Example1(models.Model):
char_1 = models.CharField(u'char_1', max_length=255)
char_2 = models.CharField(u'char_2', max_length=255)
Expand All @@ -24,14 +32,8 @@ class TwoPlaceholderExample(models.Model):
class DynamicPlaceholderSlotExample(models.Model):
char_1 = models.CharField(u'char_1', max_length=255)
char_2 = models.CharField(u'char_2', max_length=255)
placeholder_1 = PlaceholderField(related_name='dynamic_pl_1')
placeholder_2 = PlaceholderField(related_name='dynamic_pl_2')

def get_placeholder_1_slot(self):
return self.char_1

def get_placeholder_2_slot(self):
return self.char_2
placeholder_1 = PlaceholderField(dynamic_placeholder_1, related_name='dynamic_pl_1')
placeholder_2 = PlaceholderField(dynamic_placeholder_2, related_name='dynamic_pl_2')


@python_2_unicode_compatible
Expand Down
7 changes: 0 additions & 7 deletions cms/tests/placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,6 @@ def test_placeholder_field_dynamic_slot_update(self):
self.assertEqual(old_placeholder_1_plugin_count, current_placeholder_1_plugin_count)
self.assertEqual(old_placeholder_2_plugin_count, current_placeholder_2_plugin_count)

def test_placeholder_field_dynamic_slot_improperly_configured(self):
def create_placeholder_model():
attrs = {'__module__': 'cms.test_utils.project.placeholderapp', 'pl_1': PlaceholderField()}
return type('PlaceholderFieldExceptionTest', (Model,), attrs)

with self.assertRaises(ImproperlyConfigured):
create_placeholder_model()

def test_plugins_language_fallback(self):
""" Tests language_fallback placeholder configuration """
Expand Down
37 changes: 10 additions & 27 deletions docs/extending_cms/placeholders.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,44 +31,27 @@ use::
# your methods


The :class:`~cms.models.fields.PlaceholderField` takes an optional parameter (`slotname`) of type string as its first
argument which will be used to configure which plugins can be used in this
placeholder.
The configuration is the same as for placeholders in the CMS.

.. warning::

For security reasons the related_name for a
:class:`~cms.models.fields.PlaceholderField` may not be surpressed using
``'+'`` to allow the cms to check permissions properly. Attempting to do
so will raise a :exc:`ValueError`.


Field Options
=================

If you don't provide a `slotname` to the :class:`~cms.models.fields.PlaceholderField`,
then you need to define a ``get_myplaceholderfield_name_slot()`` method in your model which the field will use to retrieve the
slotname dynamically.
So the above example could be defined as::
The :class:`~cms.models.fields.PlaceholderField` has one required parameter (`slotname`) which can be a of type string, allowing you to configure which plugins can be used in this
placeholder (configuration is the same as for placeholders in the CMS) or you can also provide a callable like so::

from django.db import models
from cms.models.fields import PlaceholderField

def my_placeholder_slotname(instance):
return 'placeholder_name'

class MyModel(models.Model):
# your fields
my_placeholder = PlaceholderField()

my_placeholder = PlaceholderField(my_placeholder_slotname)
# your methods
def get_my_placeholder_slot(self):
return 'placeholder_name'


.. warning::

If you don't provide a `slotname` to :class:`~cms.models.fields.PlaceholderField` and
don't define a ``get_myplaceholderfield_name_slot()`` method in your model,
then the field will throw an :exc:`ImproperlyConfigured exception`.
For security reasons the related_name for a
:class:`~cms.models.fields.PlaceholderField` may not be surpressed using
``'+'`` to allow the cms to check permissions properly. Attempting to do
so will raise a :exc:`ValueError`.


Admin Integration
Expand Down

0 comments on commit 5f0bf32

Please sign in to comment.