Custom linkhandler does not show up in PageLinkChooser - python

I am following the Wagtail docs to create a custom link handler:
myapp.handlers.py
from django.contrib.auth import get_user_model
from wagtail.core.rich_text import LinkHandler
class UserLinkHandler(LinkHandler):
identifier = 'user'
#staticmethod
def get_model():
return get_user_model()
#classmethod
def get_instance(cls, attrs):
model = cls.get_model()
return model.objects.get(username=attrs['username'])
#classmethod
def expand_db_attributes(cls, attrs):
user = cls.get_instance(attrs)
return '<a href="mailto:%s">' % user.email
my_app/wagtail_hooks.py
from wagtail.core import hooks
from my_app.handlers import MyCustomLinkHandler
#hooks.register('register_rich_text_features')
def register_link_handler(features):
features.register_link_type(LinkHandler)
However, the handler does not show up in the admin widget. The expected behaviour is it should be in an option in the link type bar:
I've followed the docs exactly, is there something missing?

This is not part of register_link_type's functionality. A link type handler only defines the mapping between the database representation of a link and the final HTML output - it doesn't provide any user interface support for actually inserting those links. As the linked documentation notes:
This example assumes that equivalent front-end functionality has been added to allow users to insert these kinds of links into their rich text editor.
For that, you'll need to consult https://docs.wagtail.org/en/stable/extending/extending_draftail.html.

Related

Best Way To Manage Model Instances Based On Attributes In Django Admin

Lets say I have a model:
class Applicant(models.Model):
name = models.CharField()
status = Models.CharField(choices=Choices)
Lets say Choices gives the choices of Reviewed, Rejected, Accepted.
I want to be able to declare a choice for an instance in the admin panel, and once I save the choice, the instance is moved to another section, preferably another admin folder such as Reviewed Applicants, Rejected Applicant, etc etc.
Whats the best way to achieve this?
Thank you
Quoted from This, This should solve your problem
from django.shortcuts import redirect
class PaymentAdmin(VersionAdmin, admin.ModelAdmin):
def response_add(self, request, obj, post_url_continue=None):
return redirect('/admin/sales/invoice')
def response_change(self, request, obj):
return redirect('/admin/sales/invoice')
and here're the official doc links

Wagtail: Dynamically Choosing Template With a Default

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)

CKAN: View is empty when inherited from a base controller

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

How to inherit and modify the standard Django CMS Link plugin?

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

Django Admin - how to prevent deletion of some of the inlines

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

Categories

Resources