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
Related
I have a case that uses Page ID as a slug, after creating a new Page, Wagtail provides us a "View live" button but when we click on that button, it provides a wrong URL
The right URL should be ".../property-list/<property-id>"
I have searched on stack overflow, found this thread but the answer is still a mystery:
Wrong 'View live' url in Wagtail admin message after page creation when using id as slug
I have followed the Wagtail official document, using Wagtail Hooks to manipulate data. However, no success yet. This is my code:
#hooks.register('after_create_page')
def set_number_and_slug_after_property_page_created(request, page):
page.number = page.slug = str(page.id)
page.save()
new_revision = page.save_revision()
if page.live:
new_revision.publish()
Please help me, thank you.
The after_create_page hook doesn't work for this, as it's one of the last things to run during the Wagtail admin's page creation view, and the confirmation message (including the old URL) has already been constructed by that point. This can be remedied by using the post_save signal provided by Django instead - being a more low-level mechanism, it's more closely tied to the act of saving the database record, without Wagtail's admin logic getting in the way.
(It's also a bit more future-proof: Wagtail's after_create_page hook is designed to only be called when a user goes through the page creation area of the Wagtail admin, so that it can be used to customise that user's path through the admin if appropriate. If there's ever any other route by which a page might be created - like, say, a data import, or using translation tools for a multi-language site - then after_create_page will be bypassed, but the post_save signal will still be triggered.)
Assuming your project has a properties app where you're defining a PropertyPage model, your code can be rewritten to use post_save as follows - in properties/signals.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import PropertyPage
#receiver(post_save)
def set_number_and_slug_after_property_page_created(sender, instance, created, **kwargs):
if issubclass(sender, PropertyPage) and created:
page = instance
page.number = page.slug = str(page.id)
page.save()
new_revision = page.save_revision()
if page.live:
new_revision.publish()
Then, to connect this signal up on server startup, create a properties/apps.py containing the following (or just add / edit the ready method if you have an AppConfig class there already):
from django.apps import AppConfig
class PropertiesConfig(AppConfig):
name = 'properties'
def ready(self):
from . import signals
I want to overwrite create.html and edit.html used for models derived from Wagtails 'PageModel'.
If I understand the docs correctly it should be as simple as specifying the attributes:
class MyAdmin(ModelAdmin):
model = MyPage
create_template_name = "myapp/create.html"
edit_template_name = "myapp/edit.html"
My templates are located at projectroot/templates/myapp. It works fine if my model is a Django model but for a PageModel based model the create view still uses wagtailadmin/pages/create.html. I also tried the other location patterns mentioned in the docs w/o success.
Is it possible to change the edit and create templates for a PageModel? Or do the same limitations as for views apply, i.e. only index.html and inspect.html can be overwritten?
ModelAdmin does not provide create, edit, or delete functionality for Page models, as per the documentation note.
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’ functionality for non page type models (i.e. models that do not extend wagtailcore.models.Page). If your model is a ‘page type’ model, customising any of the following will not have any effect.
It can be a bit confusing as the ModelAdmin system would seem to work for page models also, but there are some other ways to modify how your page can be edited. These will not be scoped to the ModelAdmin area though.
Option 1 - customise the generated form for your MyPage model
If you only want to customise how the edit page form that gets generated you can modify the base_form_class on your page model.
Wagtail has documentation about how to create a custom page form.
Note: WagtailAdminPageForm extends Django's ModelFormMetaClass
Example
from django import forms
from django.db import models
from wagtail.admin.forms import WagtailAdminPageForm
from wagtail.core.models import Page
class EventPageForm(WagtailAdminPageForm):
# ...
class MyPage(Page):
# ...
base_form_class = MyPageForm
Option 2 - customise the view via hooks
To customise the create & edit views for the normal (e.g. clicking edit page on the Wagtail user bar or explorer) page editing interface, you will need to use Wagtail hooks. Here you have access to the request so you will likely be able to determine if you are in the ModelAdmin area.
Create a file called wagtail_hooks.py in your app folder and provide a hook that will return a custom response (this will need to be rendered by your custom view.).
There are separate hooks for before_create_page and before_edit_page
Example from before_create_page docs below.
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
#hooks.register('before_create_page')
def before_create_page(request, parent_page, page_class):
# Use a custom create view for the AwesomePage model
if page_class == AwesomePage:
return create_awesome_page(request, parent_page)
```python
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
I'm creating a custom django admin action to show the selected Projects in a chart which I have in a template, the problem that I'm having is that it's displaying all the existing projects and I want just to display the ones which the user selects in the admin part.
Here's the admin.py part which should filter the projects that the user had selected:
def show_gantt_chart_of_selected_projects(modeladmin, request, queryset):
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect("/xxx/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))
Here's the view.py part which should get the filtered projects:
def index(request):
projects = Project.objects.order_by('projectName') // I think this line could be the problem
context = {'projects': projects }
return render_to_response('xxx/ganttChart.html', context, context_instance=RequestContext(request))
When I open the chart site, the URL show the items that the user selected correctly(ex. http://x.x.x.x:xxxx/xxx/?ct=15&ids=10,1,3,5), but the chart still show all the existing projects.
The queryset parameter to the admin action already contains the selected projects. Alter to:
def show_gantt_chart_of_selected_projects(modeladmin, request, queryset):
ct = ContentType.objects.get_for_model(queryset.model) # why do you do this, you're not using it?
return HttpResponseRedirect("/xxx/?ct=%s&ids=%s" % (ct.pk, ",".join(queryset.values_list('pk', flat=True)))
BTW you should use reverse url resolving instead of hardcoding urls.
View, which I took the liberty to switch to a class-based version. You will want to do this eventually anyway:
from django.views.generic import ListView
class IndexView(ListView):
template_name = 'xxx/ganttChart.html'
context_object_name = 'projects'
model = Project
def get_queryset(self):
return Project.objects.filter(
pk__in=self.request.GET.get('ids','').split(','),
).order_by('projectName')
index = IndexView.as_view()
I have 2 models - for example, Book and Page.
Page has a foreign key to Book.
Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).
In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).
My problem - how can I achieve the behavior that a page that was read won't be deleted?
I'm using Django 1.4 and I tried several options:
Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.
Are there any other good options without overriding the html code?
Thanks,
Li
The solution is as follows (no HTML code is required):
In admin file, define the following:
from django.forms.models import BaseInlineFormSet
class PageFormSet(BaseInlineFormSet):
def clean(self):
super(PageFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = form.cleaned_data
curr_instance = form.instance
was_read = curr_instance.was_read
if (data.get('DELETE') and was_read):
raise ValidationError('Error')
class PageInline(admin.TabularInline):
model = Page
formset = PageFormSet
You could disable the delete checkbox UI-wise by creating your own custom
formset for the inline model, and set can_delete to False there. For
example:
from django.forms import models
from django.contrib import admin
class MyInline(models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInline, self).__init__(*args, **kwargs)
self.can_delete = False
class InlineOptions(admin.StackedInline):
model = InlineModel
formset = MyInline
class MainOptions(admin.ModelAdmin):
model = MainModel
inlines = [InlineOptions]
Another technique is to disable the DELETE checkbox.
This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
if some_criteria_to_prevent_deletion:
form.fields['DELETE'].disabled = True
This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.
In your inline, you can add the flag can_delete=False
EG:
class MyInline(admin.TabularInline):
model = models.mymodel
can_delete = False
I found a very easy solution to quietly avoid unwanted deletion of some inlines. You can just override delete_forms property method.
This works not just on admin, but on regular inlines too.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
#property
def deleted_forms(self):
deleted_forms = super(MyInlineFormSet, self).deleted_forms
for i, form in enumerate(deleted_forms):
# Use form.instance to access object instance if needed
if some_criteria_to_prevent_deletion:
deleted_forms.pop(i)
return deleted_forms