CMS插件是一個可重複使用內容發佈者,它可以嵌入到django CMS頁面上,或者利用django CMS placeholder嵌入到任意內容。它們不須要進一步的干預就可以自動的發佈信息。這就意味着,當你發佈網頁內容時,不論是什麼內容,總能保持最新。 javascript
它就像一個魔術,可是更快。 css
若是你的需求在內嵌的或者第三方的插件裏都實現了,那麼你很幸運,不然的話,你須要去實現本身的CMS插件。可是不用太擔憂,寫一個CMS插件很是簡單。 html
若是要把django應用集成到django CMS頁面上,插件是最方便的方法。例如:若是你在部署一個唱片公司的django CMS站點,你可能想在主頁上放一個"最新發布"的版塊,這樣可能須要常常編輯該頁面去更新信息。然而,一個明智的唱片公司一樣會在django裏管理它的目錄,這樣的話django就已經知道這周它要發佈什麼。 java
這是一個很好的機會去利用這些信息來簡化你的工做,你所須要作的事就是建立一個CMS插件把它插入到你的主頁上,讓它去完成發佈信息的工做。 python
插件是可重複使用的,這樣你可能只須要稍做修改就能夠用於相似目的。 git
一個django CMS插件基本上由三部分組成 github
這些與MVT (Model-View-Template)模式是一致的 shell
因此,要編寫你本身的plugin,你須要從下面開始 數據庫
cms.plugin_base.CMSPluginBase 其實是 django.contrib.admin.ModelAdmin 子類 django
由於 CMSPluginBase 從 ModelAdmin 子類化,因此 ModelAdmin 的幾個重要的選項對CMS plugin開發者也適用。下面這些選項常常被用到::
然而,並非ModelAdmin的全部的操做在CMS plugin都能用,特別是一些ModelAdmin的changelist專用的那些選項是無效的。下面這些選項在CMS中須要被忽略:
Model插件從cms.models.pluginmodel.CMSPlugin繼承而來,實際上它是可選的。
若是它永遠只作同一件事,你的插件能夠不去配置,例如:若是你的插件永遠只是發佈過去幾天裏銷量最好的唱票。很明顯,這個不夠靈活,他並不能發佈過去幾個月銷量最好的。
一般,若是你發現你須要去配置你的插件,這就須要定義一個model。
你能夠用python manage.py startapp去設置基本的應用佈局(記得把你的插件加入INSTALLED_APPS),或者,你也能夠只須要在當前的django應用里加入一個叫cms_plugins.py的文件。
能夠把你的插件內容放到這個文件裏,例如,你能夠加入如下代碼:
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from cms.models.pluginmodel import CMSPlugin
from django.utils.translation import ugettext_lazy as _
@plugin_pool.register_plugin
class HelloPlugin(CMSPluginBase):
model = CMSPlugin
render_template = "hello_plugin.html"
cache = False
這樣,基本上就完成了。剩下的只須要去添加模板。在模板根目錄添加hello_plugin.html文件
<h1>Hello {% if request.user.is_authenticated %}{{ request.user.first_name }} {{ request.user.last_name}}{% else %}Guest{% endif %}</h1>
該插件會在頁面上顯示歡迎信息,若是是登陸用戶,顯示名字,不然顯示Guest。
在cms_plugins.py文件,你會子類化cms.plugin_base.CMSPluginBase,這些類會定義了不一樣的插件。
有兩個屬性是這些類必須的:
model:model用於存儲你的插件信息。若是你不打算存儲一些特別信息,直接用cms.models.pluginmodel.CMSPlugin就能夠了。在一個正常的admin class,你不須要提供這個信息。
name:顯示在admin上的你的插件名字。經過,實際工做中咱們會經過django.utils.translation.ugettext_lazy()將改字符串設成可翻譯的。
若是render_plugin設爲True,下面內容必須定義
render_template:插件的渲染模板
get_render_template:返回渲染插件模板的路徑
除了這些屬性,你也能夠重寫render()方法,該方法決定渲染插件的模板上下文變量。默認狀況下,這個方法只會把instance和placeholder對象添加到你的context,插件能夠經過重寫這個方法添加更多的上下文內容。
你也能夠重寫其餘的CMSPluginBase子類的方法,詳細信息參考CMSPluginBase 。
由於插件的modules經過django的importlib加載,你可能會碰到路徑環境致使的問題。若是你的cms_plugins不能加載或者訪問,嘗試下面的操做:
$ python manage.py shell
>>> from importlib import import_module
>>> m = import_module("myapp.cms_plugins")
>>> m.some_test_function()
許多狀況下,你須要給你的插件實例存儲配置。例如:若是你有一個插件顯示最新的發佈博客,你可能也但願可以選擇顯示條目的數量。
去實現這些功能,你須要在已安裝的models.py文件裏,建立一個model子類化cms.models.pluginmodel.CMSPlugin
接下來,咱們來改進一下上面的HelloPlugin,給未受權用戶添加可配置的名字
在models.py文件,添加以下內容
from cms.models.pluginmodel import CMSPlugin
from django.db import models
class Hello(CMSPlugin):
guest_name = models.CharField(max_length=50, default='Guest')
這個跟正常的model定義沒有太大差異,惟一不一樣是它是從cms.models.pluginmodel.CMSPlugin 繼承而不是django.db.models.Model.
如今,咱們須要修改咱們的插件定義來使用這個model,新的cms_plugins.py以下
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from django.utils.translation import ugettext_lazy as _
from .models import Hello
@plugin_pool.register_plugin
class HelloPlugin(CMSPluginBase):
model = Hello
name = _("Hello Plugin")
render_template = "hello_plugin.html"
cache = False
def render(self, context, instance, placeholder):
context = super(HelloPlugin, self).render(context, instance, placeholder)
return context
咱們修改model屬性,而且將model實例傳遞給context.
最後,更新模板,在模板裏使用新的配置信息。
<h1>Hello {% if request.user.is_authenticated %}
{{ request.user.first_name }} {{ request.user.last_name}}
{% else %}
{{ instance.guest_name }}
{% endif %}</h1>
這兒,咱們使用{{ instance.guest_name }}來取代固定的Guest字符串
每次你的頁面發佈時,若是自定義插件在頁面裏,那麼它就會被拷貝。因此,你的自定義插件有ForeignKey (from或者to)或者m2m,你須要拷貝這些關聯對象。它不會自動幫你完成。
每一個插件model會從基類繼承空方法cms.models.pluginmodel.CMSPlugin.copy_relations(),在你的插件被拷貝時,它會被調用。因此,你能夠在這兒適配你的目的。
典型狀況下,你須要用它去拷貝關聯對象。要實現該功能,你須要在你的插件model建立copy_relations()方法,老的instance會做爲一個參數傳入。
也有可能你決定不須要拷貝這些關聯對象,你想讓它們獨立存在。這些取決於你想怎樣讓這些插件工做。
若是你想拷貝關聯對象,你須要用兩個近似的方法去實現,具體須要看你的插件和對象之間的關係 (from仍是to)
你的插件可能有一些條目的外鍵指向它,這些是典型的admin內聯場景。因此,你可能須要兩個model,一個plugin,一個給那些條目。
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
class AssociatedItem(models.Model):
plugin = models.ForeignKey(
ArticlePluginModel,
related_name="associated_item"
)
這樣,你須要 copy_relations()方法去輪訓關聯條目而且拷貝它們,並將外鍵賦作新的插件
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
def copy_relations(self, oldinstance):
# Before copying related objects from the old instance, the ones
# on the current one need to be deleted. Otherwise, duplicates may
# appear on the public version of the page
self.associated_item.all().delete()
for associated_item in oldinstance.associated_item.all():
# instance.pk = None; instance.pk.save() is the slightly odd but
# standard Django way of copying a saved model instance
associated_item.pk = None
associated_item.plugin = self
associated_item.save()
假定你得插件有關聯對象
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
sections = models.ManyToManyField(Section)
當插件被拷貝是,咱們須要section保持不變,因此改爲以下:
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
sections = models.ManyToManyField(Section)
def copy_relations(self, oldinstance):
self.sections = oldinstance.sections.all()
若是你的插件有這兩種關聯域,你就須要用到以上的兩種技術。
若是插件直接有關聯,關係拷貝就會變得很是困難。細節查看GitHub issue copy_relations() does not work for relations between cmsplugins #4143
若是你想外鍵關係做爲inline admin,你須要建立admin.StackedInlineclass,而且把插件放到inlines。而後,你能夠用這個inline admin form做爲你的外鍵引用。
class ItemInlineAdmin(admin.StackedInline):
model = AssociatedItem
class ArticlePlugin(CMSPluginBase):
model = ArticlePluginModel
name = _("Article Plugin")
render_template = "article/index.html"
inlines = (ItemInlineAdmin,)
def render(self, context, instance, placeholder):
context = super(ArticlePlugin, self).render(context, instance, placeholder)
items = instance.associated_item.all()
context.update({
'items': items,
})
return context
由於 cms.plugin_base.CMSPluginBase 從django.contrib.admin.ModelAdmin擴展而來, 你能夠爲你的插件定製化form,方法跟定製化admin form同樣.
插件編輯機制使用的模板是cms/templates/admin/cms/page/plugin/change_form.html,你可能須要修改它。
若是你想定製化,最好的方法是:
從cms/templates/admin/cms/page/plugin/change_form.html擴展可以保證你的插件和其餘的外觀和功能統一。
若是你的插件依賴於特定的media文件, JavaScript或者stylesheets, 你能夠經過django-sekizai把它們加入到你的插件模板。你的CMS模板是強制要求加入css 和 js sekizai 域名空間。更多信息請參考 django-sekizai documentation.
要想充分利用django-sekizai, 最好使用一致的風格,下面是一些遵照的慣例:
一個好的例子:
{% load sekizai_tags %}
{% addtoblock "js" %}<script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myjsfile.js"></script>{% endaddtoblock %}
{% addtoblock "js" %}<script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myotherfile.js"></script>{% endaddtoblock %}
{% addtoblock "css" %}<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}myplugin/css/astylesheet.css">{% endaddtoblock %}
{% addtoblock "js" %}
<script type="text/javascript">
$(document).ready(function(){
doSomething();
});
</script>
{% endaddtoblock %}
一個很差的例子:
{% load sekizai_tags %}
{% addtoblock "js" %}<script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myjsfile.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myotherfile.js"></script>{% endaddtoblock %}
{% addtoblock "css" %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}myplugin/css/astylesheet.css"></script>
{% endaddtoblock %}
{% addtoblock "js" %}<script type="text/javascript">
$(document).ready(function(){
doSomething();
});
</script>{% endaddtoblock %}
插件可以訪問django模板,你能夠經過with tag覆蓋變量
例子:
{% with 320 as width %}{% placeholder "content" %}{% endwith %}
在渲染以前,插件context processor能夠被調用去修改插件的context。能夠經過CMS_PLUGIN_CONTEXT_PROCESSORS使能改功能。
一個插件context processor包含三個參數
它返回一個字典,包含了添加到context中的全部變量。
例子:
def add_verbose_name(instance, placeholder, context):
'''
This plugin context processor adds the plugin model's verbose_name to context.
'''
return {'verbose_name': instance._meta.verbose_name}
在渲染以前,插件processor能夠被調用去修改插件的輸出。能夠經過CMS_PLUGIN_PROCESSORS使能改功能。
一個插件processor包含三個參數
例子
加入你要在主placeholder裏面將全部插件放到一個用一個彩色盒子裏,編輯每一個插件的目標會很是複雜
在你的 settings.py:
CMS_PLUGIN_PROCESSORS = (
'yourapp.cms_plugin_processors.wrap_in_colored_box',
)
在你的 yourapp.cms_plugin_processors.py:
def wrap_in_colored_box(instance, placeholder, rendered_content, original_context):
'''
This plugin processor wraps each plugin's output in a colored box if it is in the "main" placeholder.
'''
# Plugins not in the main placeholder should remain unchanged
# Plugins embedded in Text should remain unchanged in order not to break output
if placeholder.slot != 'main' or (instance._render_meta.text_enabled and instance.parent):
return rendered_content
else:
from django.template import Context, Template
# For simplicity's sake, construct the template from a string:
t = Template('<div style="border: 10px {{ border_color }} solid; background: {{ background_color }};">{{ content|safe }}</div>')
# Prepare that template's context:
c = Context({
'content': rendered_content,
# Some plugin models might allow you to customise the colors,
# for others, use default colors:
'background_color': instance.background_color if hasattr(instance, 'background_color') else 'lightyellow',
'border_color': instance.border_color if hasattr(instance, 'border_color') else 'lightblue',
})
# Finally, render the content through that template, and return the output
return t.render(c)
你可讓插件相互嵌套。要實現這個功能,須要完成如下幾個事情:
models.py:
class ParentPlugin(CMSPlugin):
# add your fields here
class ChildPlugin(CMSPlugin):
# add your fields here
cms_plugins.py:
from .models import ParentPlugin, ChildPlugin
@plugin_pool.register_plugin
class ParentCMSPlugin(CMSPluginBase):
render_template = 'parent.html'
name = 'Parent'
model = ParentPlugin
allow_children = True # This enables the parent plugin to accept child plugins
# You can also specify a list of plugins that are accepted as children,
# or leave it away completely to accept all
# child_classes = ['ChildCMSPlugin']
def render(self, context, instance, placeholder):
context = super(ParentCMSPlugin, self).render(context, instance, placeholder)
return context
@plugin_pool.register_plugin
class ChildCMSPlugin(CMSPluginBase):
render_template = 'child.html'
name = 'Child'
model = ChildPlugin
require_parent = True # Is it required that this plugin is a child of another plugin?
# You can also specify a list of plugins that are accepted as parents,
# or leave it away completely to accept all
# parent_classes = ['ParentCMSPlugin']
def render(self, context, instance, placeholder):
context = super(ChildCMSPlugin, self).render(context, instance, placeholder)
return context
parent.html:
{% load cms_tags %}
<div class="plugin parent">
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
</div>
child.html:
<div class="plugin child">
{{ instance }}
</div>
有三種方法去擴展placeholder或者插件的上下文菜單
你能夠重寫下面CMSPluginBase的三個方法來實現這個目的
例子
class AliasPlugin(CMSPluginBase):
name = _("Alias")
allow_children = False
model = AliasPluginModel
render_template = "cms/plugins/alias.html"
def render(self, context, instance, placeholder):
context = super(AliasPlugin, self).render(context, instance, placeholder)
if instance.plugin_id:
plugins = instance.plugin.get_descendants(include_self=True).order_by('placeholder', 'tree_id', 'level',
'position')
plugins = downcast_plugins(plugins)
plugins[0].parent_id = None
plugins = build_plugin_tree(plugins)
context['plugins'] = plugins
if instance.alias_placeholder_id:
content = render_placeholder(instance.alias_placeholder, context)
print content
context['content'] = mark_safe(content)
return context
def get_extra_global_plugin_menu_items(self, request, plugin):
return [
PluginMenuItem(
_("Create Alias"),
reverse("admin:cms_create_alias"),
data={'plugin_id': plugin.pk, 'csrfmiddlewaretoken': get_token(request)},
)
]
def get_extra_placeholder_menu_items(self, request, placeholder):
return [
PluginMenuItem(
_("Create Alias"),
reverse("admin:cms_create_alias"),
data={'placeholder_id': placeholder.pk, 'csrfmiddlewaretoken': get_token(request)},
)
]
def get_plugin_urls(self):
urlpatterns = [
url(r'^create_alias/$', self.create_alias, name='cms_create_alias'),
]
return urlpatterns
def create_alias(self, request):
if not request.user.is_staff:
return HttpResponseForbidden("not enough privileges")
if not 'plugin_id' in request.POST and not 'placeholder_id' in request.POST:
return HttpResponseBadRequest("plugin_id or placeholder_id POST parameter missing.")
plugin = None
placeholder = None
if 'plugin_id' in request.POST:
pk = request.POST['plugin_id']
try:
plugin = CMSPlugin.objects.get(pk=pk)
except CMSPlugin.DoesNotExist:
return HttpResponseBadRequest("plugin with id %s not found." % pk)
if 'placeholder_id' in request.POST:
pk = request.POST['placeholder_id']
try:
placeholder = Placeholder.objects.get(pk=pk)
except Placeholder.DoesNotExist:
return HttpResponseBadRequest("placeholder with id %s not found." % pk)
if not placeholder.has_change_permission(request):
return HttpResponseBadRequest("You do not have enough permission to alias this placeholder.")
clipboard = request.toolbar.clipboard
clipboard.cmsplugin_set.all().delete()
language = request.LANGUAGE_CODE
if plugin:
language = plugin.language
alias = AliasPluginModel(language=language, placeholder=clipboard, plugin_type="AliasPlugin")
if plugin:
alias.plugin = plugin
if placeholder:
alias.alias_placeholder = placeholder
alias.save()
return HttpResponse("ok")
在版本3.1,django MPTT遷移到了django-treebeard,插件模式在這兩個版本是不一樣的。Schema遷移並無受影響,由於遷移系統( south & django)檢測到了這個不一樣的基礎。若是你的數據遷移有下面的這些:
MyPlugin = apps.get_model('my_app', 'MyPlugin')
for plugin in MyPlugin.objects.all():
... do something ...
你能夠會碰到錯誤django.db.utils.OperationalError: (1054, "Unknown column 'cms_cmsplugin.level' in 'field list'")。由於不一樣的遷移執行順序,model歷史數據可能會失步。
爲保持3.0和3.x的兼容,你應該在執行django CMS遷移以前強制執行數據遷移,django CMS會建立treebeard域。經過執行這個,數據遷移會永遠在老的數據庫模式上執行,並不會對新的產生衝突。
對south遷移,添加下面代碼
from distutils.version import LooseVersion
import cms
USES_TREEBEARD = LooseVersion(cms.__version__) >= LooseVersion('3.1')
class Migration(DataMigration):
if USES_TREEBEARD:
needed_by = [
('cms', '0070_auto__add_field_cmsplugin_path__add_field_cmsplugin_depth__add_field_c')
]
對django遷移,添加下面代碼
from distutils.version import LooseVersion
import cms
USES_TREEBEARD = LooseVersion(cms.__version__) >= LooseVersion('3.1')
class Migration(migrations.Migration):
if USES_TREEBEARD:
run_before = [
('cms', '0004_auto_20140924_1038')
]
下一篇開始會介紹如何自定義django CMS菜單
關注下方公衆號獲取更多文章
http://docs.django-cms.org/en/release-3.4.x/how_to/custom_plugins.html