贊助方

Blog

Django CMS Plugin Tutorial

Translated from: django-cms 代码研究(六)plugin的深入分析

CMS is easy to edit content with plugins, but do you know how those plugins work?
Today, we are going to inrtoduce the concept of plugin by the image plugin example.

All the plugins are managed by cms.admin.pageaadmin.edit_plugin. The way it edit is quite simple, as it showed below

def edit_plugin(self, *args, **kwargs):
    with create_revision():
        return super(PageAdmin, self).edit_plugin(*args, **kwargs)

The inheritence relation is like below:

class PageAdmin(PlaceholderAdminMixin, ModelAdmin):

Now, you might realize that edit_plugin is the function of mixin, like below:

@xframe_options_sameorigin
def edit_plugin(self, request, plugin_id):
    plugin_id = int(plugin_id)
    cms_plugin = get_object_or_404(CMSPlugin.objects.select_related('placeholder'), pk=plugin_id)
 
    instance, plugin_admin = cms_plugin.get_plugin_instance(self.admin_site)
    if not self.has_change_plugin_permission(request, cms_plugin):
        return HttpResponseForbidden(force_unicode(_("You do not have permission to edit this plugin")))
    plugin_admin.cms_plugin_instance = cms_plugin
    try:
        plugin_admin.placeholder = cms_plugin.placeholder
    except Placeholder.DoesNotExist:
        pass
    if request.method == "POST":
        # set the continue flag, otherwise will plugin_admin make redirect to list
        # view, which actually doesn't exists
        request.POST['_continue'] = True
    if request.POST.get("_cancel", False):
        # cancel button was clicked
        context = {
            'CMS_MEDIA_URL': get_cms_setting('MEDIA_URL'),
            'plugin': cms_plugin,
            'is_popup': True,
            "type": cms_plugin.get_plugin_name(),
            'plugin_id': plugin_id,
            'icon': force_escape(escapejs(cms_plugin.get_instance_icon_src())),
            'alt': force_escape(escapejs(cms_plugin.get_instance_icon_alt())),
            'cancel': True,
        }
        instance = cms_plugin.get_plugin_instance()[0]
        if instance:
            context['name'] = force_unicode(instance)
        else:
            # cancelled before any content was added to plugin
            cms_plugin.delete()
            context.update({
                "deleted": True,
                'name': force_unicode(cms_plugin),
            })
        return render_to_response('admin/cms/page/plugin/confirm_form.html', context, RequestContext(request))
 
    if not instance:
        # instance doesn't exist, call add view
        response = plugin_admin.add_view(request)
    else:
        # already saved before, call change view
        # we actually have the instance here, but since i won't override
        # change_view method, is better if it will be loaded again, so
        # just pass id to plugin_admin
        response = plugin_admin.change_view(request, str(plugin_id))
    if request.method == "POST" and plugin_admin.object_successfully_changed:
        self.post_edit_plugin(request, plugin_admin.saved_object)
        saved_object = plugin_admin.saved_object
        context = {
            'CMS_MEDIA_URL': get_cms_setting('MEDIA_URL'),
            'plugin': saved_object,
            'is_popup': True,
            'name': force_unicode(saved_object),
            "type": saved_object.get_plugin_name(),
            'plugin_id': plugin_id,
            'icon': force_escape(saved_object.get_instance_icon_src()),
            'alt': force_escape(saved_object.get_instance_icon_alt()),
        }
        return render_to_response('admin/cms/page/plugin/confirm_form.html', context, RequestContext(request))
    return response

Plugin is kind of object, and the plugin_id already has its value in the steps, so we can suggest that there is a many-to-many table, in which we match the placeholder name and the plugin itself.

@python_2_unicode_compatible
class CMSPlugin(with_metaclass(PluginModelBase, MPTTModel)):
   ...
   placeholder = models.ForeignKey(Placeholder, editable=False, null=True)

In this case, placeholder seems to be the identification of plugin.

@python_2_unicode_compatible
class Placeholder(models.Model):
    slot = models.CharField(_("slot"), max_length=50, db_index=True, editable=False)
    default_width = models.PositiveSmallIntegerField(_("width"), null=True, editable=False)
    cache_placeholder = True
 
...
 
    def get_plugins_list(self, language=None):
        return list(self.get_plugins(language))
 
    def get_plugins(self, language=None):
        if language:
            return self.cmsplugin_set.filter(language=language).order_by('tree_id', 'lft')
        else:
            return self.cmsplugin_set.all().order_by('tree_id', 'lft')

Then, let's see how it works to create a plugin
When creating a new plugin, the instance is None, and the plugin_admin is the instance of PicturePlugin. Thus, the plugin is retrieved from the plugin pool.

def get_plugin_class(self):
    from cms.plugin_pool import plugin_pool
 
    return plugin_pool.get_plugin(self.plugin_type)
 
def get_plugin_class_instance(self, admin=None):
    plugin_class = self.get_plugin_class()
    # needed so we have the same signature as the original ModelAdmin
    return plugin_class(plugin_class.model, admin)
 
def get_plugin_instance(self, admin=None):
    plugin = self.get_plugin_class_instance(admin)
    if hasattr(self, "_inst"):
        return self._inst, plugin
    if plugin.model != self.__class__: # and self.__class__ == CMSPlugin:
        # (if self is actually a subclass, getattr below would break)
        try:
            instance = plugin.model.objects.get(cmsplugin_ptr=self)
            instance._render_meta = self._render_meta
        except (AttributeError, ObjectDoesNotExist):
            instance = None
    else:
        instance = self
    self._inst = instance
    return self._inst, plugin

Eventually, once the instance is empty, it will be directed into this function

if not instance:
    # instance doesn't exist, call add view
    response = plugin_admin.add_view(request)
Conclusion

1. The inheritence:


2. Add a plugin in the page, the operations start:
(a) PageAdmin.edit_plugin
(b) PlaceholderAdminMixin.edit_plugin:
Get the cms_plugin by the CMSPlugin
Get the instance and the plugin_admin, got from the plugin pools by the type.
If the instance is empty, return the corresponding object via add_view; otherwise, edit the object by change_view.
If the request is Post, and the setting value of object_successfully_changed is True, then give the response.

 

Click here to download a PDF copy of this article

Save it to read later, or share with a colleague


Comment

There is no comment, you can make the first one!

Comment