Skip to content

Commit

Permalink
Merge pull request #109 from basxsoftwareassociation/refactor/forms-api
Browse files Browse the repository at this point in the history
Refactor/forms api
  • Loading branch information
saemideluxe authored Dec 24, 2021
2 parents e7fa596 + af38306 commit 7a3ecd2
Show file tree
Hide file tree
Showing 45 changed files with 1,904 additions and 1,413 deletions.
29 changes: 14 additions & 15 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,29 @@ jobs:
include:
# django 3.1
- python-version: "3.7"
django-version: "3.1"
django-version: "Django>=3.1,<3.2"
- python-version: "3.8"
django-version: "3.1"
django-version: "Django>=3.1,<3.2"
- python-version: "3.9"
django-version: "3.1"
django-version: "Django>=3.1,<3.2"
- python-version: "3.10"
django-version: "3.1"
django-version: "Django>=3.1,<3.2"
# django 3.2
- python-version: "3.7"
django-version: "3.2"
django-version: "Django>=3.2,<4.0"
- python-version: "3.8"
django-version: "3.2"
django-version: "Django>=3.2,<4.0"
- python-version: "3.9"
django-version: "3.2"
django-version: "Django>=3.2,<4.0"
- python-version: "3.10"
django-version: "3.2"
# django 3.2
django-version: "Django>=3.2,<4.0"
# django 4.0
- python-version: "3.8"
django-version: "4.0"
django-version: "Django>=4.0"
- python-version: "3.9"
django-version: "4.0"
django-version: "Django>=4.0"
- python-version: "3.10"
django-version: "4.0"
django-version: "Django>=4.0"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -44,7 +44,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install Django==${{ matrix.django-version }}
pip install "${{ matrix.django-version }}"
pip install flake8 flake8-black pytest pytest-custom_exit_code black isort bandit safety
pip install -e .[testing]
Expand All @@ -61,8 +61,7 @@ jobs:
run: bandit -c .bandit -r .

- name: Run Safety
# there is no allow-failure yet. see https://github.com/actions/toolkit/issues/399
run: safety check || true
run: safety check

- name: Test with pytest
run: ./manage.py test
93 changes: 47 additions & 46 deletions bread/contrib/reports/fields/queryfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.core.exceptions import FieldDoesNotExist, ValidationError
from django.db import models
from django.urls import reverse
from django.utils.html import mark_safe
from django.utils.translation import gettext_lazy as _
from djangoql.exceptions import DjangoQLError
from djangoql.queryset import apply_search
Expand Down Expand Up @@ -74,16 +73,6 @@ def value_to_string(self, obj):
def get_db_prep_value(self, value, connection, prepared=False):
return self.get_clean_value(value)

def formfield(self, **kwargs):
ret = super().formfield(**kwargs)
ret.layout = QuerySetFormWidget
ret.layout_kwargs = {
"modelfieldname": self.modelfieldname,
"rows": 1,
"name": self.name,
}
return ret

def check(self, **kwargs):
return [
*super().check(**kwargs),
Expand Down Expand Up @@ -115,45 +104,57 @@ def validate(self, value, model_instance):
)


class QuerySetFormWidget(layout.text_area.TextArea):
def __init__(self, *args, modelfieldname, name, **kwargs):
super().__init__(*args, **kwargs)
self.name = name
self.boundfield = kwargs.get("boundfield", None)
self.model = None
if "boundfield" in kwargs:
self.model = getattr(
self.boundfield.form.instance, modelfieldname
).model_class()

def render(self, context):
if self.model:
class QuerysetFormWidget(layout.forms.widgets.Textarea):
django_widget = None

def __init__(
self,
label=None,
help_text=None,
errors=None,
inputelement_attrs=None,
boundfield=None,
**attributes
):
super().__init__(
label=label,
help_text=help_text,
errors=errors,
inputelement_attrs=inputelement_attrs,
boundfield=boundfield,
**attributes
)

def introspections(context):
return json.dumps(
DjangoQLSchemaSerializer().serialize(
DjangoQLSchema(
hg.resolve_lazy(boundfield, context).value().queryset.model
)
)
)

if boundfield:
self.append(
hg.SCRIPT(
mark_safe(
hg.format(
"""
document.addEventListener("DOMContentLoaded", () => DjangoQL.DOMReady(function () {
new DjangoQL({
introspections: %s,
selector: 'textarea[name=%s]',
syntaxHelp: '%s',
autoResize: false
});
}));
"""
% (
json.dumps(
DjangoQLSchemaSerializer().serialize(
DjangoQLSchema(self.model)
)
),
self.name,
reverse("reporthelp"),
)
)
),
document.addEventListener("DOMContentLoaded", () => DjangoQL.DOMReady(function () {{
new DjangoQL({{
introspections: {},
selector: 'textarea[name={}]',
syntaxHelp: '{}',
autoResize: false
}});
}}));
""",
hg.F(introspections),
inputelement_attrs.get("name"),
reverse("reporthelp"),
autoescape=False,
),
)
)
return super().render(context)


def parsequeryexpression(basequeryset, expression):
Expand Down
59 changes: 32 additions & 27 deletions bread/contrib/reports/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from bread.utils.links import ModelHref

from ...layout.components.datatable import DataTableColumn, sortingname_for_column
from .fields.queryfield import QuerysetFormWidget
from .models import Report


Expand All @@ -29,7 +30,7 @@ def get_layout(self):
)
column_helper = get_attribute_description_modal(modelclass)

F = _layout.form.FormField
F = _layout.forms.FormField
ret = hg.BaseElement(
hg.LINK(
rel="stylesheet",
Expand All @@ -38,34 +39,38 @@ def get_layout(self):
),
hg.SCRIPT(src=staticfiles_storage.url("djangoql/js/completion.js")),
hg.H3(self.object),
_layout.form.Form.wrap_with_form(
_layout.forms.Form(
hg.C("form"),
hg.BaseElement(
hg.DIV(
_("Base model"),
": ",
hg.C("object.model"),
style="margin: 2rem 0 2rem 0",
),
F("name"),
F("filter"),
F("custom_queryset"),
_layout.form.FormsetField.as_datatable(
"columns",
["column", "name"],
formsetfield_kwargs={
"extra": 1,
"can_order": True,
},
),
_layout.button.Button(
_("Help"),
buttontype="ghost",
style="margin-top: 1rem",
**column_helper.openerattributes,
),
column_helper,
hg.DIV(
_("Base model"),
": ",
hg.C("object.model"),
style="margin: 2rem 0 2rem 0",
),
F("name"),
F(
"filter",
widgetclass=QuerysetFormWidget,
inputelement_attrs={"rows": 1},
style="width: 100%",
),
F("custom_queryset"),
_layout.forms.FormsetField.as_datatable(
"columns",
["column", "name"],
formsetfield_kwargs={
"extra": 1,
"can_order": True,
},
),
_layout.button.Button(
_("Help"),
buttontype="ghost",
style="margin-top: 1rem",
**column_helper.openerattributes,
),
layout.forms.helpers.Submit(),
column_helper,
),
hg.C("object.preview"),
)
Expand Down
12 changes: 8 additions & 4 deletions bread/contrib/workflows/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ def get_layout(self):
class WorkflowEditView(views.EditView):
def get_layout(self):
fields = hg.BaseElement(
*[layout.form.FormField(f) for f in self.object.active_fields()]
*[layout.forms.FormField(f) for f in self.object.active_fields()]
)
return hg.BaseElement(
hg.H1(self.object, style="margin-bottom: 2rem"),
hg.DIV(
hg.DIV(
layout.form.Form.wrap_with_form(hg.C("form"), fields),
layout.forms.Form(
hg.C("form"), fields, layout.forms.helpers.Submit()
),
style="padding: 1rem",
),
hg.DIV(
Expand All @@ -60,7 +62,7 @@ class WorkflowReadView(views.ReadView):
def get_layout(self):
fields = hg.BaseElement(
*[
layout.form.FormField(f)
layout.forms.FormField(f)
for f in utils.filter_fieldlist(self.model, ["__all__"], for_form=True)
]
)
Expand All @@ -77,7 +79,9 @@ def get_layout(self):
views.layoutasreadonly(
hg.DIV(
hg.DIV(
layout.form.Form.wrap_with_form(hg.C("form"), fields),
layout.forms.Form(
hg.C("form"), fields, layout.forms.helpers.Submit()
),
style="padding: 1rem",
),
hg.DIV(
Expand Down
34 changes: 14 additions & 20 deletions bread/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ def save(self, *args, **kwargs):
attribs[modelfield.name] = GenericForeignKeyField(
required=not model._meta.get_field(modelfield.fk_field).blank
)
elif modelfield.one_to_many or (
modelfield.one_to_one and not modelfield.concrete
):
# elif modelfield.one_to_many or (
# modelfield.one_to_one and not modelfield.concrete
# ):
elif isinstance(formfieldelement, _layout.forms.FormsetField):
attribs[modelfield.name] = FormsetField(
_generate_formset_class(
request=request,
Expand All @@ -142,7 +143,8 @@ def save(self, *args, **kwargs):
fields=[
f.fieldname
for f in formfieldelements
if isinstance(f, _layout.form.FormField) and f.fieldname in modelfields
if isinstance(f, _layout.forms.fields.FormFieldMarker)
and f.fieldname in modelfields
],
formfield_callback=lambda field: _formfield_callback_with_request(
field, request, model, instance, cache_querysets
Expand Down Expand Up @@ -182,7 +184,7 @@ def _generate_formset_class(

formfieldelements = _get_form_fields_from_layout(
hg.BaseElement(*formsetfieldelement)
) # make sure the _layout.form.FormsetField does not be considered recursively
) # make sure the _layout.forms.FormsetField does not be considered recursively

formclass = breadmodelform_factory(
request=request,
Expand All @@ -194,9 +196,7 @@ def _generate_formset_class(
)

base_formset_kwargs = {
"fields": [
formfieldelement.fieldname for formfieldelement in formfieldelements
],
"fields": [field.fieldname for field in formfieldelements],
"form": formclass,
"extra": 0,
"can_delete": True,
Expand Down Expand Up @@ -260,19 +260,13 @@ def _formfield_callback_with_request(field, request, model, instance, cache_quer
return ret


class FilterForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = lambda request: _layout.form.Form.from_django_form(
self, method="GET"
)


class PreferencesForm(GlobalPreferenceForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = lambda request: _layout.form.Form.from_fieldnames(
hg.C("form"), self.fields
self.layout = lambda request: _layout.forms.Form(
hg.C("form"),
*[_layout.forms.FormField(f) for f in self.fields],
_layout.forms.helpers.Submit(),
)


Expand All @@ -281,14 +275,14 @@ def _get_form_fields_from_layout(layout):

def walk(element):
# do not descend into formsets, they need to be gathered separately
if isinstance(element, _layout.form.FormsetField):
if isinstance(element, _layout.forms.FormsetField):
yield element
return
# do not descend into script tags because we keep formset-empty form templates there
if isinstance(element, hg.SCRIPT):
return
if (
isinstance(element, _layout.form.FormField)
isinstance(element, _layout.forms.fields.FormFieldMarker)
and element.fieldname not in INTERNAL_FIELDS
):
yield element
Expand Down
Loading

0 comments on commit 7a3ecd2

Please sign in to comment.