Django CMS how to create nested plugins programatically - python

I am writing a migration script to parse an old html website into Django CMS pages.
The thing I need is to understand on how to nest plugins programatically.
In particular case I need to have html < a> tags converted into CMS LinkPlugin objects, nested inside text that is edited by standard ckeditor TextPlugin of Django-CMS.
How to programmatically nest plugins inside other plugins of Django CMS. In my case I need to nest a CMS Link plugin inside of the TextPlugin in the text.
I know on how to parse text. I do not understand on how to do it from nested CMS plugins perspective?
I can not interconnect the Link CMS plugin object instance and CMSPlugin object instance that I insert into the ancestor TextPlugin.
More context:
Note I really know how to do this from UI perspective. I need to emulate this in a script.
I have dumped the database into JSON and noticed there are certain things there.
First I have a CMSPlugin class instance that is placed into a page placeholder. (Sotle this part from placeholderadmin.py of the CMS)
position = CMSPlugin.objects.filter(language=lang, parent=parent).count()
plugin = CMSPlugin(
language='en',
position=position,
plugin_type=plugin_type,
placeholder=placeholder,
)
plugin.insert_at(parent, position='last-child', save=False)
plugin.save()
# ?????
plugin.link = Link(
name='Link text',
page_link=target_page,
placeholder=placeholder,
)
plugin.save()
This creates a nested plugin in a proper placeholder and attaches it into a text plugin. However it is added with a blank LinkPlugin instance. I'm later creating an instance of a Link plugin in the CMS.
The thing is I do not know on how to do this properly.
From UI perspective the CMS plugin is added nested but contains no real plugin instance. SO the Admin plugins tree for that placeholder is rendered with empty Link plugins.
CMSPlugins are added Link < Empty>.
I can edit this created Link plugin through admin and add a text and target link. How to do this programatically. E.g. inside of a script? Script must do 1000-s of pages so I can not do it manually

Sorry just to be consistent on this. This is far more complicated in logic then it seems.
I did an article on this.
Django CMS Adding plugins inside plugins programmatically
In general the solution is to mimic the CMS way of doing this.
# Getting an site admin instance
admin_site = AdminSite()
instance, plugin_admin = plugin.get_plugin_instance(admin_site)
plugin_admin.cms_plugin_instance = plugin
plugin_admin.placeholder = plugin.placeholder
# Triggering the Django Admin add view with our request.
# That's how Django-CMS does this action itself.
response = plugin_admin.add_view(request)
Look for the full snippet in article. Hope this helps someone with similar problems.

To add nested plugins you need to do this:
add_plugin(
placeholder=placeholder,
plugin_type='TextPlugin',
language=translation.get_language(),
)
target = placeholder.get_plugins().get(plugin_type='TextPlugin')
add_plugin(
placeholder=placeholder, #same placeholder as the parent plugin
plugin_type='LinkPlugin',
language=translation.get_language(),
target=target, #the parent plugin
#here comes the params from the selected plugin
name='Google',
url='http://www.google.com'
)
This also works with custom plugins.

Did you tried to save Link plugin that you've created?
plugin.link = Link(
name='Link text',
page_link=target_page,
placeholder=placeholder,
)
maybe try to add
plugin.link.save()
I hope that is the case.

To create nested plugins in your cms_plugins.py file
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().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
In your plugin template file of parent plugin
{% load cms_tags %}
<div class="plugin parent">
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
</div>
For detailed documentation, go through
https://docs.django-cms.org/en/latest/how_to/custom_plugins.html#nested-plugins

Related

Include a menu to several templates in Django

I'm trying NOT to write same code twice on different templates. Real hassle when changing something.
So when I go to a section of the webpage, I want to display a side menu. This side-menu is suppose to be on several templates. Like index.html, detail.html, manage.html and so on.
But the section is only a part of the webpage, so I can't have it in base.html.
I was thinking about using include. But since the side menu is dependent of DB queries to be generated, I then have to do queries for each view. Which also makes redundant code.
What is best practice for this feature?
Cheers!
You could write custom inclusion_tag, that's more feasible for the scenario:
my_app/templatetags/my_app_tags.py
from django import template
register = template.Library()
#register.inclusion_tag('side_menu.html')
def side_menu(*args, **kwargs):
# prepare context here for `side_menu.html`
ctx = {}
return ctx
Then in any template you want to include side menu do this:
{% load side_menu from my_app_tags %}
{% side_menu %}

How to make HTML imported Campaign Monitor templates editable?

During the import procedure I made sure to mark the imgs editable, and put several content sections into multiline constructs. The imported templates show up in the client's template set, but when I try to edit them, I only presented with the zip/HTML upload option instead of the actual editor:
From my code base, my own template object has the exporting code:
class EmailBodyTemplate(models.Model):
...
def migrate_email_template(self, cm_client_id, cm_token):
cm_template = Template(cm_token)
# Basically I expose a specially tailored HTML version of my template
# for CM: adding editable to imgs, introducing multiline wrappers and
# adding unsubscribe, view in browser and other special links
html_url = settings.HOME_URL + reverse('email_import_view', args=(self.id,))
template_id = cm_template.create(cm_client_id, self.name, html_url, None)
self.template_id = template_id
self.save()
I reached out to Campaign Monitor support on the matter, and got the info that their editor is not applicable for imported HTML templates from an external source. In that case you need to keep editing those outside of the system and re-upload after edit.

django-easy-select2 Select2Multiple widget doesn't render

The installation page looked simple enough. I installed it, added easy_select2 in INSTALLED_APPS in the settings, ran collectstatic, and then had this in my form:
from easy_select2 import Select2Multiple
# from django_select2 import forms as select2_forms
class LeadForm(forms.Form):
email = forms.CharField(max_length=100)
overseas_company = forms.MultipleChoiceField(
choices=countries,
label='Do you have any companies overseas and where?',
widget=Select2Multiple()
)
But it still renders as if I had done nothing at all. I tried django_select2 as well, and it didn't work either, so I must be doing something wrong Select2 wise.
I tried looking at the HTTP request log. Merely enabling easy_select2 doesn't make the template request the jQuery/select2 js files that are needed for the Select2 widget to function. Is this the problem? But the tutorial never said I had to add anything to any existing templates.
I had the same problem too.
You have to add {{ form.media }} in the head section of your template for django to make it work.
source: http://do-it-big.com/getting-django-easy-select2-to-include-jquery-and-friends/

How to add button next to Add User button in Django Admin Site

I am working on Django Project where I need to extract the list of user to excel from the Django Admin's Users Screen. I added actions variable to my Sample Class for getting the CheckBox before each user's id.
class SampleClass(admin.ModelAdmin):
actions =[make_published]
Action make_published is already defined. Now I want to append another button next to Add user button as shown in fig. . But I dont know how can I achieve this this with out using new template. I want to use that button for printing selected user data to excel. Thanks, please guide me.
Create a template in you template folder: admin/YOUR_APP/YOUR_MODEL/change_list.html
Put this into that template
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
{{ block.super }}
<li>
Export
</li>
{% endblock %}
Create a view function in YOUR_APP/admin.py and secure it with annotation
from django.contrib.admin.views.decorators import staff_member_required
#staff_member_required
def export(self, request):
... do your stuff ...
return HttpResponseRedirect(request.META["HTTP_REFERER"])
Add new url into YOUR_APP/admin.py to url config for admin model
from django.conf.urls import patterns, include, url
class YOUR_MODELAdmin(admin.ModelAdmin):
... list def stuff ...
def get_urls(self):
urls = super(MenuOrderAdmin, self).get_urls()
my_urls = patterns("",
url(r"^export/$", export)
)
return my_urls + urls
Enjoy ;)
The easy and accepted way is to override the template.
If you don't want to mess with the Django templates, you could add a Media class to your admin and add some javascript to create the button although I think creating elements with javascript is a bit nasty and should be avoided.
Though other answers are entirely valid, I think it is important to note that it is absolutely not necessary to add a button to get such behavior. You can use admin actions, as you did for the make_published action.
This as the advantage of not requiring to override any template, and thus prevent from potential troubles when upgrading django version (as admin templates may change, and changes might not be "compatible" with the way you overrode it).
import csv
from django.http import HttpResponse
from django.utils import timezone
def export_as_csv(modeladmin, request, queryset):
opts = modeladmin.model._meta
filename = format(timezone.now(), "{app}_{model}-%Y%m%d_%H%M.csv").format(
app=opts.app_label, model=opts.model_name)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
writer = csv.writer(response)
field_names = [f.get_attname() for f in opts.concrete_fields]
writer.writerow(field_names)
for obj in queryset.only(*field_names):
writer.writerow([str(getattr(obj, f)) for f in field_names])
return response
Admin actions are made for this, adding a custom button is one step closer to "over-customization", which means it's probably time to write your own views.
The admin has many hooks for customization, but beware of trying to use those hooks exclusively. If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.
Quote from the introduction paragraph of Django Admin's documentation

Render django application within TextField

I am creating an application to display articles. In my model, I have a TextField that will contain the content of the article.
Now I would like to be able to render another application within my article. Let say I have a poll application and I would like to display a poll in the middle of the article. How can I do that ?
I tried by putting a {% render_poll 42 %} within the post but, as expected, it just display that within the generated page.
Should I create some kind of tag (like let say [poll=42]) and parse it before displaying the rendered html page ? (I use the markdown library, maybe I could extend it.) How can I do that to stay in a nice "django friendly" way ? I want that, when in the admin panel, I can easily insert poll (or other apps) within an article.
You could compile the TextField string as a template. Ie.:
from django.db import models
from django.template import Template, Context
class YourModel(models.Model):
body = models.TextField()
def render_body(self, context=None):
template = Template(self.body)
context = context or {}
context['object'] = self
return template.render(Context(context))
Then in the actual template, you could use {{ your_model.render_body }} rather than {{ your_model.body }}.

Categories

Resources