如何访问Django-CMS插件的父模型

时间:2016-07-21 17:51:30

标签: django django-models django-cms

我创建了2个django-cms插件,一个父“容器”,可以包含多个子“内容”插件。

当我保存子插件时,我想访问父插件的模型。

from cms.plugin_pool import plugin_pool
from cms.plugin_base import CMSPluginBase

from .models import Container, Content

class ContainerPlugin(CMSPluginBase):
    model = Container
    name = "Foo Container"
    render_template = "my_package/container.html"
    allow_children = True
    child_classes = ["ContentPlugin"]

class ContentPlugin(CMSPluginBase):
    model = content
    name = "Bar Content"
    render_template = "my_package/content.html"
    require_parent = True
    parent_classes = ["ContainerPlugin"]
    allow_children = True

    def save_model(self, request, obj, form, change):
        response = super(ContentPlugin, self).save_model(
            request, obj, form, change
        )

        # here I want to access the parent's (container) model, but how?

        return response

plugin_pool.register_plugin(ContainerPlugin)
plugin_pool.register_plugin(ContentPlugin)

obj是当前的插件实例,因此我可以获得此模型的所有属性,但我无法弄清楚如何访问父插件模型。有obj.parent,但据我所知,它不是插件实例。还尝试使用self.cms_plugin_instanceobj.parent.get_plugin_instance(),但没有成功。

有什么建议吗?

3 个答案:

答案 0 :(得分:5)

给定一个插件实例,instance.get_plugin_instance()方法返回一个包含:

的元组
  1. instance - 插件实例
  2. 插件 - 关联的插件类实例 get_plugin_instance
  3. 这样的东西来获取父对象:

    instance, plugin_class = object.parent.get_plugin_instance()
    

答案 1 :(得分:1)

选项1

查看CMSPluginBase的源代码,您可以使用get_child_classes的实现。不幸的是,该方法实际上只返回类名,因此您无法直接使用它。但我认为它确实迭代子实例以获取类名:

def get_child_classes(self, slot, page):
    from cms.utils.placeholder import get_placeholder_conf

    template = page and page.get_template() or None

    # config overrides..
    ph_conf = get_placeholder_conf('child_classes', slot, template, default={})
    child_classes = ph_conf.get(self.__class__.__name__, self.child_classes)
    if child_classes:
        return child_classes
    from cms.plugin_pool import plugin_pool
    installed_plugins = plugin_pool.get_all_plugins(slot, page)
    return [cls.__name__ for cls in installed_plugins]

你感兴趣的是这两行:

    from cms.plugin_pool import plugin_pool
    installed_plugins = plugin_pool.get_all_plugins(slot, page)

选项2

另一种方式(我在我的代码中使用的方法)是使用信号,但这也需要找到正确的对象。代码不是非常易读(参见我挥之不去的内联评论),但它确实有效。它是在不久之前写的,但我仍然使用它与django-cms 3.2.3。

占位符名称实际上是您为占位符配置的名称。将它移到设置或某个地方当然更可取。我不知道为什么我没有那样做。

我对您的解决方案感兴趣!

# signals.py
import itertools
import logging

from cms.models import CMSPlugin
from cms.plugin_pool import plugin_pool
from django.db import ProgrammingError
from django.db.models.signals import post_save

logger = logging.getLogger(__name__)
_registered_plugins = [CMSPlugin.__name__]


def on_placeholder_saved(sender, instance, created, raw, using, update_fields, **kwargs):
    """
    :param sender: Placeholder
    :param instance: instance of Placeholder
    """
    logger.debug("Placeholder SAVED: %s by sender %s", instance, sender)
    # TODO this is totally ugly - is there no generic way to find out the related names?
    placeholder_names = [
        'topicintro_abstracts',
        'topicintro_contents',
        'topicintro_links',
        'glossaryentry_explanations',
    ]
    fetch_phs = lambda ph_name: _fetch_qs_as_list(instance, ph_name)
    container = list(itertools.chain.from_iterable(map(fetch_phs, placeholder_names)))
    logger.debug("Modified Placeholder Containers %s (%s)", container, placeholder_names)
    if container:
        if len(container) > 1:
            raise ProgrammingError("Several Containers use the same placeholder.")
        else:
            # TODO change modified_by (if possible?)
            container[0].save()

def _fetch_qs_as_list(instance, field):
    """
    :param instance: a model
    :param field: optional field (might not exist on model)
    :return: the field values as list (not as RelatedManager)
    """
    qs = getattr(instance, field)
    fields = qs.all() if qs else []
    return fields

def on_cmsplugin_saved(sender, instance, created, raw, using, update_fields, **kwargs):
    """
    :param sender: CMSPlugin or subclass
    :param instance: instance of CMSPlugin
    """
    plugin_class = instance.get_plugin_class()
    logger.debug("CMSPlugin SAVED: %s; plugin class: %s", instance, plugin_class)
    if not plugin_class.name in _registered_plugins:
        post_save.connect(on_cmsplugin_saved, sender=plugin_class)
        _registered_plugins.append(plugin_class.name)
        logger.info("Registered post_save listener with %s", plugin_class.name)
    on_placeholder_saved(sender, instance.placeholder, created, raw, using, update_fields)

def connect_existing_plugins():
    plugin_types = CMSPlugin.objects.order_by('plugin_type').values_list('plugin_type').distinct()
    for plugin_type in plugin_types:
        plugin_type = plugin_type[0]
        if not plugin_type in _registered_plugins:
            plugin_class = plugin_pool.get_plugin(plugin_type)
            post_save.connect(on_cmsplugin_saved, sender=plugin_class)
            post_save.connect(on_cmsplugin_saved, sender=plugin_class.model)
            _registered_plugins.append(plugin_type)
            _registered_plugins.append(plugin_class.model.__name__)
    logger.debug("INIT registered plugins: %s", _registered_plugins)

post_save.connect(on_cmsplugin_saved, sender=CMSPlugin)

你必须在某处设置这些信号。我在我的urls.py中这样做,虽然app配置可能是适合它的位置? (我试图避免app配置。)

# This code has to run at server startup (and not during migrate if avoidable)
try:
    signals.connect_existing_plugins()
except db.utils.ProgrammingError:
    logger.warn('Failed to setup signals (if your DB is not setup (not tables), you can savely ignore this error.')

答案 2 :(得分:1)

由于子插件总是继承上下文。在父模板中,您可以执行以下操作:

{% with something=instance.some_parent_field %}
  {% for plugin in instance.child_plugin_instances %}
          {% render_plugin plugin %}
  {% endfor %}
{% endwith %}

在您的子模板中使用