Django CMS plugin nesting --- child doesn't show up in the "structure" interface - django-cms

I'm trying to create a Django CMS custom plugin that can assemble other plugins.
As far as I can tell, Django CMS can do this using Plugin nesting, and I've followed the examples to create a simple test case.
My expectation is that when you go into the "Structure" tab for a record in the model that has a PlaceholderField that includes the parent plugin, when you add a parent plugin, the pop-up for that model should ALSO have some way to edit/create/add an instance of the child plugin. But it doesn't --- all I see are the fields for the parent plugin and NOTHING about the children (see screenshot below).
Or am I missing the point of Plugin nesting entirely?
models.py:
from django.db import models
from cms.models import CMSPlugin
from cms.models.fields import PlaceholderField
from djangocms_text_ckeditor.models import AbstractText
class CustomPlugin(CMSPlugin):
title = models.CharField('Title', max_length=200, null=False)
placeholder_items = PlaceholderField ('custom-content')
renderer = models.CharField('Renderer', max_length=50, null=True, blank=True,
help_text='This is just to show that a custom renderer CAN be done here!')
class ChildTextPlugin(AbstractText):
pass
cms_plugins.py:
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from django.utils.translation import ugettext as _
from .models import CustomPlugin, ChildTextPlugin
class CMSCustomPlugin(CMSPluginBase):
model = CustomPlugin
name = _('Custom Plugin')
render_template = 'custom/custom_plugin.html'
allow_children = True
def render(self, context, instance, placeholder):
context = super(CMSCustomPlugin, self).render(context, instance, placeholder)
return context
class CMSChildTextPlugin(CMSPluginBase):
model = ChildTextPlugin
name = _('Child Text Plugin')
render_template = 'custom/child_text_plugin.html'
parent_classes = ['CMSCustomPlugin',]
def render(self, context, instance, placeholder):
context = super(ChildTextPlugin, self).render(context, instance, placeholder)
return context
plugin_pool.register_plugin(CMSCustomPlugin)
plugin_pool.register_plugin(CMSChildTextPlugin)

... and the answer is "it was working all the time" --- the interface comes AFTER the screen I posted above is submitted --- the Custom Plugin entry will have a "+" icon, and it's THERE that the children are found.

Related

I am trying to bind my FormView to show the Form in djangocms but i am not able to

I am trying to bind my FormView to show the Form in djangocms but I am not able to,
my model is:
class ContactFormView(FormView, CMSPlugin):
template = '/ContactForm/ContactForm.html'
form_class = contact_form
success_url = reverse_lazy('success-page')
def post(self, request, **kwargs):
assert request.is_ajax()
request_data = json.loads(request.body)
form = self.form_class(data=request_data[self.form_class.scope_prefix])
if form.is_valid():
return JsonResponse({'success_url': force_text(self.success_url)})
else:
response_data = {form.form_name: form.errors}
return JsonResponse(response_data, status=422)
cms_plugins.py:
class ContactFormPlugin(CMSPluginBase):
model = ContactForm.ContactFormView
render_template = '/ContactForm/ContactForm.html'
name = 'Contact Form'
allow_children = False
plugin_pool.register_plugin(ContactFormPlugin)
Looking at what you've got there, I'm not sure you've given your plugin a proper model but a view!? And the form class isn't a form class, form_class = contact_form but what is contact_form? You need it to be the class of your form e.g.
class ContactForm(forms.Form):
name = forms.CharField(label='Your name', max_length=100)
email = forms.EmailField(label='Your email', max_length=100)
class ContactFormView(FormView):
template = '/ContactForm/ContactForm.html'
form_class = ContactForm
success_url = reverse_lazy('success-page')
And if you've got a form you want your plugin to render, you need to tell it what to do...
from cms.models.pluginmodel import CMSPlugin
class ContactFormPlugin(CMSPluginBase):
model = CMSPlugin
render_template = '/ContactForm/ContactForm.html'
name = 'Contact Form'
allow_children = False
def render(self, context, instance, placeholder):
context = super(ContactFormPlugin, self).render(context, instance, placeholder)
context['form'] = ContactForm()
return context
However you're working with a CMS plugin, so you don't define a View at all for a plugin. I think you could make this do what you're trying to do but the view doesn't want to inherit CMSPlugin because you just want a view to receive data from the plugin.
If you need to define views then you're building an app which in CMS requires an Apphook, not a plugin. (Technically you could post to a URL without an Apphook, but depends how much CMS integration you want)
There is a great example that starts with a plugin posting to a view then progresses into the app integration which is possible here; http://docs.django-cms.org/en/latest/introduction/plugins.html

Plone: Add additional registration fields to users (members)

I have two additional fields for my group objects (like described here).
Now I need (other) additional fields for my member objects as well (short strings). I have created them in portal_memberdata/manage_propertiesForm, but I still can't select them for registration form usage (##member-registration).
I need the two new fields for registration, at least one of them mandatory. How can I achieve this? Thank you!
Update:
I found plone.app.users.userdataschema and added my fields to the interface IUserDataSchema; furthermore, I monkeypatched plone.app.users.browser.personalpreferences.UserDataPanelAdapter. There still seems to be missing something (no change visible in ##member-registration).
My customization code looks like this:
from plone.app.users.userdataschema import IUserDataSchema
from zope import schema
from Products.CMFPlone import PloneMessageFactory as _
IUserDataSchema.custom1 = schema.ASCIILine(
title=_(u'label_custom1',
default=u'Custom1 member id'),
description=_(u'help_custom1_creation',
default=u'Custom1 membership is required; '
u'please enter your member id'),
required=True)
from plone.app.users.browser.personalpreferences import UserDataPanelAdapter
def set_custom1(self, value):
if value is None:
value = ''
return self.context.setMemberProperties({'custom1': value})
def get_custom1(self):
return self._getProperty('custom1')
UserDataPanelAdapter.custom1 = property(get_custom1, set_custom1)
It didn't work when I used the monkeypatched original interface class;
but it does work to monkeypatch the UserDataSchemaProvider to return a subclass:
from plone.app.users.userdataschema import IUserDataSchema
from plone.app.users.userdataschema import UserDataSchemaProvider
from zope import schema
from Products.CMFPlone import PloneMessageFactory as _
class IUserDataSchemaExtended(IUserDataSchema):
"""
Extends the userdata schema
by a mandatory field
"""
customField1 = schema.ASCIILine(
title=_(u'label_customField1',
default=u'CustomField1 member id'),
description=_(u'help_customField1_creation',
default=u'CustomField1 membership is required; '
u'please enter your member id'),
required=True)
def getExtendedSchema(self):
return IUserDataSchemaExtended
UserDataSchemaProvider.getSchema = getExtendedSchema
from plone.app.users.browser.personalpreferences import UserDataPanelAdapter
def set_customField1(self, value):
if value is None:
value = ''
return self.context.setMemberProperties({'customField1': value})
def get_customField1(self):
return self._getProperty('customField1')
UserDataPanelAdapter.customField1 = property(get_customField1, set_customField1)
Remarks:
It might be better to simply use customField1 for the translatable title instead of label_customField as the name the field is used when the registration page is quickedited
with Plone 5, it apparently is possible to configure additional userdata fields via XML

Scrapy - parsing all sub-pages of a given domain

I would like to parse kickstarter.com projects using scrapy, but can't figure out how to make the spider search projects that I don't explicitly specify under start_urls. I have the first part of the scrapy code figured out (I can extract the necessary information from one website), I just can't get it to do this for all projects under the domain kickstarter.com/projects.
From what I've read, I believe that parsing is possible (1) using links on the starting page (kickstarter.com/projects), (2) using links from one project page to jump to another project, and (3) using a site map (which I don't think kickstarter.com has) to locate webpages to parse.
I've spent hours trying each of these methods but and I am getting nowhere.
I've used the scrapy tutorial code and built on it.
Here is the part so far that works:
from scrapy import log
from scrapy.contrib.spiders import CrawlSpider
from scrapy.selector import HtmlXPathSelector
from tutorial.items import kickstarteritem
class kickstarter(CrawlSpider):
name = 'kickstarter'
allowed_domains = ['kickstarter.com']
start_urls = ["http://www.kickstarter.com/projects/brucegoldwell/dragon-keepers-book-iv-fantasy-mystery-magic"]
def parse(self, response):
x = HtmlXPathSelector(response)
item = kickstarteritem()
item['url'] = response.url
item['name'] = x.select("//div[#class='NS-project_-running_board']/h2[#id='title']/a/text()").extract()
item['launched'] = x.select("//li[#class='posted']/text()").extract()
item['ended'] = x.select("//li[#class='ends']/text()").extract()
item['backers'] = x.select("//span[#class='count']/data[#data-format='number']/#data-value").extract()
item['pledge'] = x.select("//div[#class='num']/#data-pledged").extract()
item['goal'] = x.select("//div[#class='num']/#data-goal").extract()
return item
Since you're subclassing CrawlSpider, do not override parse. CrawlSpider's link crawling logic is contained within parse, which you really need.
As for the crawling itself, that's what the rules class attribute is for. I haven't tested it, but it should work:
from scrapy.contrib.spiders import CrawlSpider
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.loader import XPathItemLoader
from scrapy.selector import HtmlXPathSelector
from tutorial.items import kickstarteritem
class kickstarter(CrawlSpider):
name = 'kickstarter'
allowed_domains = ['kickstarter.com']
start_urls = ['http://www.kickstarter.com/discover/recently-launched']
rules = (
Rule(
SgmlLinkExtractor(allow=r'\?page=\d+'),
follow=True
),
Rule(
SgmlLinkExtractor(allow=r'/projects/'),
callback='parse_item'
)
)
def parse_item(self, response):
xpath = HtmlXPathSelector(response)
loader = XPathItemLoader(item=kickstarteritem(), response=response)
loader.add_value('url', response.url)
loader.add_xpath('name', '//div[#class="NS-project_-running_board"]/h2[#id="title"]/a/text()')
loader.add_xpath('launched', '//li[#class="posted"]/text()')
loader.add_xpath('ended', '//li[#class="ends"]/text()')
loader.add_xpath('backers', '//span[#class="count"]/data[#data-format="number"]/#data-value')
loader.add_xpath('pledge', '//div[#class="num"]/#data-pledged')
loader.add_xpath('goal', '//div[#class="num"]/#data-goal')
yield loader.load_item()
The spider crawls the pages of the recently launched projects.
Also, use yield instead of return. It's better to keep your spider's output a generator and it lets you yield multiple items/requests without making a list to hold them.

How could I render just the form from an autoform?

I'm in the context of a BrowserView. I want to render a form inside the page.
class CommandeView(BrowserView):
...
def render_myform(self):
livraison_form = LivraisonForm(self.context, self.request)
livraison_form.update()
return livraison_form.render()
class ILivraisonForm(interface.Interface):
livraison = schema.Choice(title=_(u"Mode de livraison"),
vocabulary=livraison_vocabulary)
class LivraisonForm(AutoExtensibleForm, form.EditForm):
schema = ILivraisonForm
class LivraisonFormAdapter(object):
component.adapts(ICommande)
interface.implements(ILivraisonForm)
def __init__(self, context):
self.context = context
self.livraison = self.context.livraison
I would like render_myform to render only the form but it return a HTML Plone page.
Any tips are welcomed.
Haven't checked this in-depth, but the form is probably wrapped by the plone FormWrapper class.
Look for a .form attribute on the LivraisonForm instance:
def render_myform(self):
livraison_form = LivraisonForm(self.context, self.request).form
livraison_form.update()
return livraison_form.render()
It may be you need to add an additional interface to the request for this to work (the wrapper normally does this for you. I'm a little low on time right now, so you get this untested:
from plone.z3cform import z2
class CommandeView(BrowserView):
def render_myform(self):
z2.switch_on(self) # Apply layer interface, set up locale, add `getURL` method
livraison_form = LivraisonForm(self.context, self.request).form
livraison_form.update()
return livraison_form.render()
The Plone Knowledgebase uses this same setup to show a form in a viewlet.

relating models to one another using generic views

I'm new to Django and programming in general. I'm trying to make a simple site that allows players of a sport sign up for leagues that have been created by the admin. In my models.py, I created two models:
from django.db import models
from django.forms import ModelForm
class League(models.Model):
league_name = models.CharField(max_length=100)
pub_date = models.DateTimeField('date published')
class Info(models.Model):
league = models.ManyToManyField(League)
name = models.CharField(max_length=50)
phone = models.IntegerField()
email = models.EmailField()
def __unicode__(self):
return self.info
class InfoForm (ModelForm):
class Meta:
model = Info
exclude = ('league')
From what I've read, I can probably use the Create/Update/Delete generic views to display a form for the user to sign up for the league. So with my app, I want the user to come to a simple homepage that lists the leagues, be able to click on the league and enter their info to sign up. Here's what my urlconf looks like:
from django.conf.urls.defaults import *
from mysite.player_info.models import League, Info, InfoForm
info_dict = {
'queryset': League.objects.all(),
}
InfoForm = {'form_class' : InfoForm}
urlpatterns = patterns('',
(r'^$', 'django.views.generic.list_detail.object_list', info_dict),
(r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='player_info/results.html'), 'league_results'),
(r'^(?P<object_id>\d+)/info/create/$', 'django.views.generic.create_update.create_object', InfoForm),
)
Here's my problem: When I click on a league to sign up for on the homepage with my current setup, I get this error: TypeError at /league/1/info/create.... create_object() got an unexpected keyword argument 'object_id'. What am I doing wrong?
The issue isn't with your models, but rather with the function your "create" URL calls -- the line that calls django.views.generic.create_update.create_object() in urls.py. create_object() doesn't take an object_id argument, but you specified one in your url (r'^(?P<object_id>\d+)/info/create/$'). This makes sense -- you're creating an object, so you don't know its ID yet. create_object() only takes a form_class or model argument, as noted in the docs.
I'm guessing you're trying to create an Info object that is attached to a League object, and in that URL, <object_id> is the ID number of the League object; in which case, you shouldn't name that ID number, and instead should just use r"^\d+/info/create/$" as the URL. I'm not sure how you'll grab the league ID number using Django's create_object() function, though. You might have to write your own view handler. You may be able to use a custom ModelForm and pass it in with the form_class parameter, but I'm not sure.

Resources