Commit 37d14937 authored by John Kirkwood's avatar John Kirkwood

Completed MDCSelect #1; refactored Factory (away from Widget

mixin); added redirect to UpdateView #2
parent 77dad952
Pipeline #1248 passed with stage
in 43 seconds
......@@ -75,16 +75,14 @@ def Date(props=[], children=[]):
def Select(props=[], children=[]):
required = get_prop(props, "required")
if required is not None:
# find first option and disable it if it no value
# find first option and disable it if it has no value
try:
props_option = children[0]["props"]
option_value = get_prop(props_option, "value")
if (option_value is None
or option_value["value"] == ""):
props_option.extend([
cp("disabled", True),
cp("selected", True),
])
props_option.append(
cp("disabled", True))
except (Exception, ) as e:
pass
return ce('select', props, children)
......@@ -94,6 +92,10 @@ def Option(props=[], children=[]):
return ce("option", props, children)
def Optgroup(props=[], children=[]):
return ce("optgroup", props, children)
# def Label(name):
# def c(context):
# props = [
......
from django.middleware.csrf import get_token
from ..components import Input
from ..pyreact import ce, cp, get_prop
def Csrf(props=[], children=[]):
"""Return the CSRF token.
Not possible as the request is not available at this point.
"""
props.extend([
cp("type", "hidden"),
cp("name", "csrfmiddlewaretoken"),
cp("value", "REQUEST NOT AVAILABLE IN CONTEXT"),
# cp("value", get_token(request)),
])
return Input(props, children)
......@@ -4,13 +4,13 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
# from chp import chp
from chp.components import *
from chp.components import * #noqa
from chp.pyreact import (
context_middleware, inject_ids, render_element
)
from chp.store import (create_store, Inject_ast_into_DOM, render_app)
from chp.mdc.components import *
# from chp.django.components import * #noqa
from chp.mdc.components import * # noqa
from chp.mdc.django.factory import Factory as MdcField
from .models import Post
......@@ -31,48 +31,24 @@ class PostForm(forms.ModelForm):
def render(self, *args, **kwargs):
form = Form([
cp('action', reverse('blog:post_create')),
cp('method', "POST"),
],
[
form = (
# Form([
# cp('id', "form-chp"),
# ],
# [
# Csrf(),
Flex([],
[
MdcField.render(self["checkbox"]),
MdcField.render(self["text"]),
MdcField.render(self["date"]),
SelectField([
# cp("required", "required"),
cp("name", "foreignkey"),
cp("id", "id_foreignkey"),
cp("required", True),
], [
Option([
cp("value", ""),
]
),
Option([
cp("value", "grains"),
],
"Bread, Cereal, Rice, and Pasta"
),
Option([
cp("value", "vegetables"),
],
"Vegetables"
),
Option([
cp("value", "fruit"),
],
"Fruit"
),
],
{"label": "Pick a Food Group",
}
)
MdcField.render(self["media"]),
MdcField.render(self["foreignkey"]),
],
),
])
)
# ,
# ])
)
return form
......
# Generated by Django 2.1 on 2018-09-04 17:58
# Generated by Django 2.1 on 2018-11-07 13:01
from django.conf import settings
from django.db import migrations, models
......@@ -21,6 +21,7 @@ class Migration(migrations.Migration):
('checkbox', models.BooleanField()),
('text', models.CharField(max_length=200)),
('date', models.DateField()),
('media', models.CharField(choices=[('Audio', (('vinyl', 'Vinyl'), ('cd', 'CD'), ('mp3', 'MP3'))), ('Video', (('vhs', 'VHS Tape'), ('dvd', 'DVD'), ('blu-ray', 'Blu-ray')))], max_length=12)),
('foreignkey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
......
......@@ -2,9 +2,32 @@ from django.db import models
class Post(models.Model):
VINYL = 'vinyl'
CD = 'cd'
MP3 = 'mp3'
VHS = 'vhs'
DVD = 'dvd'
BLURAY = 'blu-ray'
MEDIA_CHOICES = (
('Audio', (
(VINYL, 'Vinyl'),
(CD, 'CD'),
(MP3, 'MP3')
)
),
('Video', (
(VHS, 'VHS tape'),
(DVD, 'DVD'),
(BLURAY, 'Blu-ray')
)
),
)
checkbox = models.BooleanField()
text = models.CharField(max_length=200)
date = models.DateField()
media = models.CharField(max_length=12,
choices=MEDIA_CHOICES)
foreignkey = models.ForeignKey('auth.User', models.CASCADE)
class Meta:
......@@ -12,3 +35,8 @@ class Post(models.Model):
def __str__(self):
return self.text
def get_absolute_url(self):
from django.urls import reverse
return reverse("blog:post_update",
kwargs={'pk': self.pk})
......@@ -5,17 +5,28 @@
<script type="text/javascript" src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
</head>
<body>
<form action="{% url 'blog:post_create' %}" method="post">
{% csrf_token %}
<form id="form-django" method="post">
{% csrf_token %}
{{ form }}
{{ form }}
<div class="mdc-button">
<button form="form-django" type="submit">Submit</button>
</div>
</form>
{{ form.render }}
<form action="{% url 'blog:post_create' %}" method="post">
{% csrf_token %}
<form id="form-chp" method="post">
{% csrf_token %}
{{ form.render }}
<div class="mdc-button">
<button form="form-chp" type="submit">Submit</button>
</div>
</form>
<form id="form-mdc" method="post">
{% csrf_token %}
<div class="mdc_layout_grid__cell">
<div style="display: flex;">
......@@ -66,11 +77,10 @@
</div>
</div>
</div>
<div class="mdc-button" data-mdc-auto-init="MDCButton">
<button type="submit">Submit</button>
<div class="mdc-button">
<button form="form-mdc" type="submit">Submit</button>
</div>
</form>
{# {{ form.render }} #}
<script type="text/javascript" src="{% static "output.js" %}"></script>
<script>window.mdc.autoInit();</script>
</body>
......
import pytest
import re
from chp.django.example.blog.forms import PostForm
pytestmark = pytest.mark.django_db
def test_render():
"""Regression test."""
# <form action="/blog/post/create" method="POST" class="mdc-layout-grid__cell" chp-id="[0-9]+">
# </form>
# <option value="grains" chp-id="[0-9]+">Bread, Cereal, Rice, and Pasta</option><option value="vegetables" chp-id="[0-9]+">Vegetables</option><option value="fruit" chp-id="[0-9]+">Fruit</option>
# <option value="1" chp-id="[0-9]+">johnk</option>
# <option value="2" chp-id="[0-9]+">jpic</option>
# <option value="3" chp-id="[0-9]+">vindarel</option>
regex = """
<form action="/blog/post/create" method="POST" class="mdc-layout-grid__cell" chp-id="[0-9]+">
<div style="display: flex;" chp-id="[0-9]+">
<div class="mdc-form-field mdc-form-field--align-end" data-mdc-auto-init="MDCFormField" chp-id="[0-9]+">
<div class="mdc-checkbox" data-mdc-auto-init="MDCCheckbox" chp-id="[0-9]+"><input name="checkbox" id="id_checkbox" class="mdc-checkbox__native-control" type="checkbox" chp-id="[0-9]+" />
<div class="mdc-checkbox__background" chp-id="[0-9]+"></div></div><label for="id_checkbox" chp-id="[0-9]+">This is my checkbox:</label></div>
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField" chp-id="[0-9]+"><input name="text" required id="id_text" class="mdc-text-field__input" type="text" chp-id="[0-9]+" /><label for="id_text" class="mdc-floating-label" chp-id="[0-9]+">Input Label:</label>
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField" chp-id="[0-9]+"><input name="text" maxlength="200" required id="id_text" class="mdc-text-field__input" type="text" chp-id="[0-9]+" />
<label for="id_text" class="mdc-floating-label" chp-id="[0-9]+">Input Label:</label>
<div class="mdc-line-ripple" chp-id="[0-9]+"></div></div>
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField" chp-id="[0-9]+"><input name="date" required id="id_date" class="mdc-text-field__input" type="date" chp-id="[0-9]+" />
<label for="id_date" class="mdc-floating-label" chp-id="[0-9]+">Type = date:</label>
<div class="mdc-line-ripple" chp-id="[0-9]+"></div></div>
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField" chp-id="[0-9]+"><input name="date" required id="id_date" class="mdc-text-field__input" type="date" chp-id="[0-9]+" /><label for="id_date" class="mdc-floating-label" chp-id="[0-9]+">Type = date:</label>
<div class="mdc-select" data-mdc-auto-init="MDCSelect" chp-id="[0-9]+"><select name="media" required id="id_media" class="mdc-select__native-control" chp-id="[0-9]+">
<option value="" selected disabled chp-id="[0-9]+"></option><optgroup label="Audio" chp-id="[0-9]+"><option value="vinyl" chp-id="[0-9]+">Vinyl</option><option value="cd" chp-id="[0-9]+">CD</option><option value="mp3" chp-id="[0-9]+">MP3</option></optgroup><optgroup label="Video" chp-id="[0-9]+"><option value="vhs" chp-id="[0-9]+">VHS tape</option><option value="dvd" chp-id="[0-9]+">DVD</option><option value="blu-ray" chp-id="[0-9]+">Blu-ray</option></optgroup>
</select><label for="id_media" class="mdc-floating-label" chp-id="[0-9]+">Media:</label>
<div class="mdc-line-ripple" chp-id="[0-9]+"></div></div>
<div class="mdc-select" data-mdc-auto-init="MDCSelect" chp-id="[0-9]+"><select name="foreignkey" id="id_foreignkey" required class="mdc-select__native-control" chp-id="[0-9]+"><option value="" disabled selected chp-id="[0-9]+"></option><option value="grains" chp-id="[0-9]+">Bread, Cereal, Rice, and Pasta</option><option value="vegetables" chp-id="[0-9]+">Vegetables</option><option value="fruit" chp-id="[0-9]+">Fruit</option></select><label for="id_foreignkey" class="mdc-floating-label" chp-id="[0-9]+">Pick a Food Group</label>
<div class="mdc-line-ripple" chp-id="[0-9]+"></div></div></div></form>
<div class="mdc-select" data-mdc-auto-init="MDCSelect" chp-id="[0-9]+"><select name="foreignkey" required id="id_foreignkey" class="mdc-select__native-control" chp-id="[0-9]+">
<option value="" selected disabled chp-id="[0-9]+"></option>
</select>
<label for="id_foreignkey" class="mdc-floating-label" chp-id="[0-9]+">Foreignkey:</label>
<div class="mdc-line-ripple" chp-id="[0-9]+"></div></div></div>
"""
regex = regex.replace("\n", "")
regexc = re.compile(regex)
result = PostForm().render()
assert re.match(regexc, result) is not None
def test_render_checkbox():
test_value = True
f = PostForm(initial={"checkbox": test_value})
assert "checked" in f.render()
def test_render_text():
test_value = "Initial value"
f = PostForm(initial={"text": test_value})
assert test_value in f.render()
def test_render_date():
test_value = "2018-10-03"
f = PostForm(initial={"date": test_value})
assert test_value in f.render()
def test_render_media():
test_value = "vinyl"
f = PostForm(initial={"media": test_value})
assert test_value in f.render()
# TODO: needs a fixture to create a foreign key
def test_render_foreignkey():
test_value = ""
f = PostForm(initial={"foreignkey": test_value})
assert test_value in f.render()
......@@ -5,5 +5,14 @@ from . import views
app_name = 'blog'
urlpatterns = [
path('post/create', views.PostCreateView.as_view(), name='post_create'),
path('post/create',
views.PostCreateView.as_view(), name='post_create'),
path('post/<int:pk>/',
views.PostUpdateView.as_view(), name='post_update'),
# path('post/list',
# views.PostListView.as_view(), name='post_list'),
# path('post/detail/<int:pk>/',
# views.PostDetailView.as_view(), name='post_detail'),
# path('post/delete/<int:pk>/',
# views.PostDeleteView.as_view(), name='post_delete'),
]
from django.urls.base import reverse_lazy
from django.views import generic
from .forms import PostForm
......@@ -10,7 +11,32 @@ class PostCreateView(generic.CreateView):
def get_initial(self):
initial = super(PostCreateView, self).get_initial()
initial.update({'checkbox': True,
'text': 'Initial value',
})
initial.update({
'checkbox': True,
'text': 'Initial value',
'media': Post.VHS,
})
return initial
class PostUpdateView(generic.UpdateView):
form_class = PostForm
model = Post
"""
class PostListView(generic.ListView):
model = Post
class PostDetailView(generic.DetailView):
form_class = PostForm
model = Post
# template_name_suffix = "_form"
class PostDeleteView(generic.DeleteView):
form_class = PostForm
model = Post
success_url = reverse_lazy("post_list")
"""
......@@ -18,17 +18,18 @@ INSTALLED_APPS = [
'crudlfap',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'db.sqlite3',
}
}
STATIC_URL = '/static/'
BASE_DIR = os.path.dirname(__file__)
STATICFILES_DIRS = [
BASE_DIR,
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
......
......@@ -4,7 +4,9 @@ from django.urls import include, path
urlpatterns = [
path('', generic.RedirectView.as_view(url='/todos')),
path('blog/', include('chp.django.example.blog.urls', namespace='blog')),
path('todos/', include('chp.django.example.todos.urls')),
path('blog/', include('chp.django.example.blog.urls',
namespace='blog')),
path('todos/', include('chp.django.example.todos.urls',
namespace='todos')),
path('admin/', admin.site.urls),
]
......@@ -70,7 +70,7 @@ def FormField(children=[]):
"""Wrap an element in an MDC FormField.
Only required for checkbox and radio button fields
"""
"""
props = [
cp("class", "mdc-form-field mdc-form-field--align-end"),
cp("data-mdc-auto-init", "MDCFormField"),
......@@ -102,10 +102,11 @@ def Text(props=[], children=[]):
)
return chp.Text(props, children)
def TextField(props=[], children=[], context={}):
"""Wrap a text input element with a label (from context) in an MDC Field.
If a label is provided, find a "for" field id from context or else the
If a label is provided, find a "for" field id from context or else the
input element id."""
children_field = [
Text(props, children)
......@@ -138,7 +139,7 @@ def Date(props=[], children=[]):
def DateField(props=[], children=[], context={}):
"""Wrap a date input element with a label (from context) in an MDC Field.
If a label is provided, find a "for" field id from context or else the
If a label is provided, find a "for" field id from context or else the
input element id."""
children_field = [
Date(props, children)
......@@ -163,7 +164,7 @@ def DateField(props=[], children=[], context={}):
def Checkbox(props=[], children=[], context={}):
"""Wrap a checkbox input element with a default background
"""Wrap a checkbox input element with a default background
in an MDC Field."""
context.update({"type": "checkbox"})
props.append(
......@@ -179,10 +180,10 @@ def Checkbox(props=[], children=[], context={}):
def CheckboxField(props=[], children=[], context={}):
"""Wrap a checkbox MDC field with a label (from context)
"""Wrap a checkbox MDC field with a label (from context)
in an MDC FormField.
If a label is provided, find a "for" field id from context or else the
If a label is provided, find a "for" field id from context or else the
input element id."""
ast_checkbox = Checkbox(props, children, context)
children_formfield = [ast_checkbox]
......@@ -236,9 +237,9 @@ def Select(props=[], children=[]):
def SelectField(props=[], children=[], context={}):
"""Wrap a select element with a label (from context) in an MDC Field.
"""Wrap a Select element with a label (from context) in an MDC Field.
If a label is provided, find a "for" field id from context or else the
If a label is provided, find a "for" field id from context or else the
select element id."""
context.update({"type": "select"})
ast = Select(props, children)
......
......@@ -2,7 +2,6 @@ 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.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
import django.forms.widgets as Widgets
......@@ -26,117 +25,129 @@ 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
if isinstance(contents, Promise):
contents = conditional_escape(contents)
return contents
@staticmethod
def render(field):
"""Introspect the field and call an MdcWidget render method."""
def render(field, widget=None, attrs=None, only_initial=False):
"""Introspect the field and call an MDC render method."""
if isinstance(field, BoundField):
widget_type = field.field.widget.__class__.__name__
mdc_widget = getattr(
MdcWidgets, f"Mdc{widget_type}")
label = Factory.get_label(field)
return field.as_widget(mdc_widget(label=label))
mdc_render = getattr(
Factory, f"mdc_{widget_type.lower()}", None)
if mdc_render is not None:
# code from from boundfield.as_widget()
widget = field.field.widget
if field.field.localize:
widget.is_localized = True
attrs = attrs or {}
attrs = field.build_widget_attrs(attrs, widget)
if field.auto_id and 'id' not in widget.attrs:
attrs.setdefault('id',
field.html_initial_id
if only_initial else field.auto_id)
context = widget.get_context(
name=(field.html_initial_name
if only_initial else field.html_name),
value=field.value(),
attrs=attrs
)
label = Factory.get_label(field)
for_id = widget.attrs.get('id') or field.auto_id
context.update(
{'label': label,
'for': widget.id_for_label(for_id),
'type':
context['widget'].get('type',
field.field.widget.input_type),
})
return mdc_render(context)
  • Maybe swap the if ?

    if mdc_render is None:
        raise NotImplementedError
    widget = field.field.widget
    ...
    return mdc_render(context)
    Edited by
  • I agree - that's more explicit and easier to read/debug.

Please register or sign in to reply
raise NotImplementedError
class ChpWidgetMixin:
chp_widget = None
def __init__(self, attrs=None, **kwargs):
"""Capture the field label in the widget."""
self.label = kwargs.pop("label", None)
if attrs is not None:
attrs = attrs.copy()
super().__init__(attrs)
def _add_label(self, context):
"""Add the field label to the widget context."""
label = self.label
# cast any lazy translation strings
if isinstance(label, Promise):
label = conditional_escape(label)
context['widget'].update(
{'label': label,
'id_for_label':
self.id_for_label(context['widget']['attrs']['id'])
})
return context
def get_mdc_context(self, context):
"""Update the context for MDC components."""
context = self._add_label(context)
context.update({
"type": context['widget']['type'],
"label": context['widget']['label'],
"for": context['widget']['id_for_label'],
})
return context
def _render(self, template_name, context, renderer=None):
"""Render the widget as a CHP MDC AST."""
# prepare the field label
context = self.get_mdc_context(context)
return self._mdc_render(context)
def _mdc_render(self, context):
"""Override this method for MDC widget rendering."""
raise NotImplementedError
class MdcWidgets:
class MdcCheckboxInput(ChpWidgetMixin, Widgets.CheckboxInput):
@staticmethod
def mdc_checkboxinput(context):
mdc_type = "checkbox"
def _mdc_render(self, context):
props, children = [], []
props.append(
cp("name", context['widget']['name']))
props, children = [], []
props.append(
cp("name", context['widget']['name']))
if context["widget"]["value"] is not None:
cp("value", context["widget"]["value"])
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
return mdc.CheckboxField(props, children, context)
return mdc.CheckboxField(props, children, context)
class MdcTextInput(ChpWidgetMixin, Widgets.TextInput):
@staticmethod
def mdc_textinput(context):
mdc_type = "text"
def _mdc_render(self, context):
props, children = [], []
props, children = [], []
props.append(
cp("name", context['widget']['name']))
if context["widget"]["value"] is not None:
props.append(
cp("name", context['widget']['name']))
cp("value", context["widget"]["value"]))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
return mdc.TextField(props, children, context)
return mdc.TextField(props, children, context)
class MdcDateInput(ChpWidgetMixin, Widgets.DateInput):
@staticmethod
def mdc_dateinput(context):
mdc_type = "date"
def _mdc_render(self, context):
props, children = [], []
props, children = [], []
props.append(
cp("name", context['widget']['name']))
if context["widget"]["value"] is not None:
props.append(
cp("name", context['widget']['name']))
cp("value", context["widget"]["value"]))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
return mdc.DateField(props, children, context)
return mdc.DateField(props, children, context)
class MdcSelect(ChpWidgetMixin, Widgets.Select):
@staticmethod
def mdc_select(context):
mdc_type = "select"
def _mdc_render(self, context):
props, children = [], []
props.append(
cp("name", context['widget']['name']))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
# TODO: build children list of options from queryset.
# TODO: create optgroups if required.
return mdc.SelectField(props, children, context)
props, children = [], []
props.append(
cp("name", context['widget']['name']))
for attr, value in context['widget']['attrs'].items():
props.append(cp(attr, value))
# Implementation of
# django/forms/templates/django/forms/widgets/select.html
for group_name, group_choices, group_index in \
context["widget"]["optgroups"]:
props_group, children_group = [], []
if group_name:
props_group = [cp("label", group_name)]
for option in group_choices:
props_option = []
option_label = option["label"]
if option["value"] == "":
option_label = ""
props_option.append(
cp("value", str(option["value"])))
for attr, value in option['attrs'].items():
props_option.append(cp(attr, value))
children_group.append(
chp.Option(props_option, option_label))
if group_name:
children.append(
chp.Optgroup(props_group, children_group))
else:
children.extend(children_group)
Please register or sign in to reply
return mdc.SelectField(props, children, context)
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