I want to change CKAN registration logic, so I inherited from the CKAN UserController class and registered it as a route, but when I try to view the page, the registration form is empty. It shows only header, footer and the title of the form. I didn't create a ./user/new.html file, so I'm still using the ckan template file.
My plugin.py file
from ckan.config.routing import SubMapper
class AuthPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IConfigurer)
plugins.implements(IRoutes, inherit=True)
def update_config(self, config_):
toolkit.add_template_directory(config_, 'templates')
toolkit.add_public_directory(config_, 'public')
toolkit.add_resource('fanstatic', 'auth')
def before_map(self, map):
user_controller = 'ckanext.auth.controllers.user:UserController'
with SubMapper(map, controller=user_controller) as m:
m.connect('/user/register', action='register')
return map
User controller class:
from ckan.controllers.user import UserController as CoreUserController
class UserController(CoreUserController):
def register(self, data=None, errors=None, error_summary=None):
return super(UserController, self).register(data, errors, error_summary)
What should I do to make this work?
P.S: As I think the bug is in the ckan ./templates/user.html file, which has a {{ form | safe }} tag. There must be a some kind of context or something that I should set in my controller in order to make it work.
P.S.2: Strange thing is when I do the same thing for Organization controller, it works!!!
Thanks
Related
I'm wondering if there is a way in Wagtail to enter a custom template path via CharField in a base model, and then establish a template in an inherited model that would be the default. For example:
base/models.py
class WebPage(Page):
template_path = models.CharField()
def get_template(self, request):
if self.template_path:
template = template_path
else:
template = *something*
app/models.py
class MyWebPage(WebPage):
template = 'default_template.html'
Ideally, I'd establish the template attribute in the MyWebPage model, and that would act as a default. However, the get_template method in the WebPage base model would supersede it, but only if it's not empty. Is any of this possible?
I was reading through the Wagtail Docs and found this page (http://docs.wagtail.io/en/v2.1.1/advanced_topics/third_party_tutorials.html) and on that page was an article about dynamic templating. This is the page that has it: https://www.coactivate.org/projects/ejucovy/blog/2014/05/10/wagtail-notes-dynamic-templates-per-page/
The idea is to set a CharField and let the user select their template. In the following example they're using a drop down, which might even be better for you.
class CustomPage(Page):
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
^ code is from the coactivate.org website, it's not mine to take credit for.
In the template property, you could check if not self.template_string: and set your default path in there.
Edit #1:
Adding Page inheritance.
You can add a parent Page (the Base class) and modify that, then extend any other class with your new Base class. Here's an example:
class BasePage(Page):
"""This is your base Page class. It inherits from Page, and children can extend from this."""
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
class CustomPage(BasePage):
"""Your new custom Page."""
#property
def template(self):
"""Overwrite this property."""
return self.template_string
Additionally, you could set the BasePage to be an abstract class so your migrations don't create a database table for BasePage (if it's only used for inheritance)
I need to change some field default value given a value from the parent page when creating a new page. When editing an existing page, this is not a problem, but when creating a new page, I need to set a default value for a field. I have tried overriding the admin form, on init for WagtailAdminPageForm, the parent_page parameter is empty. And if I try to do it on the init method form Page subclass, there is no arg or kwargs with the parent information.
is there a way to get the page for which the new page will be the parent?
This is what I tried on Page constructor
class CMSPage(Page):
.
.
.
def __init__(self, *args, **kwargs):
super(BaseContentMixin, self).__init__(*args, **kwargs)
if hasattr(self.get_parent().specific, 'published_site') and self.get_parent().specific.published_site == 'extranet':
self.published_site = 'extranet'
This works editing a page, for new page, I get that NoneType objects has no attribute specific.
Django version is 1.10, Python version is 3.6, Wagtail version is 1.9
After clarification of the question, here is a second answer that might be more suitable. Note that this requires Wagtail 1.11.x which as at this time is not yet released.
Example Code for Solution
First Create a new class for your custom page form, usually in models.py or wherever the model for your page is.
from wagtail.wagtailadmin.forms import WagtailAdminPageForm
class MyCustomPageForm(WagtailAdminPageForm):
# Override the __init__ function to update 'initial' form values
def __init__(self, data=None, files=None, parent_page=None, *args, **kwargs):
print('parent_page', parent_page, parent_page.id, parent_page.title)
# update the kwargs BEFORE the init of the super form class
instance = kwargs.get('instance')
if not instance.id:
# only update the initial value when creating a new page
kwargs.update(initial={
# 'field': 'value'
'title': parent_page.id # can be anything from the parent page
})
# Ensure you call the super class __init__
super(MyCustomPageForm, self).__init__(data, files, *args, **kwargs)
self.parent_page = parent_page
Second On the page model definition, tell that model to use the form class you have defined
from wagtail.wagtailcore.models import Page
class MyCustomPage(Page):
base_form_class = MyCustomPageForm
Explanation of Solution
Wagtail provides the ability to override the form class (note: not model class) that gets called when building the form for creation and editing in Wagtail Admin.
Documentation: Customising generated forms
Our custom form class will inherit the WagtailAdminPageForm class.
Inside this new form class, we want to define our own __init__ function, this is called when the form is created.
Note the Github code for the default definition of __init__ on WagtailAdminPageForm
To inject custom values, we will override the initial data in kwargs before the calling of the class' super __init__
We only want to do this when a new page is being created, so check that the instance has no id
We can access the parent_page instance and any fields in it
After adding our custom initial data, then we call the super __init__ with the changed kwargs
Note: The reason for the requirement of Wagtail 1.11.x is that previous versions until that point did not add parent_page when calling the class model until pull request 3508
I would recommend using the Wagtail Hooks:
http://docs.wagtail.io/en/v1.10.1/reference/hooks.html
Here is a basic example that works (in the Wagtaildemo app), in this example, I am just getting the parent page's title. It should work the same if you are getting something else from the parent's page specific model. Note that this updates the record after it has been created, not as part of the creation itself.
# my_app/wagtail_hooks.py
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore import hooks
#hooks.register('after_create_page')
def do_after_page_create(request, page):
# get the parent page from the current page
parent = page.get_parent().specific
# get the value from the parent page
parent_title = parent.title
# get a new instance of the page that was just created
new_page = Page.objects.get(id=page.id).specific
# update the value in the page that was just created & save
new_page.parent_title = parent_title
new_page.save()
I have used the Wagtail hooks a lot and they work well, you could further refine this by checking the new_page.content_type == "CMSPage" to ensure this only updates pages of that specific model.
Finally, it might be good to look into how Wagtail sites works, this might be a more specific solution to your issue in the example code. You can add extra settings to your Site Model and have multiple Sites in Wagtail. I am not sure if your example was just a rough example or the exact issue you are facing.
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
I've tried the following code:
cms_plugins.py:
from djangocms_link.cms_plugins import LinkPlugin
from .models import SecondaryNavigationLink
class SecondaryNavigationLinkPlugin(LinkPlugin):
name = _("Secondary Navigation Link")
model = SecondaryNavigationLink
def render(self, context, instance, placeholder):
context.update({
'instance': instance,
'placeholder': placeholder,
})
return context
plugin_pool.register_plugin(SecondaryNavigationLinkPlugin)
models.py:
from djangocms_link.models import Link
class SecondaryNavigationLink(Link):
pass
I can see my link plugin rendered on the cms control panel under Generic section, but as soon as I clicked save, I see a 500 interval server error from the request, and Django CMS renders a plugin block like this
Secondary Navigation Link <Empty>
And I cannot do anything about it.
So it turns out that I made a stupid mistake by overwriting the default render method of Link plugin:
https://github.com/divio/djangocms-link/blob/master/djangocms_link/cms_plugins.py#L21
And this problem is fixed by simply get rid of my render function:
class SecondaryNavigationLinkPlugin(LinkPlugin):
name = _("Secondary Navigation Link")
model = SecondaryNavigationLink
# No more render
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