Skip to content

Instantly share code, notes, and snippets.

@jezdez
Created June 23, 2010 15:16
Show Gist options
  • Select an option

  • Save jezdez/450067 to your computer and use it in GitHub Desktop.

Select an option

Save jezdez/450067 to your computer and use it in GitHub Desktop.
diff --git a/cms/models/pluginmodel.py b/cms/models/pluginmodel.py
index fc318a9..108a165 100644
--- a/cms/models/pluginmodel.py
+++ b/cms/models/pluginmodel.py
@@ -5,7 +5,8 @@ from cms.plugin_rendering import PluginContext, PluginRenderer
from cms.exceptions import DontUsePageAttributeWarning
from publisher import MpttPublisher
from django.db import models
-from django.db.models.base import ModelBase
+from django.db.models.base import ModelBase, model_unpickle, simple_class_factory
+from django.db.models.query_utils import DeferredAttribute
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.conf import settings
@@ -18,6 +19,9 @@ class PluginModelBase(ModelBase):
Metaclass for all plugins.
"""
def __new__(cls, name, bases, attrs):
+ render_meta = attrs.pop('RenderMeta', None)
+ if render_meta is not None:
+ attrs['_render_meta'] = render_meta()
new_class = super(PluginModelBase, cls).__new__(cls, name, bases, attrs)
found = False
bbases = bases
@@ -27,32 +31,32 @@ class PluginModelBase(ModelBase):
found = True
bbases = False
else:
- bbases = bcls.__bases__
+ bbases = bcls.__bases__
if found:
if new_class._meta.db_table.startswith("%s_" % new_class._meta.app_label):
table = "cmsplugin_" + new_class._meta.db_table.split("%s_" % new_class._meta.app_label, 1)[1]
new_class._meta.db_table = table
- return new_class
-
-
+ return new_class
+
+
class CMSPlugin(MpttPublisher):
__metaclass__ = PluginModelBase
-
+
placeholder = models.ForeignKey(Placeholder, editable=False, null=True)
parent = models.ForeignKey('self', blank=True, null=True, editable=False)
position = models.PositiveSmallIntegerField(_("position"), blank=True, null=True, editable=False)
language = models.CharField(_("language"), max_length=5, blank=False, db_index=True, editable=False)
plugin_type = models.CharField(_("plugin_name"), max_length=50, db_index=True, editable=False)
creation_date = models.DateTimeField(_("creation date"), editable=False, default=datetime.now)
-
+
level = models.PositiveIntegerField(db_index=True, editable=False)
lft = models.PositiveIntegerField(db_index=True, editable=False)
rght = models.PositiveIntegerField(db_index=True, editable=False)
tree_id = models.PositiveIntegerField(db_index=True, editable=False)
-
+
class Meta:
app_label = 'cms'
-
+
class PublisherMeta:
exclude_fields = []
exclude_fields_append = ['plugin_ptr']
@@ -62,24 +66,59 @@ class CMSPlugin(MpttPublisher):
total = 1
text_enabled = False
- def __init__(self, *args, **kwargs):
- self._render_meta = self.RenderMeta()
- super(CMSPlugin, self).__init__(*args, **kwargs)
+ def __reduce__(self):
+ """
+ Provide pickling support. Normally, this just dispatches to Python's
+ standard handling. However, for models with deferred field loading, we
+ need to do things manually, as they're dynamically created classes and
+ only module-level classes can be pickled by the default path.
+ """
+ data = self.__dict__
+ model = self.__class__
+ # The obvious thing to do here is to invoke super().__reduce__()
+ # for the non-deferred case. Don't do that.
+ # On Python 2.4, there is something wierd with __reduce__,
+ # and as a result, the super call will cause an infinite recursion.
+ # See #10547 and #12121.
+ defers = []
+ pk_val = None
+ if self._deferred:
+ factory = deferred_class_factory
+ for field in self._meta.fields:
+ if isinstance(self.__class__.__dict__.get(field.attname),
+ DeferredAttribute):
+ defers.append(field.attname)
+ if pk_val is None:
+ # The pk_val and model values are the same for all
+ # DeferredAttribute classes, so we only need to do this
+ # once.
+ obj = self.__class__.__dict__[field.attname]
+ model = obj.model_ref()
+ else:
+ factory = simple_class_factory
+ return (model_unpickle, (model, defers, factory), data)
def __unicode__(self):
return unicode(self.id)
-
+
+ class Meta:
+ app_label = 'cms'
+
+ class PublisherMeta:
+ exclude_fields = []
+ exclude_fields_append = ['plugin_ptr']
+
def get_plugin_name(self):
from cms.plugin_pool import plugin_pool
return plugin_pool.get_plugin(self.plugin_type).name
-
+
def get_short_description(self):
- return self.get_plugin_instance()[0].__unicode__()
-
+ return self.get_plugin_instance()[0].__unicode__()
+
def get_plugin_class(self):
from cms.plugin_pool import plugin_pool
return plugin_pool.get_plugin(self.plugin_type)
-
+
def get_plugin_instance(self, admin=None):
from cms.plugin_pool import plugin_pool
plugin_class = plugin_pool.get_plugin(self.plugin_type)
@@ -100,7 +139,7 @@ class CMSPlugin(MpttPublisher):
else:
instance = self
return instance, plugin
-
+
def render_plugin(self, context=None, placeholder=None, admin=False, processors=None):
instance, plugin = self.get_plugin_instance()
if instance and not (admin and not plugin.admin_preview):
@@ -120,7 +159,7 @@ class CMSPlugin(MpttPublisher):
renderer = PluginRenderer(context, instance, placeholder, template, processors)
return renderer.content
return ""
-
+
def get_media_path(self, filename):
pages = self.placeholder.page_set.all()
if pages.count():
@@ -128,7 +167,7 @@ class CMSPlugin(MpttPublisher):
else: # django 1.0.2 compatibility
today = date.today()
return join(settings.CMS_PAGE_MEDIA_PATH, str(today.year), str(today.month), str(today.day), filename)
-
+
@property
def page(self):
warnings.warn(
@@ -136,7 +175,7 @@ class CMSPlugin(MpttPublisher):
"guaranteed to have a page associated with them!",
DontUsePageAttributeWarning)
return get_page_from_placeholder_if_exists(self.placeholder)
-
+
def get_instance_icon_src(self):
"""
Get src URL for instance's icon
@@ -156,22 +195,22 @@ class CMSPlugin(MpttPublisher):
return unicode(plugin.icon_alt(instance))
else:
return u''
-
+
def save(self, no_signals=False, *args, **kwargs):
if no_signals:# ugly hack because of mptt
super(CMSPlugin, self).save_base(cls=self.__class__)
else:
super(CMSPlugin, self).save()
-
-
+
+
def set_base_attr(self, plugin):
for attr in ['parent_id', 'placeholder', 'language', 'plugin_type', 'creation_date', 'level', 'lft', 'rght', 'position', 'tree_id']:
setattr(plugin, attr, getattr(self, attr))
-
+
def _publisher_get_public_copy(self):
"""Overrides publisher public copy acessor, because of the special
kind of relation between Plugins.
- """
+ """
publisher_public = self.publisher_public
if not publisher_public:
return
@@ -188,7 +227,7 @@ class CMSPlugin(MpttPublisher):
setattr(public_copy, field.name, value)
public_copy.publisher_is_draft=False
return public_copy
-
+
def copy_plugin(self, target_placeholder, target_language, plugin_tree):
"""
Copy this plugin and return the new plugin.
@@ -233,12 +272,12 @@ class CMSPlugin(MpttPublisher):
plugin_instance.save()
self.copy_relations(new_plugin, plugin_instance)
return new_plugin
-
+
def copy_relations(self, new_plugin, plugin_instance):
"""
Handle copying of any relations attached to this plugin
"""
-
+
def has_change_permission(self, request):
page = get_page_from_placeholder_if_exists(self.placeholder)
if page:
@@ -248,16 +287,16 @@ class CMSPlugin(MpttPublisher):
elif self.parent:
return self.parent.has_change_permission(request)
return False
-
+
def is_first_in_placeholder(self):
return self.position == 0
-
+
def is_last_in_placeholder(self):
"""
WARNING: this is a rather expensive call compared to is_first_in_placeholder!
"""
return self.placeholder.cmsplugin_set.all().order_by('-position')[0].pk == self.pk
-
+
def get_position_in_placeholder(self):
"""
1 based position!
@@ -265,3 +304,38 @@ class CMSPlugin(MpttPublisher):
return self.position + 1
reversion_register(CMSPlugin)
+
+def deferred_class_factory(model, attrs):
+ """
+ Returns a class object that is a copy of "model" with the specified "attrs"
+ being replaced with DeferredAttribute objects. The "pk_value" ties the
+ deferred attributes to a particular instance of the model.
+ """
+ class Meta:
+ pass
+ setattr(Meta, "proxy", True)
+ setattr(Meta, "app_label", model._meta.app_label)
+
+ class RenderMeta:
+ pass
+ setattr(RenderMeta, "index", model._render_meta.index)
+ setattr(RenderMeta, "total", model._render_meta.total)
+ setattr(RenderMeta, "text_enabled", model._render_meta.text_enabled)
+
+ # The app_cache wants a unique name for each model, otherwise the new class
+ # won't be created (we get an old one back). Therefore, we generate the
+ # name using the passed in attrs. It's OK to reuse an old case if the attrs
+ # are identical.
+ name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs))))
+
+ overrides = dict([(attr, DeferredAttribute(attr, model))
+ for attr in attrs])
+ overrides["Meta"] = RenderMeta
+ overrides["RenderMeta"] = RenderMeta
+ overrides["__module__"] = model.__module__
+ overrides["_deferred"] = True
+ return type(name, (model,), overrides)
+
+# The above function is also used to unpickle model instances with deferred
+# fields.
+deferred_class_factory.__safe_for_unpickling__ = True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment