From acbeb54f7b204e41983ceaaffa33f35fe51443b9 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:11:12 +0200 Subject: [PATCH 01/43] Fixed widget order --- formfactory/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index 0e991a1..681d524 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -4,15 +4,17 @@ SETTINGS = getattr(settings, "FORMFACTORY", { "field-types": [ "BooleanField", "CharField", "ChoiceField", "DateField", - "DateTimeField", "DecimalField", "EmailField", "FloatField", - "GenericIPAddressField", "IntegerField", "MultipleChoiceField", - "SlugField", "SplitDateTimeField", "TimeField", "URLField", "UUIDField" + "DateTimeField", "DecimalField", "EmailField", "FileField", + "FloatField", "GenericIPAddressField", "IntegerField", + "MultipleChoiceField", "SlugField", "SplitDateTimeField", "TimeField", + "URLField", "UUIDField" ], "widget-types": [ - "TextInput", "NumberInput", "EmailInput", "URLInput", "PasswordInput", - "HiddenInput", "DateInput", "DateTimeInput", "TimeInput", - "Textarea", "CheckboxInput", "Select", "NullBooleanSelect", - "SelectMultiple", "RadioSelect", "CheckboxSelectMultiple" + "CheckboxInput", "CheckboxSelectMultiple", "DateInput", + "DateTimeInput", "EmailInput", "FileInput", "HiddenInput", + "NullBooleanSelect", "NumberInput", "PasswordInput", "RadioSelect", + "Select", "SelectMultiple", "Textarea", "TextInput", "TimeInput", + "URLInput" ], "redirect-url-param-name": "next" }) From b07f9818fc64bba6e40af3e9eac6c530768a7242 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:55:43 +0200 Subject: [PATCH 02/43] Added medai dir to ignored files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 595da20..761f4d9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ pep8.txt .coverage *.db .idea/ +*media/ From 7ba0a6e3a5788419029381c40270da6737dd2e87 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:56:13 +0200 Subject: [PATCH 03/43] Added action to upload files --- formfactory/actions.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/formfactory/actions.py b/formfactory/actions.py index d36a1ee..c555771 100644 --- a/formfactory/actions.py +++ b/formfactory/actions.py @@ -1,3 +1,7 @@ +import os + +from django.conf import settings +from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.mail import send_mail from django.contrib import auth @@ -104,3 +108,33 @@ def login(form_instance, **kwargs): ) if user is not None: auth.login(request, user) + + +@register +def file_upload(form_instance, **kwargs): + """An action which uploads all files to a specific location. + """ + cleaned_data = form_instance.cleaned_data + + file_objects = [ + f for f in cleaned_data.values() if isinstance(f, InMemoryUploadedFile) + ] + + print "file_objects", file_objects + + try: + upload_path = cleaned_data.pop(kwargs["upload_path_field"]) + except KeyError: + raise exceptions.MissingActionParam("file_upload", "upload_path_field") + + full_upload_path = os.path.join(settings.MEDIA_ROOT, upload_path) + + # Creates the dir path if it does not already exist + if not os.path.exists(full_upload_path): + os.makedirs(full_upload_path) + + for file_object in file_objects: + file_path = os.path.join(full_upload_path, file_object.name) + with open(file_path, "wb+") as destination: + for chunk in file_object.chunks(): + destination.write(chunk) From b77764464ddff175e37ec13d0dde4765e3617407 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:57:31 +0200 Subject: [PATCH 04/43] Modified multipart form --- formfactory/templates/formfactory/form_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formfactory/templates/formfactory/form_detail.html b/formfactory/templates/formfactory/form_detail.html index aa38777..ff3da7b 100644 --- a/formfactory/templates/formfactory/form_detail.html +++ b/formfactory/templates/formfactory/form_detail.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -
+ {% csrf_token %} {{ form.as_p }} From 134b2c7357f00297dd4479db4028656360e89a41 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:57:55 +0200 Subject: [PATCH 05/43] Cleanup html --- .../templates/formfactory/wizard_detail.html | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/formfactory/templates/formfactory/wizard_detail.html b/formfactory/templates/formfactory/wizard_detail.html index 9b4871d..74f3a4c 100644 --- a/formfactory/templates/formfactory/wizard_detail.html +++ b/formfactory/templates/formfactory/wizard_detail.html @@ -1,20 +1,20 @@ {% load i18n %} -{% csrf_token %} -{{ wizard.form.media }} -{{ wizard.management_form }} -{% if wizard.form.forms %} - {{ wizard.form.management_form }} - {% for form in wizard.form.forms %} - {{ form.as_p }} - {% endfor %} -{% else %} - {{ wizard.form.as_p }} -{% endif %} + {% csrf_token %} + {{ wizard.form.media }} + {{ wizard.management_form }} + {% if wizard.form.forms %} + {{ wizard.form.management_form }} + {% for form in wizard.form.forms %} + {{ form.as_p }} + {% endfor %} + {% else %} + {{ wizard.form.as_p }} + {% endif %} -{% if wizard.steps.prev %} - - -{% endif %} - + {% if wizard.steps.prev %} + + + {% endif %} + From ab0d62cb4aff41bdf219b775757c8775aec2c477 Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 20:58:22 +0200 Subject: [PATCH 06/43] Added media root to test settings --- formfactory/tests/settings/110.py | 6 ++++++ formfactory/tests/settings/19.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/formfactory/tests/settings/110.py b/formfactory/tests/settings/110.py index e66e7c1..743f3be 100644 --- a/formfactory/tests/settings/110.py +++ b/formfactory/tests/settings/110.py @@ -1,3 +1,8 @@ +import os + + +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + DEBUG = True DATABASES = { @@ -51,5 +56,6 @@ SITE_ID = 1 STATIC_URL = "/static/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") SECRET_KEY = "SECRET_KEY" EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" diff --git a/formfactory/tests/settings/19.py b/formfactory/tests/settings/19.py index f723a1c..1da1b6f 100644 --- a/formfactory/tests/settings/19.py +++ b/formfactory/tests/settings/19.py @@ -1,3 +1,8 @@ +import os + + +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + DEBUG = True DATABASES = { @@ -51,5 +56,6 @@ SITE_ID = 1 STATIC_URL = "/static/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") SECRET_KEY = "SECRET_KEY" EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" From 6db125c19f26a73f3deb0de385cb28f31bba18da Mon Sep 17 00:00:00 2001 From: qoda Date: Mon, 13 Feb 2017 21:00:33 +0200 Subject: [PATCH 07/43] Removed print statement --- formfactory/actions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/formfactory/actions.py b/formfactory/actions.py index c555771..1133548 100644 --- a/formfactory/actions.py +++ b/formfactory/actions.py @@ -120,8 +120,6 @@ def file_upload(form_instance, **kwargs): f for f in cleaned_data.values() if isinstance(f, InMemoryUploadedFile) ] - print "file_objects", file_objects - try: upload_path = cleaned_data.pop(kwargs["upload_path_field"]) except KeyError: From e20e3a6af27cec1d494890c6cb3423f8716f51af Mon Sep 17 00:00:00 2001 From: qoda Date: Tue, 14 Feb 2017 15:14:10 +0200 Subject: [PATCH 08/43] Minor cleanup --- formfactory/actions.py | 1 + formfactory/views.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/formfactory/actions.py b/formfactory/actions.py index 1133548..4b40754 100644 --- a/formfactory/actions.py +++ b/formfactory/actions.py @@ -45,6 +45,7 @@ def store_data(form_instance, **kwargs): uuid=cleaned_data.pop("uuid"), form_id=cleaned_data.pop("form_id"), ) + for key, value in cleaned_data.items(): FormDataItem.objects.create( form_data=form_data, diff --git a/formfactory/views.py b/formfactory/views.py index f70f414..a01c79e 100644 --- a/formfactory/views.py +++ b/formfactory/views.py @@ -1,5 +1,6 @@ from django import forms from django.core.urlresolvers import reverse +from django.core.files.storage import DefaultStorage from django.contrib import messages from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 @@ -8,7 +9,6 @@ from formtools.wizard.views import NamedUrlSessionWizardView from formfactory import SETTINGS -from formfactory.forms import EmptyForm from formfactory.models import Form, Wizard @@ -62,8 +62,9 @@ def get_success_url(self): class FactoryWizardView(NamedUrlSessionWizardView): - form_list = [EmptyForm, ] + form_list = [forms.Form] redirect_to = None + file_storage = DefaultStorage() def get_prefix(self, request, *args, **kwargs): return "%s-%s" % (self.__class__.__name__, kwargs["slug"]) From bd5c7d850cc2fa96bdd51da4a5f934b265205859 Mon Sep 17 00:00:00 2001 From: qoda Date: Tue, 14 Feb 2017 15:14:24 +0200 Subject: [PATCH 09/43] Added file field tests --- formfactory/tests/test_base.py | 43 +++++++++++++++++++++++++++++++ formfactory/tests/test_factory.py | 35 +++++++++++++++++++++---- formfactory/tests/test_views.py | 7 ++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index e85980e..e57fb40 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -152,6 +152,35 @@ def load_fixtures(kls): **kls.emailformactionthrough_data ) + kls.fileuploadaction_data = { + "action": "formfactory.actions.file_upload" + } + kls.fileuploadaction = models.Action.objects.create( + **kls.fileuploadaction_data + ) + + kls.fileuploadactionparam_data = [ + { + "key": "upload_path_field", + "value": "upload-to", + "action": kls.fileuploadaction + } + ] + for param in kls.fileuploadactionparam_data: + setattr( + kls, "fileuploadactionparam_%s" % param["key"], + models.ActionParam.objects.create(**param) + ) + + kls.fileuploadformactionthrough_data = { + "action": kls.fileuploadaction, + "form": kls.simpleform, + "order": 2 + } + kls.fileuploadformactionthrough = models.FormActionThrough.objects.create( + **kls.fileuploadformactionthrough_data + ) + kls.simpleformfield_data = { "salutation": { "title": "Salutation", @@ -189,6 +218,20 @@ def load_fixtures(kls): "initial": "dev@praekelt.com", "required": True }, + "id_copy": { + "title": "ID Copy", + "slug": "id-copy", + "field_type": "FileField", + "required": True + }, + "upload_to": { + "title": "Upload To", + "slug": "upload-to", + "field_type": "CharField", + "widget": "HiddenInput", + "initial": "uploads/test", + "required": True + }, "subject": { "title": "Subject", "slug": "subject", diff --git a/formfactory/tests/test_factory.py b/formfactory/tests/test_factory.py index d487674..fdc11f4 100644 --- a/formfactory/tests/test_factory.py +++ b/formfactory/tests/test_factory.py @@ -1,3 +1,7 @@ +import os + +from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from formfactory import models @@ -17,7 +21,11 @@ def setUp(self): "subscribe-form-email-address": "test@test.com", "subscribe-form-accept-terms": True, "subscribe-form-to-email": "dev@praekelt.com", - "subscribe-form-subject": "Test Email" + "subscribe-form-subject": "Test Email", + "subscribe-form-upload-to": "uploads/test" + } + self.form_files = { + "subscribe-form-id-copy": SimpleUploadedFile("test.txt", "Test") } def test_form(self): @@ -29,22 +37,39 @@ def test_form(self): v, getattr(self.form_factory.fields[value["slug"]], k) ) - form_factory = self.simpleform.as_form(data=self.form_data) + form_factory = self.simpleform.as_form( + data=self.form_data, files=self.form_files + ) self.assertTrue(form_factory.is_bound) self.assertFalse(bool(form_factory.errors)) self.assertTrue(form_factory.is_valid()) def test_save(self): - form_factory = self.simpleform.as_form(data=self.form_data) + form_factory = self.simpleform.as_form( + data=self.form_data, files=self.form_files + ) self.assertTrue(form_factory.is_valid()) form_factory.save() form_store = models.FormData.objects.get( uuid=form_factory.fields["uuid"].initial ) + + form_data = self.form_data.copy() + form_data.update(self.form_files.copy()) for field in form_store.items.all(): field_key = "%s-%s" % (form_factory.prefix, field.form_field.slug) - self.assertEqual(field.value, str(self.form_data[field_key])) + self.assertEqual(field.value, str(form_data[field_key])) + + upload_path = os.path.join( + settings.MEDIA_ROOT, self.form_data["subscribe-form-upload-to"] + ) + for file_field in self.form_files: + self.assertTrue(os.path.exists(os.path.join( + upload_path, self.form_files[file_field].name + ))) def tearDown(self): - pass + test_file = os.path.join(settings.MEDIA_ROOT, "uploads/test/test.txt") + if os.path.exists(test_file): + os.remove(test_file) diff --git a/formfactory/tests/test_views.py b/formfactory/tests/test_views.py index 7a323b3..cb0c9b9 100644 --- a/formfactory/tests/test_views.py +++ b/formfactory/tests/test_views.py @@ -1,3 +1,4 @@ +from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse from django.contrib.auth import get_user_model from django.test import TestCase @@ -21,7 +22,9 @@ def setUp(self): "subscribe-form-email-address": "test@test.com", "subscribe-form-accept-terms": True, "subscribe-form-to-email": "dev@praekelt.com", - "subscribe-form-subject": "Test Email" + "subscribe-form-subject": "Test Email", + "subscribe-form-upload-to": "uploads/test", + "subscribe-form-id-copy": SimpleUploadedFile("test.txt", "Test") } self.user = get_user_model().objects.create(username="testuser") @@ -163,6 +166,8 @@ def post_first_step(self): "subscribe-form-subject": "Test email", "subscribe-form-form_id": self.simpleform.id, "subscribe-form-uuid": simple_form_uuid_field, + "subscribe-form-upload-to": "uploads/test", + "subscribe-form-id-copy": SimpleUploadedFile("test.txt", "Test") } response = self.client.post( reverse("formfactory:wizard-detail", kwargs={ From 06121c0d0480e36d1011c43acaac0173989b6c18 Mon Sep 17 00:00:00 2001 From: qoda Date: Tue, 14 Feb 2017 17:05:13 +0200 Subject: [PATCH 10/43] Minor cleanup --- formfactory/actions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/formfactory/actions.py b/formfactory/actions.py index 4b40754..1133548 100644 --- a/formfactory/actions.py +++ b/formfactory/actions.py @@ -45,7 +45,6 @@ def store_data(form_instance, **kwargs): uuid=cleaned_data.pop("uuid"), form_id=cleaned_data.pop("form_id"), ) - for key, value in cleaned_data.items(): FormDataItem.objects.create( form_data=form_data, From 0638f3d92cdf658065fa630fe045ab1649b2917f Mon Sep 17 00:00:00 2001 From: qoda Date: Tue, 14 Feb 2017 17:12:50 +0200 Subject: [PATCH 11/43] Updated docs --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 3a86a77..a769a3f 100644 --- a/README.rst +++ b/README.rst @@ -161,6 +161,8 @@ FormFactory come with some predefined actions: - login: logs a user in. Requires the following ``ActionParam`` - username_field: mapping to the form field where the username will be completed. - password_field: mapping to the form field where the username will be completed. + - file_upload: handles uploading files to a predefined path. Requires the following ``ActionParam`` + - upload_path_field: mapping to the form field where the upload path has been set. Custom actions can be added by creating a function in /formfactoryapp/actions.py. For example:: From 16dc8effe669d026f974830ab208f93b805a204a Mon Sep 17 00:00:00 2001 From: qoda Date: Tue, 14 Feb 2017 18:38:05 +0200 Subject: [PATCH 12/43] Modified to ensure file names are incremented --- formfactory/actions.py | 8 +++++-- formfactory/tests/test_base.py | 9 ++++++++ formfactory/tests/test_factory.py | 38 +++++++++++++++++++++++++------ formfactory/tests/test_utils.py | 27 ++++++++++++++++++++-- formfactory/tests/test_views.py | 8 +++++-- formfactory/utils.py | 16 +++++++++++++ 6 files changed, 93 insertions(+), 13 deletions(-) diff --git a/formfactory/actions.py b/formfactory/actions.py index 1133548..6bd2918 100644 --- a/formfactory/actions.py +++ b/formfactory/actions.py @@ -6,7 +6,9 @@ from django.contrib import auth from formfactory import _registry, exceptions -from formfactory.utils import auto_registration, clean_key, get_label +from formfactory.utils import ( + auto_registration, clean_key, get_label, increment_file_name +) def register(func): @@ -132,7 +134,9 @@ def file_upload(form_instance, **kwargs): os.makedirs(full_upload_path) for file_object in file_objects: - file_path = os.path.join(full_upload_path, file_object.name) + file_path = increment_file_name( + os.path.join(full_upload_path, file_object.name) + ) with open(file_path, "wb+") as destination: for chunk in file_object.chunks(): destination.write(chunk) diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index e57fb40..8cfea7d 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -1,9 +1,18 @@ +import os +import shutil import uuid +from django.conf import settings + from formfactory import models from formfactory.tests.models import Enum, EnumItem +def cleanup_files(): + test_file_dir = os.path.join(settings.MEDIA_ROOT, "uploads/test") + shutil.rmtree(test_file_dir, ignore_errors=True) + + def load_fixtures(kls): kls.form_data = { "title": "Form 1", diff --git a/formfactory/tests/test_factory.py b/formfactory/tests/test_factory.py index fdc11f4..9eb18ed 100644 --- a/formfactory/tests/test_factory.py +++ b/formfactory/tests/test_factory.py @@ -1,16 +1,19 @@ import os +import shutil from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from formfactory import models -from formfactory.tests.test_base import load_fixtures +from formfactory.tests.test_base import cleanup_files, load_fixtures class FactoryTestCase(TestCase): def setUp(self): load_fixtures(self) + cleanup_files() + self.form_factory = self.simpleform.as_form() self.form_fields = self.form_factory.fields self.form_data = { @@ -28,6 +31,10 @@ def setUp(self): "subscribe-form-id-copy": SimpleUploadedFile("test.txt", "Test") } + self.upload_path = os.path.join( + settings.MEDIA_ROOT, self.form_data["subscribe-form-upload-to"] + ) + def test_form(self): for value in self.simpleformfield_data.values(): self.assertIn(value["slug"], [f for f in self.form_factory.fields]) @@ -61,15 +68,32 @@ def test_save(self): field_key = "%s-%s" % (form_factory.prefix, field.form_field.slug) self.assertEqual(field.value, str(form_data[field_key])) - upload_path = os.path.join( - settings.MEDIA_ROOT, self.form_data["subscribe-form-upload-to"] + for file_field in self.form_files: + self.assertTrue(os.path.exists(os.path.join( + self.upload_path, self.form_files[file_field].name + ))) + + def test_incremental_filenames(self): + form_factory = self.simpleform.as_form( + data=self.form_data, files=self.form_files ) + self.assertTrue(form_factory.is_valid()) + form_factory.save() + + # Create and save the form again to test the file name is incremented + form_factory = self.simpleform.as_form( + data=self.form_data, files=self.form_files + ) + self.assertTrue(form_factory.is_valid()) + form_factory.save() + for file_field in self.form_files: self.assertTrue(os.path.exists(os.path.join( - upload_path, self.form_files[file_field].name + self.upload_path, self.form_files[file_field].name + ))) + self.assertTrue(os.path.exists(os.path.join( + self.upload_path, "test_1.txt" ))) def tearDown(self): - test_file = os.path.join(settings.MEDIA_ROOT, "uploads/test/test.txt") - if os.path.exists(test_file): - os.remove(test_file) + cleanup_files() diff --git a/formfactory/tests/test_utils.py b/formfactory/tests/test_utils.py index 7965703..0e9b92c 100644 --- a/formfactory/tests/test_utils.py +++ b/formfactory/tests/test_utils.py @@ -1,3 +1,5 @@ +import os + from django.test import TestCase from formfactory import models, utils @@ -5,7 +7,7 @@ class UtilsTestCase(TestCase): def setUp(self): - pass + self.file_path = "/tmp/test.txt" def test_get_all_model_fields(self): self.assertEqual( @@ -13,5 +15,26 @@ def test_get_all_model_fields(self): ["items", "id", "uuid", "form"] ) + def test_set_file_name(self): + self.assertEqual( + utils.set_file_name(self.file_path, count=0), self.file_path + ) + self.assertEqual( + utils.set_file_name(self.file_path, count=1), "/tmp/test_1.txt" + ) + + def test_increment_file_name(self): + self.assertEqual( + utils.increment_file_name(self.file_path), self.file_path + ) + + file_buffer = open(self.file_path, "wb+") + file_buffer.write("Test") + file_buffer.close() + self.assertEqual( + utils.increment_file_name(self.file_path), "/tmp/test_1.txt" + ) + def tearDown(self): - pass + if os.path.exists(self.file_path): + os.remove(self.file_path) diff --git a/formfactory/tests/test_views.py b/formfactory/tests/test_views.py index cb0c9b9..a37c149 100644 --- a/formfactory/tests/test_views.py +++ b/formfactory/tests/test_views.py @@ -5,12 +5,14 @@ from django.test.client import Client from formfactory import models -from formfactory.tests.test_base import load_fixtures +from formfactory.tests.test_base import cleanup_files, load_fixtures class ViewTestCase(TestCase): def setUp(self): load_fixtures(self) + cleanup_files() + self.client = Client() self.form_factory = self.simpleform.as_form() self.form_fields = self.form_factory.fields @@ -93,7 +95,6 @@ def tearDown(self): class LoginViewDetailTestCase(TestCase): def setUp(self): - super(LoginViewDetailTestCase, self).setUp() load_fixtures(self) self.form_factory = self.simpleform.as_form() self.form_fields = self.form_factory.fields @@ -252,3 +253,6 @@ def test_form_actions_at_done_step(self): form_data=form_data, value="Tester").exists()) self.assertTrue(models.FormDataItem.objects.filter( form_data=form_data, value="tester@example.com").exists()) + + def tearDown(self): + pass diff --git a/formfactory/utils.py b/formfactory/utils.py index 02235c2..b0f8240 100644 --- a/formfactory/utils.py +++ b/formfactory/utils.py @@ -1,3 +1,5 @@ +from os import path + from django.apps import apps from django.utils.module_loading import import_module @@ -23,3 +25,17 @@ def get_label(form_instance, field_name): def get_all_model_fields(model): return [field.name for field in model._meta.get_fields()] + + +def set_file_name(file_path, count): + file_name, extension = path.splitext(file_path) + if count: + return "%s_%s%s" % (file_name, count, extension) + return file_path + + +def increment_file_name(file_path): + count = 0 + while path.exists(set_file_name(file_path, count)): + count += 1 + return set_file_name(file_path, count) From 331f9c07755ab07da7531ff8a5ca5659685083f5 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 23 Feb 2017 22:22:36 +0200 Subject: [PATCH 13/43] Fix hidden fields on HTML output --- formfactory/factory.py | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/formfactory/factory.py b/formfactory/factory.py index 137d6e5..dee7251 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -109,18 +109,17 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, """ # Errors that should be displayed above all fields. top_errors = self.non_field_errors() - output, hidden_fields = [], [] for name, field in self.fields.items(): - html_class_attr = '' + html_class_attr = "" bf = self[name] # Escape and cache in local variable. bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) if bf.is_hidden: if bf_errors: top_errors.extend( - [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': force_text(e)} + [_("(Hidden field %(name)s) %(error)s") % {"name": name, "error": force_text(e)} for e in bf_errors]) hidden_fields.append(six.text_type(bf)) @@ -181,29 +180,9 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, # Insert any hidden fields in the last row. if hidden_fields: + # Add the hidden fields outside of the fieldset grouping. str_hidden = "".join(hidden_fields) - if output: - last_row = output[-1] - # Chop off the trailing row_ender (e.g. '') and - # insert the hidden fields. - if not last_row.endswith(row_ender): - # This can happen in the as_p() case (and possibly others - # that users write): if there are only top errors, we may - # not be able to conscript the last row for our purposes, - # so insert a new, empty row. - last_row = (normal_row % { - "errors": "", "label": "", - "field": "", "help_text": "", - "html_class_attr": html_class_attr, - "field_id": "" - }) - output.append(last_row) - output[-1] = last_row[:-len(row_ender)] + str_hidden + \ - row_ender - else: - # If there aren't any rows in the output, just append the - # hidden fields. - output.append(str_hidden) + output.append(str_hidden) return mark_safe("\n".join(output)) def save(self, *args, **kwargs): From f30060e40f75f6cb84cd19c811c24c57c746777d Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Sat, 25 Feb 2017 17:35:22 +0200 Subject: [PATCH 14/43] Add model for additional validators --- formfactory/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/formfactory/models.py b/formfactory/models.py index 85ed57d..9879f2a 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -66,6 +66,19 @@ def as_function(self): return _registry["actions"][self.action] +class Validator(models.Model): + """Defines a form action. + """ + validator = models.CharField(choices=ADDITIONAL_VALIDATORS, max_length=128) + + def __unicode__(self): + return self.validator + + @property + def as_function(self): + return _registry["validators"][self.validator] + + class ActionParam(models.Model): """Defines a constant that can be passed to the action function. """ From 0452ccdffca4fbdc462a0ffb8c1a1a50e9686612 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Sat, 25 Feb 2017 19:06:33 +0200 Subject: [PATCH 15/43] Testing new validator structure --- formfactory/admin.py | 7 +++++++ formfactory/factory.py | 5 ++++- formfactory/forms.py | 6 ++++++ formfactory/models.py | 1 + formfactory/tests/test_validators.py | 12 +++++++++++- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/formfactory/admin.py b/formfactory/admin.py index ef30a61..8d43a94 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -19,6 +19,12 @@ class ActionModelAdmin(admin.ModelAdmin): inlines = [FormActionParamInline] +class ValidatorModelAdmin(admin.ModelAdmin): + form = forms.ValidatorAdminForm + model = models.Validator + # inlines = [FormActionParamInline] + + class FormActionThroughInline(admin.StackedInline): form = forms.FormActionThroughAdminForm model = models.FormActionThrough @@ -83,6 +89,7 @@ class FormFieldAdmin(admin.ModelAdmin): admin.site.register(models.Action, ActionModelAdmin) +admin.site.register(models.Validator, ValidatorModelAdmin) admin.site.register(models.FieldChoice, FieldChoiceModelAdmin) admin.site.register(models.Form, FormAdmin) admin.site.register(models.FormData, FormDataAdmin) diff --git a/formfactory/factory.py b/formfactory/factory.py index 137d6e5..2c0da3a 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -42,7 +42,10 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: - additional_validators = [field.additional_validators] + additional_validators = [field.additional_validators.as_function()] + print "======================================" + print type(field.additional_validators.as_function) + print "======================================" self.fields[field.slug] = field_type( label=field.label, diff --git a/formfactory/forms.py b/formfactory/forms.py index af6b868..4dc7aa4 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -30,6 +30,12 @@ class Meta(object): fields = ["action", "form", "order"] +class ValidatorAdminForm(forms.ModelForm): + class Meta(object): + model = models.Validator + fields = ["validator"] + + class FormDataAdminForm(forms.ModelForm): class Meta(object): model = models.FormData diff --git a/formfactory/models.py b/formfactory/models.py index 9879f2a..ee7cc9c 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -304,6 +304,7 @@ class FormField(models.Model): additional_validators = models.CharField( choices=ADDITIONAL_VALIDATORS, max_length=128, blank=True, null=True ) + additional_validators = models.ForeignKey(Validator, blank=True, null=True) def __unicode__(self): return self.title diff --git a/formfactory/tests/test_validators.py b/formfactory/tests/test_validators.py index 40c07d1..6e04d20 100644 --- a/formfactory/tests/test_validators.py +++ b/formfactory/tests/test_validators.py @@ -22,8 +22,18 @@ def test_unregistry(self): self.dummy_validator, validators.get_registered_validators() ) - def test_action(self): + def test_validator(self): validator = validators.get_registered_validators()[ self.dummy_validator ] self.assertTrue(validator(2)) + + +class ValidatorUseCaseTest(TestCase): + """Test the validator in action. + """ + def setUp(self): + pass + + def test_validation_error_raised(self): + pass From ce272e826b0ecfdc9f82b9b41a59dcc5adbab06b Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Sat, 25 Feb 2017 19:30:22 +0200 Subject: [PATCH 16/43] Added migration for new Validator relation --- .../migrations/0006_auto_20170225_1723.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 formfactory/migrations/0006_auto_20170225_1723.py diff --git a/formfactory/migrations/0006_auto_20170225_1723.py b/formfactory/migrations/0006_auto_20170225_1723.py new file mode 100644 index 0000000..372083e --- /dev/null +++ b/formfactory/migrations/0006_auto_20170225_1723.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-02-25 17:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formfactory', '0005_added_enum_generic_relation'), + ] + + operations = [ + migrations.RemoveField( + model_name='formfield', + name='additional_validators', + ), + migrations.CreateModel( + name='Validator', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('validator', models.CharField(choices=[ + (b'kevro.customer.validators.dummy_validator', b'kevro.customer.validators.dummy_validator')], + max_length=128)), + ], + ), + migrations.AddField( + model_name='formfield', + name='additional_validators', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + to='formfactory.Validator'), + ), + ] From c0dbf52a612e2254f378a0beddeeccdf181aa600 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Sat, 25 Feb 2017 19:46:30 +0200 Subject: [PATCH 17/43] Modify factory to use new additional_validators for fields --- formfactory/factory.py | 2 +- ...1723.py => 0006_additional_validators_as_foreign_key.py} | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) rename formfactory/migrations/{0006_auto_20170225_1723.py => 0006_additional_validators_as_foreign_key.py} (76%) diff --git a/formfactory/factory.py b/formfactory/factory.py index 2c0da3a..2e3d67d 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -42,7 +42,7 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: - additional_validators = [field.additional_validators.as_function()] + additional_validators = [field.additional_validators.as_function] print "======================================" print type(field.additional_validators.as_function) print "======================================" diff --git a/formfactory/migrations/0006_auto_20170225_1723.py b/formfactory/migrations/0006_additional_validators_as_foreign_key.py similarity index 76% rename from formfactory/migrations/0006_auto_20170225_1723.py rename to formfactory/migrations/0006_additional_validators_as_foreign_key.py index 372083e..4dd0089 100644 --- a/formfactory/migrations/0006_auto_20170225_1723.py +++ b/formfactory/migrations/0006_additional_validators_as_foreign_key.py @@ -20,15 +20,13 @@ class Migration(migrations.Migration): name='Validator', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('validator', models.CharField(choices=[ - (b'kevro.customer.validators.dummy_validator', b'kevro.customer.validators.dummy_validator')], - max_length=128)), + ('validator', models.CharField(choices=[], max_length=128)), ], ), migrations.AddField( model_name='formfield', name='additional_validators', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + field=models.ForeignKey(blank=True, null=True, on_delete=models.deletion.CASCADE, to='formfactory.Validator'), ), ] From 1181e0a437c62ed64354e0d5682db92002071a6f Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Sun, 26 Feb 2017 21:46:18 +0200 Subject: [PATCH 18/43] Add validator instance --- formfactory/tests/test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index 8cfea7d..491d85f 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -400,6 +400,7 @@ def load_fixtures(kls): "redirect_to": "/" } + kls.validator = models.Validator.objects.create(validator=kls.dummy_validator) kls.wizard = models.Wizard.objects.create(**kls.wizard_data) kls.wizardformthrough_simple = models.WizardFormThrough.objects.create( wizard=kls.wizard, form=kls.simpleform, order=1 From c984a9227e04e0aed416ec6e7135b1503a3dc1de Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Mon, 27 Feb 2017 09:28:37 +0200 Subject: [PATCH 19/43] Test validators --- formfactory/tests/test_validators.py | 54 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/formfactory/tests/test_validators.py b/formfactory/tests/test_validators.py index 6e04d20..9950098 100644 --- a/formfactory/tests/test_validators.py +++ b/formfactory/tests/test_validators.py @@ -1,6 +1,7 @@ +from django.forms import ValidationError from django.test import TestCase -from formfactory import validators +from formfactory import models, validators from formfactory.tests.test_base import load_fixtures @@ -8,6 +9,41 @@ class ValidatorTestCase(TestCase): def setUp(self): load_fixtures(self) + def test_action(self): + validator = validators.get_registered_validators()[ + self.dummy_validator + ] + self.assertTrue(validator(2)) + + def test_form_field_validation(self): + field = models.FormField.objects.create( + title="Number", + slug="number", + field_type="IntegerField", + additional_validators=self.validator + ) + group = models.FormFieldGroup.objects.create( + title="Field Group 3", + show_title=False + ) + models.FieldGroupThrough.objects.create( + field=field, field_group=group, order=0 + ) + form = models.Form.objects.create( + title="Form 1", + slug="slug-1" + ) + models.FieldGroupFormThrough.objects.create( + form=form, + field_group=group, + order=0 + ) + bound_form = form.as_form(data={"number": 3}) + self.assertRaises( + ValidationError, + lambda: bound_form.fields["number"].run_validators(3) + ) + def test_registry(self): self.assertIn( self.dummy_validator, validators.get_registered_validators() @@ -21,19 +57,3 @@ def test_unregistry(self): self.assertNotIn( self.dummy_validator, validators.get_registered_validators() ) - - def test_validator(self): - validator = validators.get_registered_validators()[ - self.dummy_validator - ] - self.assertTrue(validator(2)) - - -class ValidatorUseCaseTest(TestCase): - """Test the validator in action. - """ - def setUp(self): - pass - - def test_validation_error_raised(self): - pass From e1db5bc4c8e2ffa7365c94f7147452ce39bc9b85 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Mon, 27 Feb 2017 09:34:33 +0200 Subject: [PATCH 20/43] Cleanup --- formfactory/admin.py | 1 - formfactory/factory.py | 3 --- formfactory/models.py | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/formfactory/admin.py b/formfactory/admin.py index 8d43a94..b877615 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -22,7 +22,6 @@ class ActionModelAdmin(admin.ModelAdmin): class ValidatorModelAdmin(admin.ModelAdmin): form = forms.ValidatorAdminForm model = models.Validator - # inlines = [FormActionParamInline] class FormActionThroughInline(admin.StackedInline): diff --git a/formfactory/factory.py b/formfactory/factory.py index 183939d..1f4d44a 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -43,9 +43,6 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: additional_validators = [field.additional_validators.as_function] - print "======================================" - print type(field.additional_validators.as_function) - print "======================================" self.fields[field.slug] = field_type( label=field.label, diff --git a/formfactory/models.py b/formfactory/models.py index ee7cc9c..efd56ee 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -67,7 +67,7 @@ def as_function(self): class Validator(models.Model): - """Defines a form action. + """Defines a form field validator. """ validator = models.CharField(choices=ADDITIONAL_VALIDATORS, max_length=128) From 3fe1c406ddf355a589af08ce3a53605e63985d36 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 2 Mar 2017 13:20:23 +0200 Subject: [PATCH 21/43] Fix duplicate model field --- formfactory/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/formfactory/models.py b/formfactory/models.py index efd56ee..893db4d 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -301,9 +301,6 @@ class FormField(models.Model): model_choices = GenericForeignKey( "model_choices_content_type", "model_choices_object_id" ) - additional_validators = models.CharField( - choices=ADDITIONAL_VALIDATORS, max_length=128, blank=True, null=True - ) additional_validators = models.ForeignKey(Validator, blank=True, null=True) def __unicode__(self): From 51d6cbd61d984b3d88af99e41bead4dec3f664f3 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 2 Mar 2017 13:49:33 +0200 Subject: [PATCH 22/43] PEP8 fix --- formfactory/tests/test_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index 491d85f..259e2e7 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -400,7 +400,9 @@ def load_fixtures(kls): "redirect_to": "/" } - kls.validator = models.Validator.objects.create(validator=kls.dummy_validator) + kls.validator = models.Validator.objects.create( + validator=kls.dummy_validator + ) kls.wizard = models.Wizard.objects.create(**kls.wizard_data) kls.wizardformthrough_simple = models.WizardFormThrough.objects.create( wizard=kls.wizard, form=kls.simpleform, order=1 From 26bae1d28480b6a910888e6a4333c7ac4cacc72b Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 09:36:37 +0200 Subject: [PATCH 23/43] Revert validator as ForeignKey --- formfactory/admin.py | 6 ---- formfactory/factory.py | 2 +- formfactory/forms.py | 6 ---- formfactory/models.py | 17 ++--------- formfactory/tests/test_base.py | 3 -- formfactory/tests/test_validators.py | 44 +++++----------------------- 6 files changed, 11 insertions(+), 67 deletions(-) diff --git a/formfactory/admin.py b/formfactory/admin.py index b877615..ef30a61 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -19,11 +19,6 @@ class ActionModelAdmin(admin.ModelAdmin): inlines = [FormActionParamInline] -class ValidatorModelAdmin(admin.ModelAdmin): - form = forms.ValidatorAdminForm - model = models.Validator - - class FormActionThroughInline(admin.StackedInline): form = forms.FormActionThroughAdminForm model = models.FormActionThrough @@ -88,7 +83,6 @@ class FormFieldAdmin(admin.ModelAdmin): admin.site.register(models.Action, ActionModelAdmin) -admin.site.register(models.Validator, ValidatorModelAdmin) admin.site.register(models.FieldChoice, FieldChoiceModelAdmin) admin.site.register(models.Form, FormAdmin) admin.site.register(models.FormData, FormDataAdmin) diff --git a/formfactory/factory.py b/formfactory/factory.py index 1f4d44a..dee7251 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -42,7 +42,7 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: - additional_validators = [field.additional_validators.as_function] + additional_validators = [field.additional_validators] self.fields[field.slug] = field_type( label=field.label, diff --git a/formfactory/forms.py b/formfactory/forms.py index 4dc7aa4..af6b868 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -30,12 +30,6 @@ class Meta(object): fields = ["action", "form", "order"] -class ValidatorAdminForm(forms.ModelForm): - class Meta(object): - model = models.Validator - fields = ["validator"] - - class FormDataAdminForm(forms.ModelForm): class Meta(object): model = models.FormData diff --git a/formfactory/models.py b/formfactory/models.py index 893db4d..85ed57d 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -66,19 +66,6 @@ def as_function(self): return _registry["actions"][self.action] -class Validator(models.Model): - """Defines a form field validator. - """ - validator = models.CharField(choices=ADDITIONAL_VALIDATORS, max_length=128) - - def __unicode__(self): - return self.validator - - @property - def as_function(self): - return _registry["validators"][self.validator] - - class ActionParam(models.Model): """Defines a constant that can be passed to the action function. """ @@ -301,7 +288,9 @@ class FormField(models.Model): model_choices = GenericForeignKey( "model_choices_content_type", "model_choices_object_id" ) - additional_validators = models.ForeignKey(Validator, blank=True, null=True) + additional_validators = models.CharField( + choices=ADDITIONAL_VALIDATORS, max_length=128, blank=True, null=True + ) def __unicode__(self): return self.title diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index 259e2e7..8cfea7d 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -400,9 +400,6 @@ def load_fixtures(kls): "redirect_to": "/" } - kls.validator = models.Validator.objects.create( - validator=kls.dummy_validator - ) kls.wizard = models.Wizard.objects.create(**kls.wizard_data) kls.wizardformthrough_simple = models.WizardFormThrough.objects.create( wizard=kls.wizard, form=kls.simpleform, order=1 diff --git a/formfactory/tests/test_validators.py b/formfactory/tests/test_validators.py index 9950098..40c07d1 100644 --- a/formfactory/tests/test_validators.py +++ b/formfactory/tests/test_validators.py @@ -1,7 +1,6 @@ -from django.forms import ValidationError from django.test import TestCase -from formfactory import models, validators +from formfactory import validators from formfactory.tests.test_base import load_fixtures @@ -9,41 +8,6 @@ class ValidatorTestCase(TestCase): def setUp(self): load_fixtures(self) - def test_action(self): - validator = validators.get_registered_validators()[ - self.dummy_validator - ] - self.assertTrue(validator(2)) - - def test_form_field_validation(self): - field = models.FormField.objects.create( - title="Number", - slug="number", - field_type="IntegerField", - additional_validators=self.validator - ) - group = models.FormFieldGroup.objects.create( - title="Field Group 3", - show_title=False - ) - models.FieldGroupThrough.objects.create( - field=field, field_group=group, order=0 - ) - form = models.Form.objects.create( - title="Form 1", - slug="slug-1" - ) - models.FieldGroupFormThrough.objects.create( - form=form, - field_group=group, - order=0 - ) - bound_form = form.as_form(data={"number": 3}) - self.assertRaises( - ValidationError, - lambda: bound_form.fields["number"].run_validators(3) - ) - def test_registry(self): self.assertIn( self.dummy_validator, validators.get_registered_validators() @@ -57,3 +21,9 @@ def test_unregistry(self): self.assertNotIn( self.dummy_validator, validators.get_registered_validators() ) + + def test_action(self): + validator = validators.get_registered_validators()[ + self.dummy_validator + ] + self.assertTrue(validator(2)) From bd22960e2b955149a26c2defb81561518a0791a7 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 09:38:32 +0200 Subject: [PATCH 24/43] Remove unused migration --- ...06_additional_validators_as_foreign_key.py | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 formfactory/migrations/0006_additional_validators_as_foreign_key.py diff --git a/formfactory/migrations/0006_additional_validators_as_foreign_key.py b/formfactory/migrations/0006_additional_validators_as_foreign_key.py deleted file mode 100644 index 4dd0089..0000000 --- a/formfactory/migrations/0006_additional_validators_as_foreign_key.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.12 on 2017-02-25 17:23 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('formfactory', '0005_added_enum_generic_relation'), - ] - - operations = [ - migrations.RemoveField( - model_name='formfield', - name='additional_validators', - ), - migrations.CreateModel( - name='Validator', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('validator', models.CharField(choices=[], max_length=128)), - ], - ), - migrations.AddField( - model_name='formfield', - name='additional_validators', - field=models.ForeignKey(blank=True, null=True, on_delete=models.deletion.CASCADE, - to='formfactory.Validator'), - ), - ] From 975a569d66d720d02e165c769f283f3ded8f37af Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 10:18:33 +0200 Subject: [PATCH 25/43] Fixed the way additional validators are added to fields --- formfactory/factory.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/formfactory/factory.py b/formfactory/factory.py index dee7251..c298896 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -7,6 +7,8 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ +from formfactory import _registry + class FormFactory(forms.Form): """Builds a form class from defined fields passed to it by the Form model. @@ -42,7 +44,9 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: - additional_validators = [field.additional_validators] + validator = _registry["validators"].get(field.additional_validators) + if validator: + additional_validators = [validator] self.fields[field.slug] = field_type( label=field.label, From 10ec66673179720a271588f36741e1bb4a52c810 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 10:20:46 +0200 Subject: [PATCH 26/43] PEP8 fix --- formfactory/factory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/formfactory/factory.py b/formfactory/factory.py index c298896..4254110 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -44,7 +44,9 @@ def __init__(self, *args, **kwargs): additional_validators = [] if field.additional_validators: - validator = _registry["validators"].get(field.additional_validators) + validator = _registry["validators"].get( + field.additional_validators + ) if validator: additional_validators = [validator] From 11b38d4f643a77d0dea0bb1289db2a91f2918d85 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 10:31:25 +0200 Subject: [PATCH 27/43] Add test for validator --- formfactory/tests/test_validators.py | 34 ++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/formfactory/tests/test_validators.py b/formfactory/tests/test_validators.py index 40c07d1..ad3087c 100644 --- a/formfactory/tests/test_validators.py +++ b/formfactory/tests/test_validators.py @@ -1,6 +1,7 @@ +from django.forms import ValidationError from django.test import TestCase -from formfactory import validators +from formfactory import models, validators from formfactory.tests.test_base import load_fixtures @@ -22,8 +23,37 @@ def test_unregistry(self): self.dummy_validator, validators.get_registered_validators() ) - def test_action(self): + def test_form_field_validator(self): validator = validators.get_registered_validators()[ self.dummy_validator ] self.assertTrue(validator(2)) + + def test_form_field_validation(self): + field = models.FormField.objects.create( + title="Number", + slug="number", + field_type="IntegerField", + additional_validators=self.dummy_validator + ) + group = models.FormFieldGroup.objects.create( + title="Field Group 3", + show_title=False + ) + models.FieldGroupThrough.objects.create( + field=field, field_group=group, order=0 + ) + form = models.Form.objects.create( + title="Form 1", + slug="slug-1" + ) + models.FieldGroupFormThrough.objects.create( + form=form, + field_group=group, + order=0 + ) + bound_form = form.as_form(data={"number": 3}) + self.assertRaises( + ValidationError, + lambda: bound_form.fields["number"].run_validators(3) + ) From 2c8dca82ee26067b68e4676c573f1259eb250299 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 11:16:43 +0200 Subject: [PATCH 28/43] Change additional_validators on formfield to be many-to-many --- formfactory/admin.py | 6 ++++ formfactory/factory.py | 10 ++---- formfactory/forms.py | 6 ++++ .../migrations/0006_auto_20170303_0913.py | 31 ++++++++++++++++++ formfactory/models.py | 17 ++++++++-- formfactory/tests/test_base.py | 1 + formfactory/tests/test_validators.py | 32 +++++++++---------- 7 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 formfactory/migrations/0006_auto_20170303_0913.py diff --git a/formfactory/admin.py b/formfactory/admin.py index ef30a61..b877615 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -19,6 +19,11 @@ class ActionModelAdmin(admin.ModelAdmin): inlines = [FormActionParamInline] +class ValidatorModelAdmin(admin.ModelAdmin): + form = forms.ValidatorAdminForm + model = models.Validator + + class FormActionThroughInline(admin.StackedInline): form = forms.FormActionThroughAdminForm model = models.FormActionThrough @@ -83,6 +88,7 @@ class FormFieldAdmin(admin.ModelAdmin): admin.site.register(models.Action, ActionModelAdmin) +admin.site.register(models.Validator, ValidatorModelAdmin) admin.site.register(models.FieldChoice, FieldChoiceModelAdmin) admin.site.register(models.Form, FormAdmin) admin.site.register(models.FormData, FormDataAdmin) diff --git a/formfactory/factory.py b/formfactory/factory.py index 4254110..f2694c2 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -7,8 +7,6 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ -from formfactory import _registry - class FormFactory(forms.Form): """Builds a form class from defined fields passed to it by the Form model. @@ -43,12 +41,10 @@ def __init__(self, *args, **kwargs): field_type = getattr(forms, field.field_type) additional_validators = [] - if field.additional_validators: - validator = _registry["validators"].get( - field.additional_validators + for validator in field.additional_validators.all(): + additional_validators.append( + validator.as_function ) - if validator: - additional_validators = [validator] self.fields[field.slug] = field_type( label=field.label, diff --git a/formfactory/forms.py b/formfactory/forms.py index af6b868..4dc7aa4 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -30,6 +30,12 @@ class Meta(object): fields = ["action", "form", "order"] +class ValidatorAdminForm(forms.ModelForm): + class Meta(object): + model = models.Validator + fields = ["validator"] + + class FormDataAdminForm(forms.ModelForm): class Meta(object): model = models.FormData diff --git a/formfactory/migrations/0006_auto_20170303_0913.py b/formfactory/migrations/0006_auto_20170303_0913.py new file mode 100644 index 0000000..e2c3d0c --- /dev/null +++ b/formfactory/migrations/0006_auto_20170303_0913.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-03-03 09:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formfactory', '0005_added_enum_generic_relation'), + ] + + operations = [ + migrations.CreateModel( + name='Validator', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('validator', models.CharField(max_length=128)), + ], + ), + migrations.RemoveField( + model_name='formfield', + name='additional_validators', + ), + migrations.AddField( + model_name='formfield', + name='additional_validators', + field=models.ManyToManyField(to='formfactory.Validator'), + ), + ] diff --git a/formfactory/models.py b/formfactory/models.py index 85ed57d..3f4cc64 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -66,6 +66,19 @@ def as_function(self): return _registry["actions"][self.action] +class Validator(models.Model): + """Defines a form field validator. + """ + validator = models.CharField(choices=ADDITIONAL_VALIDATORS, max_length=128) + + def __unicode__(self): + return self.validator + + @property + def as_function(self): + return _registry["validators"][self.validator] + + class ActionParam(models.Model): """Defines a constant that can be passed to the action function. """ @@ -288,9 +301,7 @@ class FormField(models.Model): model_choices = GenericForeignKey( "model_choices_content_type", "model_choices_object_id" ) - additional_validators = models.CharField( - choices=ADDITIONAL_VALIDATORS, max_length=128, blank=True, null=True - ) + additional_validators = models.ManyToManyField(Validator) def __unicode__(self): return self.title diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index 8cfea7d..491d85f 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -400,6 +400,7 @@ def load_fixtures(kls): "redirect_to": "/" } + kls.validator = models.Validator.objects.create(validator=kls.dummy_validator) kls.wizard = models.Wizard.objects.create(**kls.wizard_data) kls.wizardformthrough_simple = models.WizardFormThrough.objects.create( wizard=kls.wizard, form=kls.simpleform, order=1 diff --git a/formfactory/tests/test_validators.py b/formfactory/tests/test_validators.py index ad3087c..8d88e09 100644 --- a/formfactory/tests/test_validators.py +++ b/formfactory/tests/test_validators.py @@ -9,21 +9,7 @@ class ValidatorTestCase(TestCase): def setUp(self): load_fixtures(self) - def test_registry(self): - self.assertIn( - self.dummy_validator, validators.get_registered_validators() - ) - - def test_unregistry(self): - validator = validators.get_registered_validators()[ - self.dummy_validator - ] - validators.unregister(validator) - self.assertNotIn( - self.dummy_validator, validators.get_registered_validators() - ) - - def test_form_field_validator(self): + def test_action(self): validator = validators.get_registered_validators()[ self.dummy_validator ] @@ -34,8 +20,8 @@ def test_form_field_validation(self): title="Number", slug="number", field_type="IntegerField", - additional_validators=self.dummy_validator ) + field.additional_validators.add(self.validator) group = models.FormFieldGroup.objects.create( title="Field Group 3", show_title=False @@ -57,3 +43,17 @@ def test_form_field_validation(self): ValidationError, lambda: bound_form.fields["number"].run_validators(3) ) + + def test_registry(self): + self.assertIn( + self.dummy_validator, validators.get_registered_validators() + ) + + def test_unregistry(self): + validator = validators.get_registered_validators()[ + self.dummy_validator + ] + validators.unregister(validator) + self.assertNotIn( + self.dummy_validator, validators.get_registered_validators() + ) From 483eec84d35c9b735d96dda2f6f837e9834151cf Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 11:24:00 +0200 Subject: [PATCH 29/43] Renamed migration 0006 --- ...20170303_0913.py => 0006_additional_validator_many_to_many.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename formfactory/migrations/{0006_auto_20170303_0913.py => 0006_additional_validator_many_to_many.py} (100%) diff --git a/formfactory/migrations/0006_auto_20170303_0913.py b/formfactory/migrations/0006_additional_validator_many_to_many.py similarity index 100% rename from formfactory/migrations/0006_auto_20170303_0913.py rename to formfactory/migrations/0006_additional_validator_many_to_many.py From 92c793eef9833a9cec60974d5c504efc8a0a08f0 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Fri, 3 Mar 2017 11:26:32 +0200 Subject: [PATCH 30/43] PEP8 fix --- formfactory/tests/test_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/formfactory/tests/test_base.py b/formfactory/tests/test_base.py index 491d85f..259e2e7 100644 --- a/formfactory/tests/test_base.py +++ b/formfactory/tests/test_base.py @@ -400,7 +400,9 @@ def load_fixtures(kls): "redirect_to": "/" } - kls.validator = models.Validator.objects.create(validator=kls.dummy_validator) + kls.validator = models.Validator.objects.create( + validator=kls.dummy_validator + ) kls.wizard = models.Wizard.objects.create(**kls.wizard_data) kls.wizardformthrough_simple = models.WizardFormThrough.objects.create( wizard=kls.wizard, form=kls.simpleform, order=1 From 74a36d675128031f80e0c6b4ef8dea28e93703b8 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Tue, 7 Mar 2017 15:00:03 +0200 Subject: [PATCH 31/43] Ensure additional validators can be blank --- ...0007_additional_validators_can_be_blank.py | 20 +++++++++++++++++++ formfactory/models.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 formfactory/migrations/0007_additional_validators_can_be_blank.py diff --git a/formfactory/migrations/0007_additional_validators_can_be_blank.py b/formfactory/migrations/0007_additional_validators_can_be_blank.py new file mode 100644 index 0000000..5bf1a8f --- /dev/null +++ b/formfactory/migrations/0007_additional_validators_can_be_blank.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-03-07 12:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formfactory', '0006_additional_validator_many_to_many'), + ] + + operations = [ + migrations.AlterField( + model_name='formfield', + name='additional_validators', + field=models.ManyToManyField(blank=True, to='formfactory.Validator'), + ), + ] diff --git a/formfactory/models.py b/formfactory/models.py index 3f4cc64..1202385 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -301,7 +301,7 @@ class FormField(models.Model): model_choices = GenericForeignKey( "model_choices_content_type", "model_choices_object_id" ) - additional_validators = models.ManyToManyField(Validator) + additional_validators = models.ManyToManyField(Validator, blank=True) def __unicode__(self): return self.title From f515f0eaaa62c2d19451002a6e73a164dee4a031 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Wed, 8 Mar 2017 12:23:51 +0200 Subject: [PATCH 32/43] Add support for initial field values --- formfactory/factory.py | 3 ++- formfactory/models.py | 6 +++--- formfactory/views.py | 8 ++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/formfactory/factory.py b/formfactory/factory.py index f2694c2..3fb56d0 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -23,6 +23,7 @@ def __init__(self, *args, **kwargs): form_id = kwargs.pop("form_id") defined_field_groups = kwargs.pop("field_groups") + initial = kwargs.pop("initial") super(FormFactory, self).__init__(*args, **kwargs) @@ -48,7 +49,7 @@ def __init__(self, *args, **kwargs): self.fields[field.slug] = field_type( label=field.label, - initial=field.initial, + initial=field.initial or initial.get(field.slug), required=field.required, disabled=field.disabled, help_text=field.help_text, diff --git a/formfactory/models.py b/formfactory/models.py index 3f4cc64..e2015bb 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -150,7 +150,7 @@ def get_absolute_url(self): def absolute_url(self): return self.get_absolute_url() - def as_form(self, data=None, files=None): + def as_form(self, data=None, files=None, **kwargs): """ Builds the form factory object and returns it. """ @@ -164,8 +164,8 @@ def as_form(self, data=None, files=None): ) return factory.FormFactory( - data, files, prefix=self.slug, field_groups=ordered_field_groups, - form_id=self.pk, actions=self.actions.all() + data, files, field_groups=ordered_field_groups, + form_id=self.pk, actions=self.actions.all(), **kwargs ) diff --git a/formfactory/views.py b/formfactory/views.py index a01c79e..5a48d48 100644 --- a/formfactory/views.py +++ b/formfactory/views.py @@ -49,9 +49,13 @@ def get_form(self, form_class=None): ) if self.request.POST or self.request.FILES: return self.form_object.as_form( - self.request.POST, self.request.FILES + self.request.POST, self.request.FILES, + **self.get_form_kwargs() ) - return self.form_object.as_form() + return self.form_object.as_form(**self.get_form_kwargs()) + + def get_prefix(self): + return self.kwargs.get("slug", self.form_slug) def get_success_url(self): redirect_url = self.form_object.redirect_to or self.request.GET.get( From 259a19ca1612e65710901f0b1b7efa450f32e947 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Wed, 8 Mar 2017 14:09:43 +0200 Subject: [PATCH 33/43] Refactored the way kwargs are passed to formfactory for form creation --- formfactory/factory.py | 3 +-- formfactory/models.py | 15 +++++++++------ formfactory/views.py | 5 ----- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/formfactory/factory.py b/formfactory/factory.py index 3fb56d0..50d6de4 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -23,7 +23,6 @@ def __init__(self, *args, **kwargs): form_id = kwargs.pop("form_id") defined_field_groups = kwargs.pop("field_groups") - initial = kwargs.pop("initial") super(FormFactory, self).__init__(*args, **kwargs) @@ -49,7 +48,7 @@ def __init__(self, *args, **kwargs): self.fields[field.slug] = field_type( label=field.label, - initial=field.initial or initial.get(field.slug), + initial=field.initial or self.initial.get(field.slug), required=field.required, disabled=field.disabled, help_text=field.help_text, diff --git a/formfactory/models.py b/formfactory/models.py index e2015bb..7645e7f 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -150,7 +150,7 @@ def get_absolute_url(self): def absolute_url(self): return self.get_absolute_url() - def as_form(self, data=None, files=None, **kwargs): + def as_form(self, **kwargs): """ Builds the form factory object and returns it. """ @@ -162,11 +162,14 @@ def as_form(self, data=None, files=None, **kwargs): ordered_field_groups = self.fieldgroups.all().order_by( "fieldgroupformthrough" ) - - return factory.FormFactory( - data, files, field_groups=ordered_field_groups, - form_id=self.pk, actions=self.actions.all(), **kwargs - ) + kwargs.update({ + "field_groups": ordered_field_groups, + "form_id": self.pk, + "actions": self.actions.all(), + "prefix": kwargs.get("prefix", self.slug) + }) + + return factory.FormFactory(**kwargs) class Wizard(BaseFormModel): diff --git a/formfactory/views.py b/formfactory/views.py index 5a48d48..991a621 100644 --- a/formfactory/views.py +++ b/formfactory/views.py @@ -47,11 +47,6 @@ def get_form(self, form_class=None): self.form_object = get_object_or_404( Form, slug=self.kwargs.get("slug", self.form_slug) ) - if self.request.POST or self.request.FILES: - return self.form_object.as_form( - self.request.POST, self.request.FILES, - **self.get_form_kwargs() - ) return self.form_object.as_form(**self.get_form_kwargs()) def get_prefix(self): From 4c7e796783940834bf63565aa79f650ceeac43ec Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 10:30:19 +0200 Subject: [PATCH 34/43] Added model CustomErrorMessage --- formfactory/__init__.py | 4 ++++ formfactory/models.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index 681d524..19e40ae 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.utils.translation import ugettext_lazy as _ SETTINGS = getattr(settings, "FORMFACTORY", { @@ -16,6 +17,9 @@ "Select", "SelectMultiple", "Textarea", "TextInput", "TimeInput", "URLInput" ], + "error-types": { + "required": _("This field is required."), + }, "redirect-url-param-name": "next" }) diff --git a/formfactory/models.py b/formfactory/models.py index ef57553..bacb2a0 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -22,6 +22,10 @@ if issubclass(getattr(forms.widgets, widget), forms.widgets.Widget) ) +ERROR_MESSAGES = tuple( + (error_type, error_type) for error_type in SETTINGS["error-types"] +) + ADDITIONAL_VALIDATORS = tuple( (validator, validator) for validator in validators.get_registered_validators() @@ -79,6 +83,11 @@ def as_function(self): return _registry["validators"][self.validator] +class CustomErrorMessage(models.Model): + key = models.CharField(choices=ERROR_MESSAGES, max_length=128) + value = models.CharField(max_length=256) + + class ActionParam(models.Model): """Defines a constant that can be passed to the action function. """ From 18ac476f361bb6045a04080b5a0992650cd11378 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 10:59:05 +0200 Subject: [PATCH 35/43] Add tests for custom error messages --- formfactory/factory.py | 3 +- formfactory/models.py | 1 + formfactory/tests/settings/19.py | 1 + formfactory/tests/test_error_messages.py | 43 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 formfactory/tests/test_error_messages.py diff --git a/formfactory/factory.py b/formfactory/factory.py index 50d6de4..eb44520 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -52,7 +52,8 @@ def __init__(self, *args, **kwargs): required=field.required, disabled=field.disabled, help_text=field.help_text, - validators=additional_validators + validators=additional_validators, + error_messages=dict((m.key, m.value) for m in field.error_messages.all()) ) # Saves the field model pk to the form field to prevent the diff --git a/formfactory/models.py b/formfactory/models.py index bacb2a0..01944e1 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -314,6 +314,7 @@ class FormField(models.Model): "model_choices_content_type", "model_choices_object_id" ) additional_validators = models.ManyToManyField(Validator, blank=True) + error_messages = models.ManyToManyField(CustomErrorMessage, blank=True) def __unicode__(self): return self.title diff --git a/formfactory/tests/settings/19.py b/formfactory/tests/settings/19.py index 1da1b6f..066b566 100644 --- a/formfactory/tests/settings/19.py +++ b/formfactory/tests/settings/19.py @@ -1,5 +1,6 @@ import os +from django.utils.translation import ugettext_lazy as _ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/formfactory/tests/test_error_messages.py b/formfactory/tests/test_error_messages.py new file mode 100644 index 0000000..8b50a94 --- /dev/null +++ b/formfactory/tests/test_error_messages.py @@ -0,0 +1,43 @@ +from django.test import TestCase + +from formfactory import models + + +class CustomErrorMessageTestCase(TestCase): + + def test_custom_error_message(self): + field = models.FormField.objects.create( + title="Number", + slug="number", + field_type="IntegerField", + ) + + # Add custom error message and add it to field + error_message = models.CustomErrorMessage.objects.create( + key="required", + value="Please do not leave this field empty." + ) + field.error_messages.add(error_message) + + group = models.FormFieldGroup.objects.create( + title="Test field group", + show_title=False + ) + models.FieldGroupThrough.objects.create( + field=field, field_group=group, order=0 + ) + form = models.Form.objects.create( + title="Form 1", + slug="slug-1" + ) + models.FieldGroupFormThrough.objects.create( + form=form, + field_group=group, + order=0 + ) + bound_form = form.as_form(data={"number": ""}) + self.assertFalse(bound_form.is_valid()) + self.assertEqual( + bound_form.errors["number"][0], + "Please do not leave this field empty." + ) From 61e95cb0dba748283eb5c556d2224c54d27432e5 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 11:02:37 +0200 Subject: [PATCH 36/43] Migration for CustomErrorMessage field --- .../0008_custom_field_error_messages.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 formfactory/migrations/0008_custom_field_error_messages.py diff --git a/formfactory/migrations/0008_custom_field_error_messages.py b/formfactory/migrations/0008_custom_field_error_messages.py new file mode 100644 index 0000000..a34e883 --- /dev/null +++ b/formfactory/migrations/0008_custom_field_error_messages.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-03-09 08:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('formfactory', '0007_additional_validators_can_be_blank'), + ] + + operations = [ + migrations.CreateModel( + name='CustomErrorMessage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(choices=[], max_length=128)), + ('value', models.CharField(max_length=256)), + ], + ), + migrations.AddField( + model_name='formfield', + name='error_messages', + field=models.ManyToManyField(blank=True, to='formfactory.CustomErrorMessage'), + ), + ] From c2ae924da46346130e25ec93ec9e0f6fb1d5c81c Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 11:16:49 +0200 Subject: [PATCH 37/43] Add CustomErrorMessage to admin --- formfactory/__init__.py | 6 +++--- formfactory/admin.py | 6 ++++++ formfactory/forms.py | 9 ++++++++- formfactory/models.py | 3 +++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index 19e40ae..2cc85e8 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -17,9 +17,9 @@ "Select", "SelectMultiple", "Textarea", "TextInput", "TimeInput", "URLInput" ], - "error-types": { - "required": _("This field is required."), - }, + "error-types": [ + "required", + ], "redirect-url-param-name": "next" }) diff --git a/formfactory/admin.py b/formfactory/admin.py index b877615..ad37096 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -24,6 +24,11 @@ class ValidatorModelAdmin(admin.ModelAdmin): model = models.Validator +class CustomErrorModelAdmin(admin.ModelAdmin): + form = forms.CustomErrorAdminForm + model = models.CustomErrorMessage + + class FormActionThroughInline(admin.StackedInline): form = forms.FormActionThroughAdminForm model = models.FormActionThrough @@ -89,6 +94,7 @@ class FormFieldAdmin(admin.ModelAdmin): admin.site.register(models.Action, ActionModelAdmin) admin.site.register(models.Validator, ValidatorModelAdmin) +admin.site.register(models.CustomErrorMessage, CustomErrorModelAdmin) admin.site.register(models.FieldChoice, FieldChoiceModelAdmin) admin.site.register(models.Form, FormAdmin) admin.site.register(models.FormData, FormDataAdmin) diff --git a/formfactory/forms.py b/formfactory/forms.py index 4dc7aa4..67777f3 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -36,6 +36,12 @@ class Meta(object): fields = ["validator"] +class CustomErrorAdminForm(forms.ModelForm): + class Meta(object): + model = models.CustomErrorMessage + fields = ["key", "value"] + + class FormDataAdminForm(forms.ModelForm): class Meta(object): model = models.FormData @@ -103,7 +109,8 @@ class Meta(object): "title", "slug", "field_groups", "field_type", "widget", "label", "initial", "max_length", "help_text", "placeholder", "required", "disabled", "choices", "model_choices_content_type", - "model_choices_object_id", "additional_validators" + "model_choices_object_id", "additional_validators", + "error_messages" ] diff --git a/formfactory/models.py b/formfactory/models.py index 01944e1..f7981a9 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -87,6 +87,9 @@ class CustomErrorMessage(models.Model): key = models.CharField(choices=ERROR_MESSAGES, max_length=128) value = models.CharField(max_length=256) + def __unicode__(self): + return "%s: %s" % (self.key, self.value) + class ActionParam(models.Model): """Defines a constant that can be passed to the action function. From 7e1235335327ed5bf20bd3a756f61b9720516e9a Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 11:22:10 +0200 Subject: [PATCH 38/43] Code cleanup --- formfactory/__init__.py | 1 - formfactory/factory.py | 4 +++- formfactory/tests/settings/19.py | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index 2cc85e8..fa83df6 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.utils.translation import ugettext_lazy as _ SETTINGS = getattr(settings, "FORMFACTORY", { diff --git a/formfactory/factory.py b/formfactory/factory.py index eb44520..36985f9 100644 --- a/formfactory/factory.py +++ b/formfactory/factory.py @@ -53,7 +53,9 @@ def __init__(self, *args, **kwargs): 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()) + error_messages=dict( + (m.key, m.value) for m in field.error_messages.all() + ) ) # Saves the field model pk to the form field to prevent the diff --git a/formfactory/tests/settings/19.py b/formfactory/tests/settings/19.py index 066b566..1da1b6f 100644 --- a/formfactory/tests/settings/19.py +++ b/formfactory/tests/settings/19.py @@ -1,6 +1,5 @@ import os -from django.utils.translation import ugettext_lazy as _ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) From b7ad2c5d47137320f2b0a4dd4830d247229e0915 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 12:10:39 +0200 Subject: [PATCH 39/43] Add more error message types --- formfactory/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index fa83df6..fbbc3aa 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -17,6 +17,24 @@ "URLInput" ], "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" From f338b9126b4aa90b6ef1dce4ef4e240189771763 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 14:49:36 +0200 Subject: [PATCH 40/43] Made custom error messages be inline on FormField admin --- formfactory/admin.py | 9 +++++++++ formfactory/forms.py | 8 ++++++-- formfactory/models.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/formfactory/admin.py b/formfactory/admin.py index ad37096..d086d04 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -24,6 +24,13 @@ class ValidatorModelAdmin(admin.ModelAdmin): model = models.Validator +class FieldCustomErrorInline(admin.StackedInline): + model = models.FormFieldErrorMessageProxy + verbose_name = "Error message" + verbose_name_plural = "Error messages" + form = forms.CustomErrorAdminForm + + class CustomErrorModelAdmin(admin.ModelAdmin): form = forms.CustomErrorAdminForm model = models.CustomErrorMessage @@ -90,6 +97,8 @@ class FormFieldGroupAdmin(admin.ModelAdmin): class FormFieldAdmin(admin.ModelAdmin): form = forms.FormFieldAdminForm model = models.FormField + inlines = [FieldCustomErrorInline] + exclude = ("error_messages", ) admin.site.register(models.Action, ActionModelAdmin) diff --git a/formfactory/forms.py b/formfactory/forms.py index 67777f3..ec0c8db 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import ugettext_lazy as _ from formfactory import models @@ -38,8 +39,11 @@ class Meta(object): class CustomErrorAdminForm(forms.ModelForm): class Meta(object): - model = models.CustomErrorMessage - fields = ["key", "value"] + model = models.FormField.error_messages.through + fields = ["formfield", "customerrormessage"] + labels = { + "customerrormessage": _("Error message"), + } class FormDataAdminForm(forms.ModelForm): diff --git a/formfactory/models.py b/formfactory/models.py index f7981a9..0963b35 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -87,6 +87,10 @@ class CustomErrorMessage(models.Model): key = models.CharField(choices=ERROR_MESSAGES, max_length=128) value = models.CharField(max_length=256) + class Meta(object): + verbose_name = "Field error message" + verbose_name_plural = "Field error messages" + def __unicode__(self): return "%s: %s" % (self.key, self.value) @@ -337,3 +341,11 @@ class Meta(object): def __unicode__(self): return "%s (%s)" % (self.field.title, self.order) + + +class FormFieldErrorMessageProxy(FormField.error_messages.through): + class Meta: + proxy = True + + def __unicode__(self): + return "For %s" % self.customerrormessage.key From c5efe8d7fbc00b8ef338291d9c2354ff6fceeffe Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 15:17:27 +0200 Subject: [PATCH 41/43] Make additional validators to be inline for formfields --- formfactory/admin.py | 13 ++++++++++--- formfactory/forms.py | 8 +++++++- formfactory/models.py | 10 +++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/formfactory/admin.py b/formfactory/admin.py index d086d04..672c94c 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -28,7 +28,14 @@ class FieldCustomErrorInline(admin.StackedInline): model = models.FormFieldErrorMessageProxy verbose_name = "Error message" verbose_name_plural = "Error messages" - form = forms.CustomErrorAdminForm + form = forms.CustomErrorInlineAdminForm + + +class FieldValidatorInline(admin.StackedInline): + model = models.FormFieldValidatorProxy + verbose_name = "Additional validator" + verbose_name_plural = "Additional validators" + # form = forms.CustomErrorAdminForm class CustomErrorModelAdmin(admin.ModelAdmin): @@ -97,8 +104,8 @@ class FormFieldGroupAdmin(admin.ModelAdmin): class FormFieldAdmin(admin.ModelAdmin): form = forms.FormFieldAdminForm model = models.FormField - inlines = [FieldCustomErrorInline] - exclude = ("error_messages", ) + inlines = [FieldCustomErrorInline, FieldValidatorInline] + exclude = ("error_messages", "additional_validators") admin.site.register(models.Action, ActionModelAdmin) diff --git a/formfactory/forms.py b/formfactory/forms.py index ec0c8db..f8ff628 100644 --- a/formfactory/forms.py +++ b/formfactory/forms.py @@ -39,7 +39,13 @@ class Meta(object): class CustomErrorAdminForm(forms.ModelForm): class Meta(object): - model = models.FormField.error_messages.through + model = models.CustomErrorMessage + fields = ["key", "value"] + + +class CustomErrorInlineAdminForm(forms.ModelForm): + class Meta(object): + model = models.FormFieldErrorMessageProxy fields = ["formfield", "customerrormessage"] labels = { "customerrormessage": _("Error message"), diff --git a/formfactory/models.py b/formfactory/models.py index 0963b35..c3bbf45 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -348,4 +348,12 @@ class Meta: proxy = True def __unicode__(self): - return "For %s" % self.customerrormessage.key + return str(self.customerrormessage) + + +class FormFieldValidatorProxy(FormField.additional_validators.through): + class Meta: + proxy = True + + def __unicode__(self): + return str(self.validator) From 4e098b9dda8e14d4299ec6aea2a67966f9758109 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 15:20:40 +0200 Subject: [PATCH 42/43] Code cleanup --- formfactory/__init__.py | 23 ++++------------------- formfactory/admin.py | 1 - 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/formfactory/__init__.py b/formfactory/__init__.py index fbbc3aa..b7000ea 100644 --- a/formfactory/__init__.py +++ b/formfactory/__init__.py @@ -17,25 +17,10 @@ "URLInput" ], "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", + "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" }) diff --git a/formfactory/admin.py b/formfactory/admin.py index 672c94c..d4afd1f 100644 --- a/formfactory/admin.py +++ b/formfactory/admin.py @@ -35,7 +35,6 @@ class FieldValidatorInline(admin.StackedInline): model = models.FormFieldValidatorProxy verbose_name = "Additional validator" verbose_name_plural = "Additional validators" - # form = forms.CustomErrorAdminForm class CustomErrorModelAdmin(admin.ModelAdmin): From 18e3ec4fdce1f7c98ab903a60380ab9f1be417f3 Mon Sep 17 00:00:00 2001 From: Phala Ramonyai Date: Thu, 9 Mar 2017 15:27:37 +0200 Subject: [PATCH 43/43] Fix tests --- formfactory/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/formfactory/models.py b/formfactory/models.py index c3bbf45..c5efc3a 100644 --- a/formfactory/models.py +++ b/formfactory/models.py @@ -345,6 +345,7 @@ def __unicode__(self): class FormFieldErrorMessageProxy(FormField.error_messages.through): class Meta: + auto_created = True proxy = True def __unicode__(self): @@ -353,6 +354,7 @@ def __unicode__(self): class FormFieldValidatorProxy(FormField.additional_validators.through): class Meta: + auto_created = True proxy = True def __unicode__(self):