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': { 'form_schema': {
'content_type': {'visible': False}, 'content_type': {'visible': False},
'attachments': {'visible': False},
'order': {'visible': False}, 'order': {'visible': False},
'tags': {'visible': False}, 'tags': {'visible': False},
'categories': {'visible': False} 'categories': {'visible': False}

View File

@ -4,9 +4,11 @@ import re
from bson import ObjectId from bson import ObjectId
import flask import flask
import pillarsdk import pillarsdk
import wtforms
from pillar.api.node_types import ATTACHMENT_SLUG_REGEX from pillar.api.node_types import ATTACHMENT_SLUG_REGEX
from pillar.web.utils import system_util 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) shortcode_re = re.compile(r'@\[(%s)\]' % ATTACHMENT_SLUG_REGEX)
log = logging.getLogger(__name__) 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} variations = {var.size: var for var in sdk_file.variations}
return flask.render_template('nodes/attachments/file_image.html', return flask.render_template('nodes/attachments/file_image.html',
file=sdk_file, vars=variations) 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 wtforms.validators import DataRequired
from pillar.web.utils import system_util from pillar.web.utils import system_util
from pillar.web.utils.forms import FileSelectField 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 CustomFormField
from pillar.web.utils.forms import build_file_select_form from pillar.web.utils.forms import build_file_select_form
from . import attachments
log = logging.getLogger(__name__) 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. """Add fields to a form based on the node and form schema provided.
:type node_schema: dict :type node_schema: dict
:param node_schema: the validation schema used by Cerberus :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) show and hide)
""" """
for prop, schema_prop in node_schema.iteritems(): for prop_name, schema_prop, form_prop in iter_node_properties(node_type):
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)
# Recursive call if detects a dict # Recursive call if detects a dict
field_type = schema_prop['type'] 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 field_type == 'dict':
if prop == 'attachments': assert prop_name == 'attachments'
# class AttachmentForm(Form): field = attachments.attachment_form_group_create(schema_prop)
# pass elif field_type == 'list':
# AttachmentForm.file = FileSelectField('file') if prop_name == 'files':
# AttachmentForm.size = StringField()
# AttachmentForm.slug = StringField()
field = FieldList(CustomFormField(ProceduralFileSelectForm))
elif prop == 'files':
schema = schema_prop['schema']['schema'] schema = schema_prop['schema']['schema']
file_select_form = build_file_select_form(schema) file_select_form = build_file_select_form(schema)
field = FieldList(CustomFormField(file_select_form), field = FieldList(CustomFormField(file_select_form),
@ -112,8 +111,6 @@ def get_node_form(node_type):
class ProceduralForm(Form): class ProceduralForm(Form):
pass pass
node_schema = node_type['dyn_schema'].to_dict()
form_prop = node_type['form_schema'].to_dict()
parent_prop = node_type['parent'] parent_prop = node_type['parent']
ProceduralForm.name = StringField('Name', validators=[DataRequired()]) ProceduralForm.name = StringField('Name', validators=[DataRequired()])
@ -126,7 +123,7 @@ def get_node_form(node_type):
ProceduralForm.picture = FileSelectField('Picture', file_format='image') ProceduralForm.picture = FileSelectField('Picture', file_format='image')
ProceduralForm.node_type = HiddenField(default=node_type['name']) ProceduralForm.node_type = HiddenField(default=node_type['name'])
add_form_properties(ProceduralForm, node_schema, form_prop) add_form_properties(ProceduralForm, node_type)
return ProceduralForm() return ProceduralForm()
@ -166,59 +163,44 @@ def process_node_form(form, node_id=None, node_type=None, user=None):
if form.parent.data != "": if form.parent.data != "":
node.parent = form.parent.data node.parent = form.parent.data
def update_data(node_schema, form_schema, prefix=""): for prop_name, schema_prop, form_prop in iter_node_properties(node_type):
for pr in node_schema: data = form[prop_name].data
schema_prop = node_schema[pr] if schema_prop['type'] == 'dict':
form_prop = form_schema.get(pr, {}) data = attachments.attachment_form_parse_post_data(data)
if pr == 'items': elif schema_prop['type'] == 'integer':
continue if data == '':
if 'visible' in form_prop and not form_prop['visible']: data = 0
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
else: else:
if pr in form: data = int(form[prop_name].data)
data = form[prop_name].data elif schema_prop['type'] == 'datetime':
path = prop_name.split('__') data = datetime.strftime(data, current_app.config['RFC1123_DATE_FORMAT'])
if len(path) > 1: elif schema_prop['type'] == 'list':
recursive_prop = recursive( if prop_name == 'files':
path, node.properties.to_dict(), data) # Only keep those items that actually refer to a file.
node.properties = recursive_prop data = [file_item for file_item in data
if file_item.get('file')]
else: else:
node.properties[prop_name] = data log.warning('Ignoring property %s of type %s',
update_data(node_schema, form_schema) 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) ok = node.update(api=api)
if not ok: if not ok:
log.warning('Unable to update node: %s', node.error) 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 flask_login import login_required
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
import pillar.web.nodes.attachments
from pillar.web.utils import caching from pillar.web.utils import caching
from pillar.web.nodes.forms import get_node_form from pillar.web.nodes.forms import get_node_form
from pillar.web.nodes.forms import process_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.utils.forms import build_file_select_form
from pillar.web import system_util from pillar.web import system_util
from . import finders from . import finders, attachments
blueprint = Blueprint('nodes', __name__) blueprint = Blueprint('nodes', __name__)
log = logging.getLogger(__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) # Treat it as normal file (zip, blend, application, etc)
asset_type = 'file' 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) template_path = os.path.join(template_path, asset_type)
@ -315,27 +314,18 @@ def edit(node_id):
"""Generic node editing form """Generic node editing form
""" """
def set_properties(dyn_schema, form_schema, node_properties, form, def set_properties(dyn_schema, form_schema, node_properties, form, set_data,
prefix="", prefix=""):
set_data=True):
"""Initialize custom properties for the form. We run this function once """Initialize custom properties for the form. We run this function once
before validating the function with set_data=False, so that we can set before validating the function with set_data=False, so that we can set
any multiselect field that was originally specified empty and fill it any multiselect field that was originally specified empty and fill it
with the current choices. 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': log.debug('set_properties(..., prefix=%r, set_data=%r) called', prefix, set_data)
set_properties(
schema_prop['schema'], for prop, schema_prop in dyn_schema.iteritems():
form_prop['schema'], prop_name = "{0}{1}".format(prefix, prop)
node_properties[prop_name],
form,
"{0}__".format(prop_name))
continue
if prop_name not in form: if prop_name not in form:
continue continue
@ -359,49 +349,32 @@ def edit(node_id):
form[prop_name].choices = [(d, d) for d in db_prop_value] form[prop_name].choices = [(d, d) for d in db_prop_value]
# Choices should be a tuple with value and name # Choices should be a tuple with value and name
if not set_data:
continue
# Assign data to the field # Assign data to the field
if set_data: if prop_name == 'attachments':
if prop_name == 'attachments': attachments.attachment_form_group_set_data(db_prop_value, schema_prop,
for attachment_collection in db_prop_value: form[prop_name])
for a in attachment_collection['files']: elif prop_name == 'files':
attachment_form = ProceduralFileSelectForm() subschema = schema_prop['schema']['schema']
attachment_form.file = a['file'] # Extra entries are caused by min_entries=1 in the form
attachment_form.slug = a['slug'] # creation.
attachment_form.size = 'm' field_list = form[prop_name]
form[prop_name].append_entry(attachment_form) while len(field_list) > len(db_prop_value):
field_list.pop_entry()
elif prop_name == 'files': for file_data in db_prop_value:
schema = schema_prop['schema']['schema'] file_form_class = build_file_select_form(subschema)
# Extra entries are caused by min_entries=1 in the form subform = file_form_class()
# creation. for key, value in file_data.iteritems():
field_list = form[prop_name] setattr(subform, key, value)
if len(db_prop_value) > 0: field_list.append_entry(subform)
while len(field_list):
field_list.pop_entry()
for file_data in db_prop_value: # elif prop_name == 'tags':
file_form_class = build_file_select_form(schema) # form[prop_name].data = ', '.join(data)
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
else: else:
# Default population of multiple file form list (only if form[prop_name].data = db_prop_value
# 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)
api = system_util.pillar_api() api = system_util.pillar_api()
node = Node.find(node_id, api=api) node = Node.find(node_id, api=api)
@ -446,7 +419,7 @@ def edit(node_id):
if node.parent: if node.parent:
form.parent.data = 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 # Get previews
node.picture = get_file(node.picture, api=api) if node.picture else None 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): class CustomFormField(FormField):
def __init__(self, name, **kwargs): def __init__(self, form_class, **kwargs):
super(CustomFormField, self).__init__(name, **kwargs) super(CustomFormField, self).__init__(form_class, **kwargs)
self.widget = CustomFormFieldWidget() self.widget = CustomFormFieldWidget()