Wagtail Hide/Show menu item by user permissions - wagtail

How to hide-show menu in wagtail CMS:
Here is my code on register_admin_menu_item hook inside blog/wagtail_hooks.py
from wagtail.core import hooks
from wagtail.admin.menu import MenuItem
#hooks.register('register_admin_menu_item')
def register_custom_admin_menu_item():
return MenuItem(_('Site Admin'), reverse('admin_menu'), classnames='icon icon-folder-inverse',
order=20000)

For imposing the access on the menu, we can create the custom MenuItem Class and override the is_shown method as below:
class CustomAdminMenuItem(MenuItem):
def is_shown(self, request):
return request.user.is_staff
Now use this CustomAdminMenuItem instead of MenuItem like:
from wagtail.core import hooks
from wagtail.admin.menu import MenuItem
#hooks.register('register_admin_menu_item')
def register_custom_admin_menu_item():
return CustomAdminMenuItem(_('Site Admin'), reverse('admin_menu'), classnames='icon icon-folder-inverse',
order=20000)
You can implement custom permission check also using has_perm inside is_shown like:
class CustomMenuItem(MenuItem):
def is_shown(self, request):
return (
request.user.has_perm('wagtailsearchpromotions.add_searchpromotion') or
request.user.has_perm('wagtailsearchpromotions.change_searchpromotion') or
request.user.has_perm('wagtailsearchpromotions.delete_searchpromotion')
)
For more details visit the source code here and doc here.

Related

React Tabulator - How to display table horizontally

I'm using react-tabulator for a component: http://tabulator.info/docs/4.0/frameworks
I have put the component on the page in my app but am struggling to do anything with the styling. Right now, the component just displays everything vertically and looks really bad:
I want to display this horizontally in something that looks like a normal tabular format. I would also like to change column width. I've found limited documentation examples. Someone did ask a similar question and in this StackOverflow thread: How to style react-tabulator table? but I've not been able to edit the styles.css stylesheet to do anything useful.
Here is my component code:
import React from 'react'
import { ReactTabulator } from 'react-tabulator'
import 'react-tabulator/lib/styles.css';
const TabularData = (props) => {
const dataArray = []
//gets just first streelights record
for (const [i, v] of props.streetlights.features.entries()) {
if (i < 1) {
dataArray.push(v.properties); // properties of each item is what contains the info about each streetlight
}
}
let columns = [
{title:"WORKLOCATI", field:"WORKLOCATI"},
{title:"WORKREQUES", field:"WORKREQUES"},
{title:"WORK_EFFEC", field:"WORK_EFFEC"},
{title:"WORK_REQUE", field:"WORK_REQUE"},
]
return (
<ReactTabulator
columns={columns}
layout={"fitData"}
data={dataArray}
/>
)
}
export default TabularData
The css in react-tabulator/lib/styles.css is just the most base-level css.
Try importing one of the pre-built themes:
import "react-tabulator/css/bootstrap/tabulator_bootstrap.min.css";
There are a whole bunch of them in the css folder, and you can use them as a basis for creating your own.
Minimum working example here.
To get the right styling you will also have to import tabulator.min.css in your module, which is the theme, according to here.
Your imports should look like this:
import { ReactTabulator } from 'react-tabulator'
import 'react-tabulator/lib/styles.css';
import 'react-tabulator/lib/css/tabulator.min.css'; // theme
Without it, it looks like the image you posted:
With it, it looks like this:
In the folder node_modules/react-tabulator/css you can find more themes.

How to change number of rows in a <textarea> rendered by Wagtail's form builder

I've created a form using Wagtail's form builder that includes a Multi-line text item. When the <textarea> element is rendered, it renders with rows="10". I need to have fewer rows but don't see anything in the documentation about how to do this.
Quick Solution
Create a class that extends the FormBuilder from wagtail.contrib.forms.forms
In this CustomFormBuilder class override the method create_multiline_field
This method should return a Django Form Field
By default the widget used is Textarea and the default html attrs are cols: 40 & rows: 10
In the overridden method, pas in any custom attrs you need e.g. attrs = {'cols': '20', 'rows': '20'}
Finally, ensure that your FormPage has the form_builder set to the CustomFormBuilder class.
Full code snippet below, this will make ALL multi-line form fields the same
from django.db import models
import django.forms
from modelcluster.fields import ParentalKey
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtail.admin.edit_handlers import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel
class FormField(AbstractFormField):
page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
class CustomFormBuilder(FormBuilder):
def create_multiline_field(self, field, options):
attrs = {'cols': '20', 'rows': '20'} # default attrs = {'cols': '40', 'rows': '10'}
return django.forms.CharField(widget=django.forms.Textarea(attrs=attrs), **options)
class FormPage(AbstractEmailForm):
form_builder = CustomFormBuilder # added - allows us to override the default FormBuilder
content_panels = AbstractEmailForm.content_panels + [
#... InlinePanel etc
]
More Generic Solution
Building on the code above, we have to options, make the widget attributes customisable per field or per page, all have their pros and cons but lets go with each form field can customise their own attributes.
Below we have added a new StreamField to the FormField that lets us add any arbitrary key/value pairs we can use as HTML attributes.
The form page editing is not affected too much as the new 2.7 StreamField design is quite minimal.
We then need to read this data for the field in our overridden create_multiline_field method.
Note: the example below ONLY passes the attributes to a multiline field, you would need to override EACH form field method if you wanted to pass attributes into each of their widgets.
from django.db import models
import django.forms
from modelcluster.fields import ParentalKey
from wagtail.core import blocks
from wagtail.core.fields import StreamField
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtail.admin.edit_handlers import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel, StreamFieldPanel
class FormField(AbstractFormField):
attributes = StreamField([
('attributes', blocks.StructBlock([
('name', blocks.CharBlock()),
('value', blocks.CharBlock()),
])),
], blank=True)
page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
panels = AbstractFormField.panels + [
StreamFieldPanel('attributes'),
]
class CustomFormBuilder(FormBuilder):
def create_multiline_field(self, field, options):
# note - to accept attrs - ALL form field methods will need to be updated
attributes = field.attributes
attributes_data = attributes.get_prep_value() # convert the streamfield value into a Python data structure
# outputs [{'value': {'value': '5', 'name': 'rows'}, 'id': '6cb7d669-626c-47c0-bcac-5d982e5d9209', 'type': 'attributes'}]
keys = [_.get('value').get('name') for _ in attributes_data]
values = [_.get('value').get('value') for _ in attributes_data]
attrs = dict(zip(keys, values))
return django.forms.CharField(widget=django.forms.Textarea(attrs=attrs), **options)
class FormPage(AbstractEmailForm):
form_builder = CustomFormBuilder # added - allows us to override the default FormBuilder
content_panels = AbstractEmailForm.content_panels + [
#... InlinePanel etc
]

React decorator with dynamic param

I have a simple decorator and a react native component that is decorated like this:
#withData('apple')
class Screen extends Component {
}
I need to pass the parameter to #widhData dynamically. Based on the screen user has selected. But outside class I don't have access to props and so on.
How can pass dynamic parameters to the decorator based on the param passed on navigation ?
Thanks.

Rollup and Post CSS - Auto-prefixing React className attributes

I'm using Rollup + React + Post CSS to build a component library. I'm looking for a way to autoprefix class names so that they will not conflict with styles in the project using the library.
I have already added this plugin to automate adding the 'prefix-' to every class name in the CSS:
Post CSS Prefixer
However, this does not modify the JavaScript (JSX), so the React components are still using the unnamed classes as className attributes.
Is there a way to use Rollup to automatically modify className attributes to include the same prefix specified in the CSS?
Note that I'm not looking for a fully modular solution such as CSS Modules, as I want the same 'prefix-' across every component inside the library.
You can't use static classNames to use this feature. To use it you need import style as object and assign it as object also.
import React from "react";
import style from "style.css";
class DivMyStyle extends React.Component {
render() {
return <div className={style.myStyle} />
}
}

catch keydown events in inner components using react-keydown

I'm using react-keydown library for adding keyboard shortcuts to my application, but can't make it work in inner dialogs components. The dialogs are not always visible, but I expect the keys to work when they are visible.
I'm getting to event_handlers.js._onKeyDown(event) method, but with missing value: fn = undefined, whereas fn should contain the decorated function.
My components looks like:
<Container>
<MyDialog>
<material-ui-dialog/>
</MyDialog>
</Container>
Container.js:
import keydown from 'react-keydown'
class Container extends Component {
#keydown('enter')
someMethod1(){
// working
}
render() {
return (
<div>
<MyDialog/>
</div>
)
}
}
MyDialog.js:
import keydown, {keydownScoped} from 'react-keydown'
#keydown('enter')
class MyDialog extends Component {
#keydownScoped('enter')
someMethod3(){
// not working
}
}
Based on your description in the comments, the issue appears to be that your Dialog components mount and then lose focus so any keybindings inside them will not receive the keystrokes. You have a couple of options:
1) Expand the scope of the keybinding by decorating a component that is an ancestor of your Dialog components and won't lose focus. In an extreme case this could be the root component of your app. Then decorate the desired Dialog component method with keydownScoped. Inside that method examine the props to make sure the current dialog is the active one.
2) Programmatically activate your Dialog keybindings along the lines of this https://github.com/glortho/react-keydown/issues/28.
Hope that helps!

Resources