Commit 1ffce1c3 authored by John Kirkwood's avatar John Kirkwood

Set up intersphinx for Django domain.

parent a4f8e582
......@@ -9,6 +9,8 @@ __pycache__
# Django
db.sqlite3
static/
# Sphinx
_build/
# yarn
node_modules
yarn.lock
......
......@@ -12,6 +12,9 @@ def Csrf(props=[], children=[]):
django-threadlocals is only updated to Django <1.10 on PyPI.
django-tools has many features that are not needed.
Adapted django-tools CSRF middleware feature in chp.django.threadlocals.
:param list props: Key/value properties for the element.
:param list children: An AST of child elements to include.
"""
from .threadlocals import get_current_request
......
......@@ -73,7 +73,7 @@
</div>
</form>
<form id="form-mui" method="POST">
<form class="mui-form" id="form-mui" method="POST">
{% csrf_token %}
<div class="mui-container-fluid">
......@@ -88,11 +88,14 @@
</div>
<div class="mui-textfield mui-textfield--float-label">
<input id="text-input" type="text" >
<input id="text-input" type="text">
<label>Input Label</label>
</div>
<div class="mui-textfield mui-textfield--float-label">
<!--
# MUI doesn't float labels for 'date' or 'select' fields.
-->
<div class="mui-textfield">
<input id="date-input" type="date" >
<label>Type = date</label>
</div>
......@@ -113,12 +116,13 @@
<label for="select-input">Pick a Food Group</label>
</div>
</div> <!-- class="mui-row" -->
</div> <!-- class="mui-col-md-6" -->
</div> <!-- class="mui-container-fluid" -->
<div>
<button class="mui-btn mui-btn--primary" form="form-mdc" type="submit">Submit</button>
</div>
</div> <!-- class="mui-row" -->
</div> <!-- class="mui-col-md-6" -->
</div> <!-- class="mui-container-fluid" -->
</form>
<script type="text/javascript" src="{% static "output.js" %}"></script>
......
# import django.forms.widgets as Widgets
from django.forms.boundfield import BoundField
# from django.forms.fields import Field
from django.utils.functional import Promise
from django.utils.html import conditional_escape, format_html
from django.utils.translation import gettext_lazy as _
......@@ -9,16 +6,26 @@ from django.utils.translation import gettext_lazy as _
from ... import components as chp
from .. import components as mdc
# alias for chp.pyreact.create_prop()
# Alias for chp.pyreact.create_prop().
cp = chp.cp
class Factory:
""" Provide adapter methods to render Django fields and attributes using
MDC components rather than a templating engine.
"""
@staticmethod
def get_label(field):
"""Return a field label with suffix."""
# code taken from Boundfield.label_tag()
"""Return a field label with suffix.
Code adapted from ~django.forms.Boundfield.label_tag().
:param ~django.forms.BoundField field: The field being rendered.
:return: Field label (html_safe).
:rtype: str
"""
# Code adapted from ~django.forms.Boundfield.label_tag().
contents = field.label
label_suffix = (field.field.label_suffix
if field.field.label_suffix is not None
......@@ -26,16 +33,27 @@ class Factory:
if hasattr(field, "form") else ""))
if label_suffix and contents and contents[-1] not in _(':?.!'):
contents = format_html('{}{}', contents, label_suffix)
# cast any lazy translation strings
# Cast any lazy translation strings.
if isinstance(contents, Promise):
contents = conditional_escape(contents)
return contents
@staticmethod
def render(field, widget=None, attrs=None, only_initial=False):
"""Introspect the field and call an MDC render method.
Prepare the widget attrs, field label and context first.
"""Introspect the field and call an MDC render method to return an AST.
Prepare the widget attrs, field label and context then render the
field using MDC components.
Code adapted from ~django.forms.BoundField.as_widget().
:param ~django.forms.BoundField field: The field being rendered.
:param widget: A widget to override the default for the field.
:type widget: ~django.forms.Widget or None
:param attrs: Optional widget attributes.
:type attrs: dict or None
:param bool only_initial: A flag to render only initial dynamic values.
:return: An AST representing the rendered field.
:rtype: list
"""
if not isinstance(field, BoundField):
raise NotImplementedError
......@@ -46,7 +64,7 @@ class Factory:
if mdc_render is None:
raise NotImplementedError
# code from from boundfield.as_widget()
# Code adapted from ~django.forms.BoundField.as_widget().
widget = field.field.widget
if field.field.localize:
widget.is_localized = True
......@@ -77,6 +95,10 @@ class Factory:
@staticmethod
def non_field_errors(form):
"""Return an AST of any errors for the form using MDC components.
:param `~django.forms.Form` form: The form being rendered.
"""
errs = form.errors.get("__all__", form.error_class())
if errs == []:
return {}
......@@ -86,12 +108,13 @@ class Factory:
])
return mdc.ValidationText(props, [],
{"errors": errs, })
# error_msg = "<br />".join(errs)
# return chp.Para(props, error_msg)
@staticmethod
def errors(field):
"""Render a field validation message."""
"""Return an AST of any errors for the field using MDC components.
:param ~django.forms.BoundField field: The field being rendered.
"""
errs = field.form.errors.get(field.name, field.form.error_class())
if errs == []:
......@@ -102,8 +125,11 @@ class Factory:
@staticmethod
def help_text(field):
"""Render a field help_text."""
# MDC design: don't show help if there is an error to display.
"""Return an AST of the help_text for the field 1using MDC components.
:param ~django.forms.BoundField field: The field being rendered.
"""
# MDC: don't show help text if there is an error to display.
errs = field.form.errors.get(field.name, field.form.error_class())
if errs:
return {}
......@@ -111,7 +137,7 @@ class Factory:
help_text = field.help_text
if help_text == "":
return {}
# cast any lazy translation strings
# Cast any lazy translation strings.
if isinstance(help_text, Promise):
help_text = conditional_escape(help_text)
......@@ -120,8 +146,11 @@ class Factory:
@staticmethod
def mdc_checkboxinput(context):
"""Render a BooleanField/CheckBoxInput field/widget."""
# mdc_type = "checkbox"
"""Return an AST for a BooleanField/CheckBoxInput field/widget
using MDC components.
:param dict context: Additional widget attributes.
"""
props, children = [], []
props.append(
......@@ -136,8 +165,11 @@ class Factory:
@staticmethod
def mdc_textinput(context):
"""Render a CharField/TextInput field/widget."""
# mdc_type = "text"
"""Return an AST for a CharField/TextInput field/widget using
MDC components.
:param dict context: Additional widget attributes.
"""
props, children = [], []
props.append(
......@@ -153,8 +185,11 @@ class Factory:
@staticmethod
def mdc_dateinput(context):
"""Render a DateField/DateInput field/widget."""
# mdc_type = "date"
"""Return an AST for a DateField/DateInput field/widget using
MDC components.
:param dict context: Additional widget attributes.
"""
props, children = [], []
props.append(
......@@ -170,9 +205,11 @@ class Factory:
@staticmethod
def mdc_select(context):
"""Render a CharField/choices or ForeignKey/Select field/widget.
"""Return an AST for a CharField/choices or ForeignKey/Select
field/widget using MDC components.
:param dict context: Additional widget attributes.
"""
# mdc_type = "select"
props, children = [], []
props.append(
cp("name", context['widget']['name']))
......@@ -181,7 +218,7 @@ class Factory:
props.append(cp(attr, value))
# Implementation of
# django/forms/templates/django/forms/widgets/select.html
# django/forms/templates/django/forms/widgets/select.html.
for group_name, group_choices, _ in \
context["widget"]["optgroups"]:
props_group, children_group = [], []
......@@ -191,8 +228,8 @@ class Factory:
for option in group_choices:
props_option = []
option_label = option["label"]
# override any 'empty_label' as MDC floating label will be
# displayed instead.
# MDC: Override any instance of 'empty_label' as a
# floating label will be displayed instead.
if option["value"] == "":
option_label = ""
props_option.append(
......@@ -202,11 +239,11 @@ class Factory:
children_group.append(
chp.Option(props_option, option_label))
# build list of optgroups
# Build a list of optgroups:
if group_name:
children.append(
chp.Optgroup(props_group, children_group))
# or simple list of options
# Or a list of options:
else:
children.extend(children_group)
......
=========================
Composable HTML in Python
=========================
Description
===========
Very basic Python functions to generate HTML in a composable way.
Usage
=====
Very much inspired from React.
A component is any function that returns the result of a
``create_element`` call. This function that the element name,
Let us first define a Text component. We will create a text element
without any attributes:
.. code:: python
from chp import create_element, create_prop, render_element
def Text(t):
return create_element("span", [], t)
t = Text("YourLabs")
render_element(t)
# outputs => "<span >YourLabs</span>"
Let's now use this component to display text inside a link:
.. code:: python
def Link(href, children):
href = create_prop("href", href)
return create_element("a", [href], children)
l = Link("yourlabs.org", [Text("YourLabs")])
render_element(l)
# outputs => "<a href="yourlabs.org"><span >YourLabs</span></a>"
Let us now define a Menu component that will create links inside of a
``nav`` element based on some input. Let's also use the shorthand aliases to ``create_element`` and ``create_prop``: ``ce`` and ``cp`` respectively.
.. code:: python
def Menu(links=[]):
children = [] # in this case, the menu links array
for l in links:
el = Link(l["href"], [Text(l["text"])])
children.append(el)
return ce("nav", [cp("class", "menu")], children)
links = [
{
"href": "yourlabs.org",
"text": "YourLabs",
},
{
"href": "novamedia.nyc",
"text": "NovaMedia",
},
]
m = Menu(links)
render_element(m)
# outputs => <nav class="menu"><a href="yourlabs.org"><span >YourLabs</span></a><a href="novamedia.nyc"><span >NovaMedia</span></a></nav>
Pretty printed, the final output is:
.. code:: html
<nav class="menu">
<a href="yourlabs.org">
<span>YourLabs</span>
</a>
<a href="novamedia.nyc">
<span>NovaMedia</span>
</a>
</nav>
Feel free to check out the ``app.py`` and ``components.py`` files to see
how a full page can be built easily using this method. The ``app.py``
file writes the HTML output to another file called ``output.html``.
Trying it
=========
.. code:: bash
git clone git@github.com:tbinetruy/CHIP.git
cd CHIP
python app.py # write HTML to output.html file
firefox output.html
Examples
========
To run the example project included in the module:
.. code:: bash
pip install --user --editable path/to/chp[dev]
yarn install; yarn start
chp-django runserver
py.test path/to/chp
.. code:: python
import chp
def FormSchema(is_checked):
return chp.Form([
chp.Row([
chp.Input('username'),
chp.CheckboxField(is_checked),
])
])
class PostForm(forms.ModelForm):
def render(self):
is_checked = 'checked' # self.checked
return mark_safe(FormSchema(is_checked).render_element(Form))
``_html`` becomes a string containing the following HTML code:
.. code:: html
<form class="mdc-layout-grid__cell">
<div class="mdc-layout-grid__inner">
<input class="mdc-input__native-control" type="text" id="{{ id }}" value="{{ value }}" name="username"></input>
<div class="mdc-form-field">
<div class="mdc-checkbox">
<input class="mdc-checkbox__native-control" type="checkbox" id="{{ id }}" name="password"></input>
<div class="mdc-checkbox-background"></div>
<label for="{{ id }}">{{ label }}</label>
</div>
</div>
</div>
<div >
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
</form>
TODOS
=====
- Testing of the pyreact.py file. Some of the high level results were
copy pasted into ``tests.org``.
\ No newline at end of file
......@@ -41,6 +41,7 @@ release = '0.0.2'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
]
# Add any paths that contain templates here, relative to this directory.
......@@ -70,6 +71,9 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# Autodoc option: If true, the current module name will be prepended to
# all description unit titles (such as .. function::).
add_module_names = False
# -- Options for HTML output -------------------------------------------------
......@@ -176,3 +180,11 @@ epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
# Intersphinx mapping
intersphinx_mapping = {
'python': ('https://docs.python.org/3',
None),
'django': ('http://django.readthedocs.org/en/latest/',
None),
}
......@@ -9,15 +9,9 @@ Welcome to CHP's documentation!
.. toctree::
:maxdepth: 2
:caption: Contents:
.. automodule:: chp.components
:members:
.. automodule:: chp.mdc.components
:members:
.. automodule:: chp.django.components
:members:
README
reference
Indices and tables
==================
......
=============
CHP reference
=============
chp.components
==============
.. automodule:: chp.components
:members:
:undoc-members:
chp.mdc.components
==================
.. automodule:: chp.mdc.components
:members:
:undoc-members:
chp.mdc.django.factory
======================
.. automodule:: chp.mdc.django.factory
:members:
:undoc-members:
chp.django.components
=====================
.. automodule:: chp.django.components
:members:
:undoc-members:
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment