Skip to content

Commit

Permalink
Merge 8427e0e into 976a341
Browse files Browse the repository at this point in the history
  • Loading branch information
AltusBarry committed Nov 20, 2017
2 parents 976a341 + 8427e0e commit 33a1f82
Show file tree
Hide file tree
Showing 27 changed files with 445 additions and 85 deletions.
60 changes: 49 additions & 11 deletions formfactory/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,66 @@
from django.conf import settings


# TODO add contenttype settings in
class AppFields(object):
DJANGO = "django.forms.fields"
FORMFACTORY = "formfactory.fields"

class AppWidgets(object):
DJANGO = "django.forms.widgets"
FORMFACTORY = "formfactory.widgets"

SETTINGS = getattr(settings, "FORMFACTORY", {
"field-types": [
"BooleanField", "CharField", "ChoiceField", "DateField",
"DateTimeField", "DecimalField", "EmailField", "FileField",
"FloatField", "GenericIPAddressField", "IntegerField",
"MultipleChoiceField", "SlugField", "SplitDateTimeField", "TimeField",
"URLField", "UUIDField"
("%s.BooleanField" % AppFields.DJANGO, "BooleanField"),
("%s.CharField" % AppFields.DJANGO, "CharField"),
("%s.ChoiceField" % AppFields.DJANGO, "ChoiceField"),
("%s.DateField" % AppFields.DJANGO, "DateField"),
("%s.DateTimeField" % AppFields.DJANGO, "DateTimeField"),
("%s.DecimalField" % AppFields.DJANGO, "DecimalField"),
("%s.EmailField" % AppFields.DJANGO, "EmailField"),
("%s.FileField" % AppFields.DJANGO, "FileField"),
("%s.FloatField" % AppFields.DJANGO, "FloatField"),
("%s.GenericIPAddressField" % AppFields.DJANGO, "GenericIPAddressField"),
("%s.IntegerField" % AppFields.DJANGO, "IntegerField"),
("%s.MultipleChoiceField" % AppFields.DJANGO, "MultipleChoiceField"),
("%s.SlugField" % AppFields.DJANGO, "SlugField"),
("%s.SplitDateTimeField" % AppFields.DJANGO, "SplitDateTimeField"),
("%s.TimeField" % AppFields.DJANGO, "TimeField"),
("%s.URLField" % AppFields.DJANGO, "URLField"),
("%s.UUIDField" % AppFields.DJANGO, "UUIDField"),
("%s.ParagraphField" % AppFields.FORMFACTORY, "ParagraphField"),
],
"widget-types": [
"CheckboxInput", "CheckboxSelectMultiple", "DateInput",
"DateTimeInput", "EmailInput", "FileInput", "HiddenInput",
"NullBooleanSelect", "NumberInput", "PasswordInput", "RadioSelect",
"Select", "SelectMultiple", "Textarea", "TextInput", "TimeInput",
"URLInput"
("%s.CheckboxInput" % AppWidgets.DJANGO, "CheckboxInput"),
(
"%s.CheckboxSelectMultiple" % AppWidgets.DJANGO,
"CheckboxSelectMultiple"
),
("%s.DateInput" % AppWidgets.DJANGO, "DateInput"),
("%s.DateTimeInput" % AppWidgets.DJANGO, "DateTimeInput"),
("%s.EmailInput" % AppWidgets.DJANGO, "EmailInput"),
("%s.FileInput" % AppWidgets.DJANGO, "FileInput"),
("%s.HiddenInput" % AppWidgets.DJANGO, "HiddenInput"),
("%s.NullBooleanSelect" % AppWidgets.DJANGO, "NullBooleanSelect"),
("%s.NumberInput" % AppWidgets.DJANGO, "NumberInput"),
("%s.PasswordInput" % AppWidgets.DJANGO, "PasswordInput"),
("%s.RadioSelect" % AppWidgets.DJANGO, "RadioSelect"),
("%s.Select" % AppWidgets.DJANGO, "Select"),
("%s.SelectMultiple" % AppWidgets.DJANGO, "SelectMultiple"),
("%s.Textarea" % AppWidgets.DJANGO, "Textarea"),
("%s.TextInput" % AppWidgets.DJANGO, "TextInput"),
("%s.TimeInput" % AppWidgets.DJANGO, "TimeInput"),
("%s.URLInput" % AppWidgets.DJANGO, "URLInput"),
("%s.ParagraphWidget" % AppWidgets.FORMFACTORY, "ParagraphWidget")
],
"error-types": [
"empty", "incomplete", "invalid", "invalid_choice", "invalid_image",
"invalid_list", "invalid_date", "invalid_time", "invalid_pk_value",
"list", "max_decimal_places", "max_digits", "max_length", "max_value",
"max_whole_digits", "min_length", "min_value", "missing", "required",
],
"redirect-url-param-name": "next"
"redirect-url-param-name": "next",
})


Expand Down
10 changes: 10 additions & 0 deletions formfactory/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ def store_data(form_instance, **kwargs):
uuid=cleaned_data.pop("uuid"),
form_id=cleaned_data.pop("form_id"),
)

# Formfactory can end up in a scenario where a none required field gets to
# this stage as empty in the cleaned data. However the actual data field
# that is being saved to is required and none null. At this stage seems to
# only happen with wizards. Solution, if the field is not required and the
# value is not present pop it from the dict that will be saved.
for name, field in form_instance.fields.items():
if not field.required and not cleaned_data.get(name, None):
cleaned_data.pop(name)

for key, value in cleaned_data.items():
FormDataItem.objects.create(
form_data=form_data,
Expand Down
97 changes: 79 additions & 18 deletions formfactory/factory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from uuid import uuid4
import importlib
import inspect

from django import forms
from django.conf import settings
from django.utils import six
from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.translation import ugettext_lazy as _

from formfactory import SETTINGS


class FormFactory(forms.Form):
"""Builds a form class from defined fields passed to it by the Form model.
Expand Down Expand Up @@ -34,31 +39,71 @@ def __init__(self, *args, **kwargs):
# Iterates over the fields defined in the Form model and sets the
# appropriate attributes and builds up the fieldgroups.
self.field_group = []

# Models aren't ready when the file is initially processed.
from formfactory import models
field_through = models.FieldGroupThrough
for field_group in defined_field_groups:
fields = field_group.fields.all().order_by("fieldgroupthrough")

# Issue with order by and through, see:
# https://code.djangoproject.com/ticket/26092.
try:
fields = field_group.fields.all().order_by("fieldgroupthrough")

# Make an arb call on the list to trigger the potential error.
len(fields)
except AttributeError as e:
fields = [instance.field for
instance in
field_through.objects.filter(
field_group=field_group).order_by("order")
]
self.field_group.append(
[field_group.title, field_group.show_title, [f.slug for f in fields]]
)
for field in fields:
field_type = getattr(forms, field.field_type)
field_meta = field.get_field_meta
field_type = getattr(field_meta[0], field_meta[1])

additional_validators = []
for validator in field.additional_validators.all():
additional_validators.append(
validator.as_function
)

self.fields[field.slug] = field_type(
label=field.label,
initial=field.initial or self.initial.get(field.slug),
required=field.required,
disabled=field.disabled,
help_text=field.help_text,
validators=additional_validators,
error_messages=dict(
# NOTE: Label always defaults to title if label field is empty,
# this is not expected behaviour. Label needs to be optional.
field_args = {}

# Some custom fields might need extra info passed to the actual
# field instance. Only cater for leaf args.
init_args = inspect.getargspec(field_type.__init__).args
for arg in init_args:
field_value = getattr(field, arg, None)
if field_value:
field_args[arg] = field_value

# Ensure the expected values are always present on a specific
# default subset.
field_args.update({
"label": field.label,
"initial": field.initial or self.initial.get(field.slug),
"required": field.required,
"disabled": field.disabled,
"help_text": field.help_text,
"validators": additional_validators,
"error_messages": dict(
(m.key, m.value) for m in field.error_messages.all()
)
)
})

# Sets the user defined widget if setup
if field.widget:
widget_meta = field.get_widget_meta
widget = getattr(widget_meta[0], widget_meta[1])
field_args["widget"] = widget()

self.fields[field.slug] = field_type(**field_args)

# Saves the field model pk to the form field to prevent the
# need for another query in the save method.
Expand Down Expand Up @@ -97,12 +142,10 @@ def __init__(self, *args, **kwargs):
except TypeError:
pass

# Sets the user defined widget if setup
if field.widget:
widget = getattr(forms.widgets, field.widget)
self.fields[field.slug].widget = widget()

# Adds widget-specific options to the form field
# TODO add pluggable attrs, can be assigned for now other
# fields on the form model. probably use widget class and settings.
# TODO Allowed attrs (widget_attrs["paragraph"] = field.paragraph)
widget_attrs = self.fields[field.slug].widget.attrs
widget_attrs["placeholder"] = field.placeholder
if choices:
Expand Down Expand Up @@ -202,7 +245,25 @@ def clean(self, **kwargs):
def save(self, *args, **kwargs):
"""Performs the required actions in the defined sequence.
"""
for action in self.actions.order_by("formactionthrough"):

# Models aren't ready when the file is initially processed.
from formfactory import models

# Issue with order by and through, see:
# https://code.djangoproject.com/ticket/26092.
try:
actions = self.actions.order_by("formactionthrough")

# Make an arb call on the list to trigger the potential error.
len(actions)
except AttributeError as e:
form = models.Form.objects.get(id=self.cleaned_data["form_id"])
actions = [instance.action for
instance in
models.FormActionThrough.objects.filter(
form=form).order_by("order")
]
for action in actions:
action_params = kwargs.copy()
action_params.update(dict(
(obj.key, obj.value) for obj in action.params.all()
Expand Down
30 changes: 30 additions & 0 deletions formfactory/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import markdown
import django

from django.forms.fields import Field
from django.utils.text import mark_safe

from formfactory import widgets


class ParagraphField(Field):
widget = widgets.ParagraphWidget
def __init__(self, paragraph, *args, **kwargs):
super(ParagraphField, self).__init__(*args, **kwargs)

# Always empty out label for a paragraph field.
self.label = ""

# No matter what is set, this field should never be required.
self.required = False
self.widget.is_required = False

# Pass the paragraph text to the widget without needing to override
# widget __init__. Process markdown here, its up to custom fields to
# worry about what they are trying to do, not factory.py
data = {
"base_attrs": self.widget.attrs,
"extra_attrs": {"paragraph": markdown.markdown(paragraph)}
}
attrs = self.widget.build_attrs(**data)
self.widget.attrs = attrs
2 changes: 1 addition & 1 deletion formfactory/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Meta(object):
"initial", "max_length", "help_text", "placeholder", "required",
"disabled", "choices", "model_choices_content_type",
"model_choices_object_id", "additional_validators",
"error_messages"
"error_messages", "paragraph"
]


Expand Down
46 changes: 46 additions & 0 deletions formfactory/migrations/0013_auto_20170908_1158.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-08 11:58
from __future__ import unicode_literals

from django.db import migrations, models
import simplemde.fields


class Migration(migrations.Migration):

dependencies = [
('formfactory', '0012_auto_20170703_1030'),
]

operations = [
migrations.AddField(
model_name='formfield',
name='paragraph',
field=simplemde.fields.SimpleMDEField(blank=True, help_text=b'Markdown for the formfactory ParagraphField and ParagraphWidget', null=True),
),
migrations.AlterField(
model_name='action',
name='action',
field=models.CharField(choices=[(b'formfactory.actions.file_upload', b'formfactory.actions.file_upload'), (b'formfactory.actions.login', b'formfactory.actions.login'), (b'formfactory.actions.send_email', b'formfactory.actions.send_email'), (b'formfactory.actions.store_data', b'formfactory.actions.store_data')], max_length=128),
),
migrations.AlterField(
model_name='customerrormessage',
name='key',
field=models.CharField(choices=[(b'empty', b'empty'), (b'incomplete', b'incomplete'), (b'invalid', b'invalid'), (b'invalid_choice', b'invalid_choice'), (b'invalid_image', b'invalid_image'), (b'invalid_list', b'invalid_list'), (b'invalid_date', b'invalid_date'), (b'invalid_time', b'invalid_time'), (b'invalid_pk_value', b'invalid_pk_value'), (b'list', b'list'), (b'max_decimal_places', b'max_decimal_places'), (b'max_digits', b'max_digits'), (b'max_length', b'max_length'), (b'max_value', b'max_value'), (b'max_whole_digits', b'max_whole_digits'), (b'min_length', b'min_length'), (b'min_value', b'min_value'), (b'missing', b'missing'), (b'required', b'required')], max_length=128),
),
migrations.AlterField(
model_name='formfield',
name='choices',
field=models.ManyToManyField(blank=True, to='formfactory.FieldChoice'),
),
migrations.AlterField(
model_name='formfield',
name='field_type',
field=models.CharField(choices=[(b'BooleanField', b'BooleanField'), (b'CharField', b'CharField'), (b'ChoiceField', b'ChoiceField'), (b'DateField', b'DateField'), (b'DateTimeField', b'DateTimeField'), (b'DecimalField', b'DecimalField'), (b'EmailField', b'EmailField'), (b'FileField', b'FileField'), (b'FloatField', b'FloatField'), (b'GenericIPAddressField', b'GenericIPAddressField'), (b'IntegerField', b'IntegerField'), (b'MultipleChoiceField', b'MultipleChoiceField'), (b'SlugField', b'SlugField'), (b'SplitDateTimeField', b'SplitDateTimeField'), (b'TimeField', b'TimeField'), (b'URLField', b'URLField'), (b'UUIDField', b'UUIDField'), (b'ParagraphField', b'formfactory.fields.ParagraphField')], max_length=128),
),
migrations.AlterField(
model_name='formfield',
name='widget',
field=models.CharField(blank=True, choices=[(b'CheckboxInput', b'CheckboxInput'), (b'CheckboxSelectMultiple', b'CheckboxSelectMultiple'), (b'DateInput', b'DateInput'), (b'DateTimeInput', b'DateTimeInput'), (b'EmailInput', b'EmailInput'), (b'FileInput', b'FileInput'), (b'HiddenInput', b'HiddenInput'), (b'NullBooleanSelect', b'NullBooleanSelect'), (b'NumberInput', b'NumberInput'), (b'PasswordInput', b'PasswordInput'), (b'RadioSelect', b'RadioSelect'), (b'Select', b'Select'), (b'SelectMultiple', b'SelectMultiple'), (b'Textarea', b'Textarea'), (b'TextInput', b'TextInput'), (b'TimeInput', b'TimeInput'), (b'URLInput', b'URLInput'), (b'ParagraphWidget', b'formfactory.widgets.ParagraphWidget')], help_text='Leave blank if you prefer to use the default widget.', max_length=128, null=True),
),
]
21 changes: 21 additions & 0 deletions formfactory/migrations/0014_auto_20170908_1354.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-08 13:54
from __future__ import unicode_literals

from django.db import migrations
import simplemde.fields


class Migration(migrations.Migration):

dependencies = [
('formfactory', '0013_auto_20170908_1158'),
]

operations = [
migrations.AlterField(
model_name='formfield',
name='paragraph',
field=simplemde.fields.SimpleMDEField(blank=True, help_text=b'Markdown for the formfactory ParagraphField and ParagraphWidget combination.', null=True),
),
]
Loading

0 comments on commit 33a1f82

Please sign in to comment.