How to limit Django-CMS template_choices based on Page parent - django-cms

This question could probably be solved with a more broad Q: "How to replace a python 2.7 attribute with a property from outside", but maybe there's a Django-CMS way of accomplishing this, so I ask:
I'm trying to limit the template choices of Django-CMS' (v3.4.x) pages based on their parents, so for this I thought of overriding it's template_choices with a function, but I see that in Django-CMS' Page model it's loaded on creation, like this:
#python_2_unicode_compatible
class Page(...):
...
TEMPLATE_DEFAULT = get_cms_setting('TEMPLATES')[0][0]
template_choices = [(x, _(y)) for x, y in get_cms_setting('TEMPLATES')]
...
Modifying get_cms_settins is out of the question, but I do need to alter TEMPLATE_DEFAULT and template_choices so that they have the proper values I wish for. Since I'm still fairly new to Django and Python, my question is where and how do I do this?

My first attempt was to do something like this on my models.py:
#property
def template_choices(self):
from cms.utils import get_cms_setting
templates = [(x, _(y)) for x, y in get_cms_setting('TEMPLATES')]
if self.parent:
if self.parent.template == 'parent_a.html':
templates = [('child_A.html', _('Child A'))]
elif self.parent.template == 'parent_b.html':
templates = [('child_b.html', _('Child B'))]
else:
templates = [('fullwidth.html', _('Fullwidth'))]
else:
templates = [('home_page.html', _('Homepage')),
('parent_a.html', _('Parent A')),
('parent_b.html', _('Parent B'))]
return templates
#property
def template_default(self):
return self.template_choices[0][0]
Page.template_choices = template_choices
Page.TEMPLATE_DEFAULT = template_default
And this is setting those fields correctly if I try to inspect any instance of Page, however when I try to edit any page and I click on the Page>Templates menu, all templates are up for selection, so it seems the template_choices and TEMPLATE_DEFAULT attributes are being ignored. Inspecting pagemodel.py seems to confirm this, since the get_template and get_template_name methods use get_cms_setting('TEMPLATES') instead of those fields. Also in cms_toolbars.py for the # templates menu section get_cms_setting('TEMPLATES') rather than self.page.template_choices which seems to be the main culprit. So, this question has turned into a bug ticket: https://github.com/divio/django-cms/issues/6520

Related

How to create many Bokeh figures with multiprocessing?

I would like to speed up figure generation in Bokeh by multiprocessing:
jobs = []
for label in list(peakLabels):
args = {'data': rt_proj_data[label],
'label': label,
'tools': tools,
'colors': itertools.cycle(palette),
'files': files,
'highlight': highlight}
jobs.append(args)
pool = Pool(processes=cpu_count())
m = Manager()
q = m.Queue()
plots = pool.map_async(plot_peaks_parallel, jobs)
pool.close()
pool.join()
def plot_peaks_parallel(args):
data = args['data']
label = args['label']
colors = args['colors']
tools = args['tools']
files = args['files']
highlight = args['highlight']
p = figure(title=f'Peak: {label}',
x_axis_label='Retention Time',
y_axis_label='Intensity',
tools=tools)
...
return p
Though I ran into this error:
MaybeEncodingError: Error sending result: '[Figure(id='1078', ...)]'. Reason: 'PicklingError("Can't pickle at 0x7fc7df0c0ea0>: attribute lookup ColumnDataSource. on bokeh.models.sources failed")'
Can I do something to the object p, so that it becomes pickleable?
Individual Bokeh objects are not serializable in isolation, including with pickle. The smallest meaningful unit of serialization in Bokeh is the Document, which is a specific collection of Bokeh objects guaranteed to be complete with respect to following references. However, I would be surprised if pickle works with Document either (AFAIK you are the first person to ask about it since the project started, it's never been a priority, or even looked into that I know of). Instead, I would suggest if you want to do something like this, to use Bokeh's own JSON serialization functions, such as json_item:
# python code
p_serialized = json.dumps(json_item(p))
This will properly serialize p in the context of the Document it is a part of. Then you can pass this to your page templates to display with the Bokeh JS embed API:
# javascript code
p = JSON.parse(p_serialized);
Bokeh.embed.embed_item(p, "mydiv")

Plone: (Archetypes) Make News Item and Event to be folderish

I see I can use collective.folderishtypes to add new types (folderish) to be used instead of default news item and event. But I want to convert existing news items and events to folderish content types and keep it as simple as possible. Is it possible to override (monkey-patching?) the default types in a simple way (as result to have existing objects with folderish behavior)?
Or what is the good way of solving this issue? I just need existing objects to be solved, too and to have not confusing duplicate content types like: Add new News Item, News Item Folderish... etc. Also, if possible to keep existing listings (like latest events) working.
I have no experience with collective.folderish, the description sounds promising though, too bad it seems not to work for you.
If I needed to solve this and it's not a requirement to keep the histories (workflow- & content-history), I'd go create a new folderish type with the same fields, create for each event and news an instance of the new type and copy the field-values over.
That would change the modification-date, yet could be overcome by copying the mod-date to the publication-date-field (if not used already) and do the 'Latest news/events'-listings with collections sorted by pub-date.
But if you wanted to keep histories and leave mod-date untouched, you could create a folder for each news/event-item, put the item into the folder, set the item as default-view of the folder and rename the folder to the same id as the item. That will make the folder and item appear as one item in the UI and links to the item will not break because the folder is at the destination.
I tested this with a browser-view-script. Alas, adding a folder and moving the item within one script-run does not work for reasons I couldn't track down in short time. So one needs to call the browser-view three times:
from Acquisition import aq_parent, aq_inner
from Products.Five.browser import BrowserView
class View(BrowserView):
report = ''
def __call__(self):
portal = self.context
catalog = portal.portal_catalog
news_items = catalog.searchResults(portal_type='News Item')
event_items = catalog.searchResults(portal_type='Event')
items = news_items + event_items
for i, item in enumerate(items):
self.processItem(item, i, len(items))
return self.report
def processItem(self, item, i, itemsAmount):
item = item.getObject()
item_id = item.id
parent = aq_parent(aq_inner(item))
folder = None
folder_id = item_id + '-container'
if item_id == parent.id:
if i == itemsAmount-1: self.report += '\
Nothing to do, all ' + str(itemsAmount) + ' items have the same id as their parent.'
else:
if parent.id == folder_id:
parent = getParent(parent)
folder = parent[folder_id]
folder.setDefaultPage(item_id)
parent.manage_renameObject(folder.id, item_id)
if i == itemsAmount-1: self.report += '\
Step 3/3: Renamed ' + str(itemsAmount) + ' folder-ids.'
else:
try:
folder = addFolder(parent, folder_id)
if i == itemsAmount-1: self.report += '\
Step 1/3: Added ' + str(itemsAmount) + ' folders.'
folder.setTitle(item_id) # set same title as item has
folder.reindexObject()
except:
folder = parent[folder_id]
try:
cutAndPaste(item, folder)
if i == itemsAmount-1: self.report += '\
Step 2/3: Moved ' + str(itemsAmount) + ' items into folders.'
except:
pass
def addFolder(parent, folder_id):
parent.invokeFactory('Folder', folder_id)
folder = parent[folder_id]
folder.setTitle(folder_id)
folder.reindexObject()
return folder
def cutAndPaste(item, folder):
""" Move item into folder. """
parent = aq_parent(aq_inner(item))
clipboard = parent.manage_cutObjects([item.id])
folder.manage_pasteObjects(clipboard)
folder.reindexObject()
def getParent(item):
return aq_parent(aq_inner(item))
Disclaimers:
You need to do this procedure also every time a new event/news-item is created, with an event-listener.
It would be better to create new event-listeners for each step of the process and start the next one when the preceding step has ended.
The temporary-id for the folder (composed of item-id and the arbitrary suffix "-container") is assumed to not exist already within the parent of an item. Although it is very unlikely to happen, you might want to grab that exception in the script, too.
I have not tested this, but based on the collective.folderishtypes documentation ("How to migrate non-folderishtypes to folderish ones") you should be able to call the ##migrate-btrees view on your Plone site root to migrate non-folderish content types to folderish.
Warning: do a backup of the database before attempting the migration, and test in development environment first before applying this on production data.

Grokless way for dynamic vocabularies?

I have a python-script, which returns returns a context-based, dynamically generated simple list:
def myVocabMethod(self):
mylist = ['a','list','apart']
# do sth dynamic
return mylist
I would like to pass the result to the selection-field with the vocabulary-attribute looking s.th. like this:
atapi.StringField('theField'
vocabulary=.myPythonScript.myVocabMethod(),
(...)
),
How to glue the script-results and and the vocab-value together?
The documentation I found, always requires Grok. Also it's just a simple list, no i18n or other more complex features needed.
Grokless way to register a named vocabulary:
http://developer.plone.org/forms/vocabularies.html#registering-a-named-vocabulary-provider-in-zcml
Basically you point it to a function which returns SimpleVocabulary instance.
The post, where I found what I was looking for is this one:
http://www.universalwebservices.net/web-programming-resources/zope-plone/dynamic-vocabularies-in-plone-archetypes/
And is referenced in the official docs here:
http://developer.plone.org/content/archetypes/fields.html#dynamic-vocabularies
For anyone who might be interested, this is the code:
from Acquisition import aq_parent
from Products.Archetypes import atapi
from Products.Archetypes.public import DisplayList
YourArchetypeSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((
atapi.StringField(
'somefieldname',
vocabulary = yourVocabulary,
),
))
class YourArchetype(base.ATCTContent):
def yourVocabulary(self):
dl = DisplayList()
# We can do something context-based here,
# f.e. get all Events in parent-folder:
eventlist = aq_parent(self).contentValues(filter={'portal_type' : 'Event'})
for event in eventlist:
dl.add(event['id'], event['title'])
return dl
atapi.registerType(YourArchetype, PROJECTNAME)

Pyjs / Pyjamas Frame: How to use a button to change the url in a frame

I have a web page that contains two buttons and a frame. Within the frame, a web page is displayed. I am trying to make it so button A shoes url '/AAA' in the frame while button B shoes url '/BBB' in the frame. How the heck can I do that?
Here is what I have:
class ImageButton(SimplePanel):
def __init__(self, image_location):
'''(None, str) -> None'''
SimplePanel.__init__(self)
img = Image(image_location, StyleName='rangler')
img.addClickListener(getattr(self, "onImageClick"))
self.add(img)
def onImageClick(self, sender=None):
pass
#This is where I need help!?
class QAFrame(SimplePanel):
def __init__(self, current_url):
SimplePanel.__init__(self)
frame = Frame(current_url,
Width="200%",
Height="650px")
self.add(frame)
def caption():
style_sheet = HTML("""<link rel='stylesheet' href='about_us.css'>""")
srah_pic = ImageButton("Steve.jpg")
fl_pic = ImageButton("Fraser.jpg")
horizontal = HorizontalPanel()
vertical = VerticalPanel()
vertical.add(srah_pic)
vertical.add(fl_pic)
horizontal.add(vertical)
QAFrame('Fraser_qa.htm')
QAFrame('Steve_qa.htm')
horizontal.add(QAFrame('Steve_qa.htm'))
RootPanel().add(horizontal)
Basically,
You need to .addClickListener to your button and, as a parameter, you want to pass in the handler that will perform your desired task on button click.
The one thing that really confused me was, I could not pass an argument through to my handler. But, the object "sender" is automatically passed in with the handler. You can try to fish through the senders attributes to find information that you need.
class ImageButton(SimplePanel):
def __init__(self, image_location, css_style):
'''(None, str, str) -> None'''
SimplePanel.__init__(self)
self.image_location = image_location
img = Image(self.image_location, StyleName= css_style)
img.addClickListener(Cool) # Cool is the name of my handler function
self.add(img)
def Cool(sender): # You can do whatever you want in your handler function.
# I am changing the url of a frame
# It is a little-medium "Hacky"
if sender.getUrl()[-9:] == 'Steve.jpg':
iframe.setUrl('Fraser_qa.htm')
else:
iframe.setUrl('Steve_qa.htm')
I would extend my ImageButton class to support passing in the URL to the webpage you want to show. In the __init__ function, you can store that URL in an instance property.
You should make the clickhandler into an instance method, which can access the instance variable which holds the URL to the desired page.
I lack the definitive Python knowledge to supply code examples. Hope you still understand the concept.

TreeNode.Remove doesn't work !

I have a strange problem. Let's look at that code:
TreeNode tn = TreeView1.FindNode("2009/08/12 (1)"); //OK, the Node is found
Now, I need to delete that node:
(IT DOESN'T WORK !)
(e.g. (I know that I don't need to use TreeView1.FindNode() method, but i = -1))
TreeNode tn1 = TreeView1.FindNode(tn.ValuePath);
int i = TreeView1.Nodes.IndexOf(tn1);
or
TreeView1.Nodes.Remove(tn);
The problem is, the codes above doesn't work, I mean, the node isn't removed, why ?
The TreeView looks like that:
alt text http://img130.imageshack.us/img130/230/71970321.png
It seems that the TreeView control in .net only allows to remove First Level Nodes, so if the node you are trying to delete is not this kind of node, you need to delete it trough its parent, using something like this:
Dim Padre As TreeNode = TreeView1.SelectedNode.Parent
If (Padre Is Nothing) Then
TreeView1.Nodes.Remove(TreeView1.SelectedNode)
Else
Padre.ChildNodes.Remove(TreeView1.SelectedNode)
End If
Hope it helps!
Are you sure that you've selected the node properly? If TreeView1.Nodes.IndexOf(tn1) is returning -1, this indicates the node can't be found.

Resources