open-widget-framework

an open django framework for widget customization and management


Keywords
django, react, widgets
License
MIT
Install
pip install open-widget-framework==0.2.14

Documentation

Build Status codecov

open-widget-framework

A django + react library for managing user-configurable widgets

This repository contains two packages (a pip package and a npm module).

Installation

pip install the open_widget_framework django package:

pip install open-widget-framework

npm install the @zagaran/open-widget-framework npm module:

npm install @zagaran/open-widget-framework

Configuration

Django

To configure the django app, add "open_widget_framework" to the INSTALLED_APPS in your django settings.py

If you have self-defined widget classes, they can be added to settings as

WIDGET_CLASSES = [
    'import.path.to.WidgetClass',
    ...
]

You can define a custom authentication and permission classes as well:

WIDGET_FRAMEWORK_AUTHENTICATION_CLASSES = (MyAuthenticationClass,)
WIDGET_FRAMEWORK_PERMISSION_CLASSES = (MyPermissionClass,)
WIDGET_LIST_EDIT_PERMISSIONS = ['my-permission-class']

The authentication and permission classes control who can view which widget-lists while the WIDGET_LIST_EDIT_PERMISSIONS is a list of object level permissions that a user must have to make changes to a widget list. By default these are all set to None so that anyone can view and change anything, but an easy way to restrict widget edit power would be to change WIDGET_LSIT_EDIT_PERMISSIONS to ['change_widgetlist'], which would allow only users with the permission to change a widget list change that list. That permissions can be added with

user.add_obj_perm('change_widgetlist', WidgetList.objects.get(id=id_of_widget_list))

React

To include a widget list on the page, simply import the widget list component:

import WidgetList from '@zagaran/open-widget-framework/es/widget-list'

and then include the component in your app, specifying which widget list to include by its id. Make sure that you have a widget list in the database before you do so.

<WidgetList widgetListId={id}/>

Configuring a widget list

You may want ot customize your widget list further. There are a number of settings that can be configured. The easy way to override the default settings is with the configureWidgetFrameworkSettings function from config.js:

import configureWidgetFrameworkSettings from '@zagaran/open-widget-framework/es/config'

to override the defaultSettings, simply pass your userSettings in as props on the widgetList:

const mySettings = {
  mySetting: mySettingValue,
  ...
}

<WidgetList widgetListId={id} ...mySettings/>

The settings that you can currently customize are:

disableWidgetFramework: False

If set to true, WidgetLists will not render on any page

renderers: []

An object that maps names of custom renderers to their corresponding react components. The name should match the react_renderer field on the widget class defined in your custom classes. See the section below on react renderers

fetchData: _defaultFetchJsonData

A custom fetch function that takes a url and an object of options similar to fetch. Use this to handle csrf validation and json loading. The default fetch wrapper looks for a csrfToken on the window and handles deserializing json

errorHandler: console.error

A function that takes an error message and does something with it. Use this to define your own error handling. Defaults to console.error

loader: _defaultLoader

A component that displays when data is being fetched.

defaultRenderer: _defaultRenderer

This setting defines what renderer is used in the default case. Note that unless otherwise specified in the class, widget's will only have two props available to the renderer: title and hHtml

Wrappers

ListWrapper: _defaultListWrapper

FormWrapper: _defaultFormWrapper

WidgetWrapper: _defaultWidgetWrapper

These three settings are react components that wrap the various types of components included in this package. This allows for customization of each individual part of the framework. See the section later about wrappers.

wrapperProps

listWrapperProps: null

formormWrapperProps: null

widgetWrapperProps: null

These three settings are prop objects that are passed to their respective wrappers. Use these to further customize your wrapper with information, components, and functions from your app

Creating your own widgets

Defining a widget class

When defining a widget class, simply extend WidgetClassBase and define ReactFields from react_fields.py on it. These fields allow you to configure props for the widget-form (like placeholder in the below example).

All widget classes must implement the render function and return either a string of html (WidgetSerializer runs format_html on it) or a dictionary (as in the below example) which will be used as props on the renderer. The default renderer provided with the app only handles title and html props, but you can change the default renderer.

from open_widget_framework.widget_class_base import WidgetClassBase
from open_widget_framework.react_fields improt ReactCharField, ReactChoice


class MyWidget(WidgetClassBase):
    name = 'MyWidgetClass'
    react_renderer = 'myRenderer'
    myTextField = ReactCharField(props={'placeholder': "This is my widget's text field!"})
    myClassField = ReactCharField(props={'placeholder': "This is my widget's class field!"})
    myChoiceField = ReactChoiceField([], props={'placeholder': "Choose one!"})

    def render(self):
        # render can return a string of html or a dictionary of props to set on a custom renderer
        return self.data
        
    def pre_configure(self):
        # pre_configure allows us to dynamically configure a field, in this case to load options from the db
        self.fields['myChoiceField'].choices = [user.username for user in User.objects.all()]
        
    def post_configure(self):
    # post_configure allows us to manipulate data right before it enters the database
        escaped_data = {
            myTextField: format_html(self.initial_data['myTextField'])
            myClassField: format_html(self.initial_data['myClassField'])
            myChoiceField: format_html(self.initial_data['myChoiceField'])
        return escaped_data

Add you widget class in your settings.py:

WIDGET_FRAMEWORK = {
    WIDGET_CLASSES: [
        'path.to.myWidget'
    ]
}

Defining a renderer

A custom renderer is just a React component that will receive the rendered props, either title and html or a custom set of props defined in the widget class render function.

class myRenderer extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.title}</h3>
        <div className={this.props.myClassField}>{this.props.myTextField}</div>
        <div>You chose {this.props.myChoiceField}</div>
      </div>
  }
}

Add your renderer as a prop in WidgetList:

<WidgetList widgetListId={id} renderers={myRenderer: myRenderer}/>

Database

The open_widget_framework django package makes use of PostgreSQL>=9.4 and django.contrib.postgres.fields.JSONField to store JSON content.

Running Tests

For the open_widget_framework django package, the following will use the Django's built-in test runner to discover tests:

python runtests.py

To obtain a coverage report (include omit flag as needed):

coverage run runtests.py; coverage report -m [--omit="*/.virtualenv/*"]