Forms for attachments work, VERY HACKISH Hardcodedness™

This commit is contained in:
Sybren A. Stüvel 2016-10-26 17:18:53 +02:00
parent 5bd2c101fe
commit 3cf71a365f
5 changed files with 144 additions and 140 deletions

View File

@ -43,7 +43,6 @@ node_type_asset = {
},
'form_schema': {
'content_type': {'visible': False},
'attachments': {'visible': False},
'order': {'visible': False},
'tags': {'visible': False},
'categories': {'visible': False}

View File

@ -4,9 +4,11 @@ import re
from bson import ObjectId
import flask
import pillarsdk
import wtforms
from pillar.api.node_types import ATTACHMENT_SLUG_REGEX
from pillar.web.utils import system_util
from pillar.web.utils.forms import build_file_select_form, CustomFormField
shortcode_re = re.compile(r'@\[(%s)\]' % ATTACHMENT_SLUG_REGEX)
log = logging.getLogger(__name__)
@ -86,3 +88,51 @@ def render_attachment_file_image(sdk_file):
variations = {var.size: var for var in sdk_file.variations}
return flask.render_template('nodes/attachments/file_image.html',
file=sdk_file, vars=variations)
def attachment_form_group_create(schema_prop):
"""Creates a wtforms.FieldList for attachments."""
file_select_form_group = _attachment_build_single_field(schema_prop)
field = wtforms.FieldList(CustomFormField(file_select_form_group), min_entries=1)
return field
def _attachment_build_single_field(schema_prop):
# Ugly hard-coded schema.
fake_schema = {
'slug': schema_prop['propertyschema'],
'oid': schema_prop['valueschema']['schema']['oid'],
}
file_select_form_group = build_file_select_form(fake_schema)
return file_select_form_group
def attachment_form_group_set_data(db_prop_value, schema_prop, field_list):
"""Populates the attachment form group with data from MongoDB."""
assert isinstance(db_prop_value, dict)
# Extra entries are caused by min_entries=1 in the form creation.
while len(field_list):
field_list.pop_entry()
for slug, att_data in sorted(db_prop_value.iteritems()):
file_select_form_group = _attachment_build_single_field(schema_prop)
subform = file_select_form_group()
# Even uglier hard-coded
subform.slug = slug
subform.oid = att_data['oid']
field_list.append_entry(subform)
def attachment_form_parse_post_data(data):
"""Returns a dict that can be stored in the node.properties.attachments."""
# Moar ugly hardcodedness.
attachments = {allprops['slug']: {'oid': allprops['oid']}
for allprops in data}
return attachments

View File

@ -19,14 +19,30 @@ from wtforms import FieldList
from wtforms.validators import DataRequired
from pillar.web.utils import system_util
from pillar.web.utils.forms import FileSelectField
from pillar.web.utils.forms import ProceduralFileSelectForm
from pillar.web.utils.forms import CustomFormField
from pillar.web.utils.forms import build_file_select_form
from . import attachments
log = logging.getLogger(__name__)
def add_form_properties(form_class, node_schema, form_schema, prefix=''):
def iter_node_properties(node_type):
"""Generator, iterates over all node properties with form schema."""
node_schema = node_type['dyn_schema'].to_dict()
form_schema = node_type['form_schema'].to_dict()
for prop_name, prop_schema in node_schema.iteritems():
prop_fschema = form_schema.get(prop_name, {})
if not prop_fschema.get('visible', True):
continue
yield prop_name, prop_schema, prop_fschema
def add_form_properties(form_class, node_type):
"""Add fields to a form based on the node and form schema provided.
:type node_schema: dict
:param node_schema: the validation schema used by Cerberus
@ -37,33 +53,16 @@ def add_form_properties(form_class, node_schema, form_schema, prefix=''):
show and hide)
"""
for prop, schema_prop in node_schema.iteritems():
form_prop = form_schema.get(prop, {})
if prop == 'items':
continue
if not form_prop.get('visible', True):
continue
prop_name = "{0}{1}".format(prefix, prop)
for prop_name, schema_prop, form_prop in iter_node_properties(node_type):
# Recursive call if detects a dict
field_type = schema_prop['type']
if field_type == 'dict':
# This works if the dictionary schema is hardcoded.
# If we define it using propertyschema and valueschema, this
# validation pattern does not work and crahses.
add_form_properties(form_class, schema_prop['schema'],
form_prop['schema'], "{0}__".format(prop_name))
continue
if field_type == 'list':
if prop == 'attachments':
# class AttachmentForm(Form):
# pass
# AttachmentForm.file = FileSelectField('file')
# AttachmentForm.size = StringField()
# AttachmentForm.slug = StringField()
field = FieldList(CustomFormField(ProceduralFileSelectForm))
elif prop == 'files':
if field_type == 'dict':
assert prop_name == 'attachments'
field = attachments.attachment_form_group_create(schema_prop)
elif field_type == 'list':
if prop_name == 'files':
schema = schema_prop['schema']['schema']
file_select_form = build_file_select_form(schema)
field = FieldList(CustomFormField(file_select_form),
@ -112,8 +111,6 @@ def get_node_form(node_type):
class ProceduralForm(Form):
pass
node_schema = node_type['dyn_schema'].to_dict()
form_prop = node_type['form_schema'].to_dict()
parent_prop = node_type['parent']
ProceduralForm.name = StringField('Name', validators=[DataRequired()])
@ -126,7 +123,7 @@ def get_node_form(node_type):
ProceduralForm.picture = FileSelectField('Picture', file_format='image')
ProceduralForm.node_type = HiddenField(default=node_type['name'])
add_form_properties(ProceduralForm, node_schema, form_prop)
add_form_properties(ProceduralForm, node_type)
return ProceduralForm()
@ -166,59 +163,44 @@ def process_node_form(form, node_id=None, node_type=None, user=None):
if form.parent.data != "":
node.parent = form.parent.data
def update_data(node_schema, form_schema, prefix=""):
for pr in node_schema:
schema_prop = node_schema[pr]
form_prop = form_schema.get(pr, {})
if pr == 'items':
continue
if 'visible' in form_prop and not form_prop['visible']:
continue
prop_name = "{0}{1}".format(prefix, pr)
if schema_prop['type'] == 'dict':
update_data(
schema_prop['schema'],
form_prop['schema'],
"{0}__".format(prop_name))
continue
data = form[prop_name].data
if schema_prop['type'] == 'dict':
if data == 'None':
continue
elif schema_prop['type'] == 'integer':
if data == '':
data = 0
else:
data = int(form[prop_name].data)
elif schema_prop['type'] == 'datetime':
data = datetime.strftime(data,
app.config['RFC1123_DATE_FORMAT'])
elif schema_prop['type'] == 'list':
if pr == 'attachments':
# data = json.loads(data)
data = [dict(field='description', files=data)]
elif pr == 'files':
# Only keep those items that actually refer to a file.
data = [file_item for file_item in data
if file_item.get('file')]
# elif pr == 'tags':
# data = [tag.strip() for tag in data.split(',')]
elif schema_prop['type'] == 'objectid':
if data == '':
# Set empty object to None so it gets removed by the
# SDK before node.update()
data = None
for prop_name, schema_prop, form_prop in iter_node_properties(node_type):
data = form[prop_name].data
if schema_prop['type'] == 'dict':
data = attachments.attachment_form_parse_post_data(data)
elif schema_prop['type'] == 'integer':
if data == '':
data = 0
else:
if pr in form:
data = form[prop_name].data
path = prop_name.split('__')
if len(path) > 1:
recursive_prop = recursive(
path, node.properties.to_dict(), data)
node.properties = recursive_prop
data = int(form[prop_name].data)
elif schema_prop['type'] == 'datetime':
data = datetime.strftime(data, current_app.config['RFC1123_DATE_FORMAT'])
elif schema_prop['type'] == 'list':
if prop_name == 'files':
# Only keep those items that actually refer to a file.
data = [file_item for file_item in data
if file_item.get('file')]
else:
node.properties[prop_name] = data
update_data(node_schema, form_schema)
log.warning('Ignoring property %s of type %s',
prop_name, schema_prop['type'])
# elif pr == 'tags':
# data = [tag.strip() for tag in data.split(',')]
elif schema_prop['type'] == 'objectid':
if data == '':
# Set empty object to None so it gets removed by the
# SDK before node.update()
data = None
else:
if prop_name in form:
data = form[prop_name].data
path = prop_name.split('__')
assert len(path) == 1
if len(path) > 1:
recursive_prop = recursive(
path, node.properties.to_dict(), data)
node.properties = recursive_prop
else:
node.properties[prop_name] = data
ok = node.update(api=api)
if not ok:
log.warning('Unable to update node: %s', node.error)

View File

@ -22,7 +22,6 @@ from wtforms import SelectMultipleField
from flask_login import login_required
from jinja2.exceptions import TemplateNotFound
import pillar.web.nodes.attachments
from pillar.web.utils import caching
from pillar.web.nodes.forms import get_node_form
from pillar.web.nodes.forms import process_node_form
@ -35,7 +34,7 @@ from pillar.web.utils.forms import ProceduralFileSelectForm
from pillar.web.utils.forms import build_file_select_form
from pillar.web import system_util
from . import finders
from . import finders, attachments
blueprint = Blueprint('nodes', __name__)
log = logging.getLogger(__name__)
@ -261,7 +260,7 @@ def _view_handler_asset(node, template_path, template_action, link_allowed):
# Treat it as normal file (zip, blend, application, etc)
asset_type = 'file'
node['description'] = pillar.web.nodes.attachments.render_attachments(node, node['description'])
node['description'] = attachments.render_attachments(node, node['description'])
template_path = os.path.join(template_path, asset_type)
@ -315,27 +314,18 @@ def edit(node_id):
"""Generic node editing form
"""
def set_properties(dyn_schema, form_schema, node_properties, form,
prefix="",
set_data=True):
def set_properties(dyn_schema, form_schema, node_properties, form, set_data,
prefix=""):
"""Initialize custom properties for the form. We run this function once
before validating the function with set_data=False, so that we can set
any multiselect field that was originally specified empty and fill it
with the current choices.
"""
for prop in dyn_schema:
schema_prop = dyn_schema[prop]
form_prop = form_schema.get(prop, {})
prop_name = "{0}{1}".format(prefix, prop)
if schema_prop['type'] == 'dict':
set_properties(
schema_prop['schema'],
form_prop['schema'],
node_properties[prop_name],
form,
"{0}__".format(prop_name))
continue
log.debug('set_properties(..., prefix=%r, set_data=%r) called', prefix, set_data)
for prop, schema_prop in dyn_schema.iteritems():
prop_name = "{0}{1}".format(prefix, prop)
if prop_name not in form:
continue
@ -359,49 +349,32 @@ def edit(node_id):
form[prop_name].choices = [(d, d) for d in db_prop_value]
# Choices should be a tuple with value and name
if not set_data:
continue
# Assign data to the field
if set_data:
if prop_name == 'attachments':
for attachment_collection in db_prop_value:
for a in attachment_collection['files']:
attachment_form = ProceduralFileSelectForm()
attachment_form.file = a['file']
attachment_form.slug = a['slug']
attachment_form.size = 'm'
form[prop_name].append_entry(attachment_form)
if prop_name == 'attachments':
attachments.attachment_form_group_set_data(db_prop_value, schema_prop,
form[prop_name])
elif prop_name == 'files':
subschema = schema_prop['schema']['schema']
# Extra entries are caused by min_entries=1 in the form
# creation.
field_list = form[prop_name]
while len(field_list) > len(db_prop_value):
field_list.pop_entry()
elif prop_name == 'files':
schema = schema_prop['schema']['schema']
# Extra entries are caused by min_entries=1 in the form
# creation.
field_list = form[prop_name]
if len(db_prop_value) > 0:
while len(field_list):
field_list.pop_entry()
for file_data in db_prop_value:
file_form_class = build_file_select_form(subschema)
subform = file_form_class()
for key, value in file_data.iteritems():
setattr(subform, key, value)
field_list.append_entry(subform)
for file_data in db_prop_value:
file_form_class = build_file_select_form(schema)
subform = file_form_class()
for key, value in file_data.iteritems():
setattr(subform, key, value)
field_list.append_entry(subform)
# elif prop_name == 'tags':
# form[prop_name].data = ', '.join(data)
else:
form[prop_name].data = db_prop_value
# elif prop_name == 'tags':
# form[prop_name].data = ', '.join(data)
else:
# Default population of multiple file form list (only if
# we are getting the form)
if request.method == 'POST':
continue
if prop_name == 'attachments':
if not db_prop_value:
attachment_form = ProceduralFileSelectForm()
attachment_form.file = 'file'
attachment_form.slug = ''
attachment_form.size = ''
form[prop_name].append_entry(attachment_form)
form[prop_name].data = db_prop_value
api = system_util.pillar_api()
node = Node.find(node_id, api=api)
@ -446,7 +419,7 @@ def edit(node_id):
if node.parent:
form.parent.data = node.parent
set_properties(dyn_schema, form_schema, node_properties, form)
set_properties(dyn_schema, form_schema, node_properties, form, set_data=True)
# Get previews
node.picture = get_file(node.picture, api=api) if node.picture else None

View File

@ -158,8 +158,8 @@ class CustomFormFieldWidget(object):
class CustomFormField(FormField):
def __init__(self, name, **kwargs):
super(CustomFormField, self).__init__(name, **kwargs)
def __init__(self, form_class, **kwargs):
super(CustomFormField, self).__init__(form_class, **kwargs)
self.widget = CustomFormFieldWidget()