Skip to content

Commit

Permalink
Merge 4b258f3 into 976a341
Browse files Browse the repository at this point in the history
  • Loading branch information
AltusBarry authored Nov 17, 2017
2 parents 976a341 + 4b258f3 commit 3f9d973
Show file tree
Hide file tree
Showing 22 changed files with 317 additions and 68 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
49 changes: 37 additions & 12 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 @@ -40,25 +45,41 @@ def __init__(self, *args, **kwargs):
[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()
)
)
})
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 @@ -99,10 +120,14 @@ def __init__(self, *args, **kwargs):

# Sets the user defined widget if setup
if field.widget:
widget = getattr(forms.widgets, field.widget)
widget_meta = field.get_widget_meta
widget = getattr(widget_meta[0], widget_meta[1])
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
24 changes: 24 additions & 0 deletions formfactory/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.forms.fields import Field

from formfactory import widgets


# TODO add support for paragraph field. Mark safe in templates.
class ParagraphField(Field):
widget = widgets.ParagraphWidget
def __init__(self, paragraph, *args, **kwargs):
super(ParagraphField, self).__init__(*args, **kwargs)

# Always empty out label for a pragraph 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
# widgit __init__.
attrs = self.widget.build_attrs(self.widget.attrs,
{"paragraph": paragraph}
)
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),
),
]
78 changes: 69 additions & 9 deletions formfactory/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import importlib
import markdown

from django import forms
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.text import mark_safe
from django.utils.translation import ugettext as _

from simplemde.fields import SimpleMDEField

from formfactory import (
_registry, actions, clean_methods, factory, SETTINGS, validators
_registry, actions, clean_methods, factory, SETTINGS, validators,
)


Expand All @@ -15,15 +21,26 @@
clean_methods.auto_discover()


FIELD_TYPES = tuple(
(field, field) for field in SETTINGS["field-types"]
if issubclass(getattr(forms.fields, field), forms.fields.Field)
)
def FIELD_TYPES():
fields = ()

WIDGET_TYPES = tuple(
(widget, widget) for widget in SETTINGS["widget-types"]
if issubclass(getattr(forms.widgets, widget), forms.widgets.Widget)
)
for content_type, field in SETTINGS["field-types"]:
module = importlib.import_module(content_type.replace(".%s" % field, ""))
if issubclass(getattr(module, field), forms.fields.Field):
fields = fields + ((content_type, field),)
return fields

FIELD_TYPES = FIELD_TYPES()

def WIDGET_TYPES():
widgets = ()
for content_type, widget in SETTINGS["widget-types"]:
module = importlib.import_module(content_type.replace(".%s" % widget, ""))
if issubclass(getattr(module, widget), forms.widgets.Widget):
widgets = widgets + ((content_type, widget),)
return widgets

WIDGET_TYPES = WIDGET_TYPES()

ERROR_MESSAGES = tuple(
(error_type, error_type) for error_type in SETTINGS["error-types"]
Expand Down Expand Up @@ -363,10 +380,53 @@ class FormField(models.Model):
)
additional_validators = models.ManyToManyField(Validator, blank=True)
error_messages = models.ManyToManyField(CustomErrorMessage, blank=True)
paragraph = SimpleMDEField(
null=True,
blank=True,
help_text="Markdown for the formfactory ParagraphField and"\
" ParagraphWidget combination."
)

def __unicode__(self):
return self.title

@property
def get_field_meta(self):
"""
Return field meta info
:return: tuple(module, field_class_name)
"""
for content_type, field in FIELD_TYPES:
if self.field_type == content_type:
module = importlib.import_module(
content_type.replace(".%s" % field, "")
)
return (module, field)
raise Exception("Field; %s on Field model %s: Does not have a" \
"properly defined content_type value" % (self.field_type, self.id))

@property
def get_widget_meta(self):
"""
Return field meta info
:return: tuple(module, widget_class_name)
"""
for content_type, widget in WIDGET_TYPES:
if self.widget == content_type:
module = importlib.import_module(
content_type.replace(".%s" % widget, "")
)
return (module, widget)
raise Exception("Widget; %s on Field model %s: Does not have a " \
"properly defined content_type value" % (self.widget, self.id))

@property
def safe_paragraph(self):
if self.paragraph:
return mark_safe(markdown.markdown(self.paragraph))
else:
return self.paragraph


class FieldGroupThrough(models.Model):
"""Through table for form fields and field groups with a defined order.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ widget }}
4 changes: 3 additions & 1 deletion formfactory/tests/requirements/110.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Django>=1.10,<1.11
django-formtools==2.0
django-test-without-migrations==0.4
django-test-without-migrations==0.6
Markdown==2.6.7
django-simplemde==0.0.9
2 changes: 2 additions & 0 deletions formfactory/tests/requirements/111.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Django>=1.11,<2.0
django-formtools==2.0
django-test-without-migrations==0.6
Markdown==2.6.7
django-simplemde==0.0.9
4 changes: 3 additions & 1 deletion formfactory/tests/requirements/19.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Django>=1.9,<1.10
django-formtools==2.0
django-test-without-migrations==0.4
django-test-without-migrations==0.6
Markdown==2.6.7
django-simplemde==0.0.9
Loading

0 comments on commit 3f9d973

Please sign in to comment.