Django正确完成表单生成器/构建器应用程序:可定制、模块化、用户和开发者友好。
项目描述
django-fobi(或简称fobi)是一个可定制的、模块化的、用户和开发者友好的Django表单生成器/构建器应用程序。使用fobi,您可以使用直观的GUI构建Django表单,保存或邮寄提交的表单数据,甚至将表单导出为JSON格式并在其他实例中导入。API允许您构建自己的表单元素和表单处理器(处理提交的表单数据的方法)。
先决条件
Django 2.2、3.0、3.1、3.2、4.0和4.1。
Python 3.6、3.7、3.8、3.9、3.10和3.11。
关键概念
每个表单都由元素组成。表单元素分为组
表单字段(输入字段、文本区域、隐藏字段、文件字段等)。
内容(表现)元素(文本、图像、嵌入视频等)。
安全元素(验证码等)。
表单元素的个数不受限制。
每个表单可以包含处理程序。处理程序处理表单数据(例如,保存或发送邮件)。处理程序的个数不受限制。
表单元素和处理程序都是基于Django权限系统设计的。
作为处理程序的补充,实现了表单回调。表单回调在表单数据的前后处理的不同阶段(POST阶段)被触发。表单回调不使用权限系统(除非你在回调代码中故意这样做)并且对所有表单都生效(与处理程序不同,处理程序只有被分配时才会执行)。
每个插件(表单元素或处理程序)或回调都是一个Django微应用。
除了表单元素和处理程序插件,还实现了集成表单元素和集成处理程序插件,以便与各种第三方应用程序和框架集成(如Django REST框架)。
注意,django-fobi 不需要 django-admin 和管理权限/权限来访问UI,尽管通过 simple 主题实现了与 django-admin 几乎无缝的集成。
主要功能和亮点
用户友好的GUI,可以快速构建表单。
表单向导。将表单组合成向导。表单向导可以包含处理程序。处理程序处理表单向导数据(例如,保存或发送邮件)。表单向导处理程序的个数不受限制。
表单可以根据日期自动启用/禁用(开始日期,结束日期)。
带出盒子的反垃圾邮件解决方案,如 CAPTCHA、ReCAPTCHA、Honeypot 或 Invisible reCAPTCHA。请注意,CAPTCHA和ReCAPTCHA需要安装额外的第三方应用程序;Invisible reCAPTCHA则不需要。
除了标准表单元素之外,还有与标准表单元素并行的外观(表现)元素(用于添加文本、图像或嵌入视频)。
插件中的数据处理(表单处理程序)。保存数据,将其发送到某个地址或重新发布到某个其他端点。有关更多信息,请参阅 预包装表单处理程序插件。
开发者友好的API,允许在不接触核心的情况下编辑现有或构建新的表单字段和处理程序。
支持自定义用户模型。
基于类的视图(和基于类的权限)。表单有一个所有者(auth.User)。为所有者设置了默认权限,但基于类的视图提供了很多自由度,并且可以轻松定制。
主题。有4个可用的 预包装主题:“Bootstrap 3”、“Foundation 5”、“Simple”(带有类似Django admin风格的编辑界面)和“DjangoCMS admin style”主题(这是一个另一个简单主题,带有类似 djangocms-admin-style 风格的编辑界面)。
实现了与Django REST框架的 集成。
实现了与Wagtail的集成(以Wagtail页面形式)。
实现了与FeinCMS的集成(以FeinCMS页面小部件形式)。
实现了与DjangoCMS的集成(以DjangoCMS页面插件形式)。
实现了与Mezzanine的集成(以Mezzanine页面形式)。
使用拖放重新排序表单元素。
将数据导出为XLS/CSV格式(使用数据库存储表单处理插件)。
表单元素的动态初始值。
将表单导入/导出为JSON格式。
使用mailchimp导入器从MailChimp导入表单。
路线图
一些即将推出/开发中的功能/改进包括
根据日期禁用表单。
表单克隆。
JSON模式支持。
Webpack集成。
改进Django REST framework OPTIONS。
Bootstrap 5支持。
Foundation 6支持。
查看TODOS以获取计划、待办、开发或待实施功能的完整列表。
一些截图
查看文档中的截图
演示
实时演示
查看Heroku上的实时演示应用。另外,查看Django REST framework集成演示。
凭证
用户名:test_user
密码:test_user
本地运行演示
为了能够快速评估django-fobi,创建了一个演示应用(带有快速安装器)(在Ubuntu/Debian上工作,也可能在其他Linux系统上工作,但不保证)。按照以下说明,在一分钟内启动演示。
获取最新的django_fobi_example_app_installer.sh
wget https://raw.github.com/barseghyanartur/django-fobi/stable/examples/django_fobi_example_app_installer.sh
为安装器分配执行权限并运行django_fobi_example_app_installer.sh
chmod +x django_fobi_example_app_installer.sh
./django_fobi_example_app_installer.sh
打开您的浏览器并测试应用。
仪表板
管理员用户名:test_admin
管理员密码:test
Django管理界面
管理员用户名:test_admin
管理员密码:test
如果快速安装器对您不起作用,请查看运行示例项目的手动步骤。
快速入门
查看快速入门。
安装
从PyPI安装最新稳定版本
pip install django-fobi
或从GitHub安装最新稳定版本
pip install https://github.com/barseghyanartur/django-fobi/archive/stable.tar.gz
将fobi添加到您的项目Django设置的INSTALLED_APPS中。此外,所有要使用的主题和插件也应添加到INSTALLED_APPS中。请注意,如果插件有其他依赖项,您还应在INSTALLED_APPS中提及这些依赖项。
INSTALLED_APPS = ( # Used by fobi 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', # ... # `django-fobi` core 'fobi', # `django-fobi` themes 'fobi.contrib.themes.bootstrap3', # Bootstrap 3 theme 'fobi.contrib.themes.foundation5', # Foundation 5 theme 'fobi.contrib.themes.simple', # Simple theme # `django-fobi` form elements - fields 'fobi.contrib.plugins.form_elements.fields.boolean', 'fobi.contrib.plugins.form_elements.fields.checkbox_select_multiple', 'fobi.contrib.plugins.form_elements.fields.date', 'fobi.contrib.plugins.form_elements.fields.date_drop_down', 'fobi.contrib.plugins.form_elements.fields.datetime', 'fobi.contrib.plugins.form_elements.fields.decimal', 'fobi.contrib.plugins.form_elements.fields.email', 'fobi.contrib.plugins.form_elements.fields.file', 'fobi.contrib.plugins.form_elements.fields.float', 'fobi.contrib.plugins.form_elements.fields.hidden', 'fobi.contrib.plugins.form_elements.fields.input', 'fobi.contrib.plugins.form_elements.fields.integer', 'fobi.contrib.plugins.form_elements.fields.ip_address', 'fobi.contrib.plugins.form_elements.fields.null_boolean', 'fobi.contrib.plugins.form_elements.fields.password', 'fobi.contrib.plugins.form_elements.fields.radio', 'fobi.contrib.plugins.form_elements.fields.regex', 'fobi.contrib.plugins.form_elements.fields.select', 'fobi.contrib.plugins.form_elements.fields.select_model_object', 'fobi.contrib.plugins.form_elements.fields.select_multiple', 'fobi.contrib.plugins.form_elements.fields.select_multiple_model_objects', 'fobi.contrib.plugins.form_elements.fields.slug', 'fobi.contrib.plugins.form_elements.fields.text', 'fobi.contrib.plugins.form_elements.fields.textarea', 'fobi.contrib.plugins.form_elements.fields.time', 'fobi.contrib.plugins.form_elements.fields.url', # `django-fobi` form elements - content elements 'fobi.contrib.plugins.form_elements.test.dummy', 'easy_thumbnails', # Required by `content_image` plugin 'fobi.contrib.plugins.form_elements.content.content_image', 'fobi.contrib.plugins.form_elements.content.content_image_url', 'fobi.contrib.plugins.form_elements.content.content_text', 'fobi.contrib.plugins.form_elements.content.content_video', # `django-fobi` form handlers 'fobi.contrib.plugins.form_handlers.db_store', 'fobi.contrib.plugins.form_handlers.http_repost', 'fobi.contrib.plugins.form_handlers.mail', 'fobi.contrib.plugins.form_handlers.mail_sender', # Other project specific apps 'foo', # Test app # ... )
对您的项目Django设置的TEMPLATES进行适当的修改。
并fobi.context_processors.theme和fobi.context_processors.dynamic_values。请参阅以下示例。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [(os.path.join('path', 'to', 'your', 'templates'))], 'OPTIONS': { 'context_processors': [ "django.template.context_processors.debug", 'django.template.context_processors.request', "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "fobi.context_processors.theme", # Important! "fobi.context_processors.dynamic_values", # Optional ], 'loaders': [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', 'admin_tools.template_loaders.Loader', ], 'debug': DEBUG_TEMPLATE, } }, ]
确保django.core.context_processors.request也包含在context_processors中。
配置URL
将以下行添加到您的urls模块的urlpatterns中。
# View URLs re_path(r'^fobi/', include('fobi.urls.class_based.view')), # Edit URLs re_path(r'^fobi/', include('fobi.urls.class_based.edit')),
请注意,一些插件需要额外的URL包含。例如,如果您在INSTALLED_APPS中列出了fobi.contrib.plugins.form_handlers.db_store表单处理插件,您应该在urls模块中提及以下内容。
# DB Store plugin URLs re_path(r'^fobi/plugins/form-handlers/db-store/', include('fobi.contrib.plugins.form_handlers.db_store.urls')),
将视图URL与编辑URL分开是为了能够以不同的前缀来前缀编辑URL。例如,如果您正在使用“简单”主题,您可能希望将编辑URL前缀为“admin/”,这样它看起来更像是django-admin。
创建新的表单元素插件
表单元素插件代表组成表单的元素:输入、复选框、文本区域、文件、隐藏字段,以及纯展示元素(文本或图片)。表单中的表单元素数量不限。
展示型表单元素是从fobi.base.FormElementPlugin继承的。
其余部分(真实表单元素,应该有值)是从fobi.base.FormFieldPlugin继承的。
您应该将表单元素插件视为一个Django微型应用,它可能有自己的模型、管理界面等。
django-fobi附带了一些捆绑的表单元素插件。请检查源代码作为示例。
假设,您想创建一个文本区域表单元素插件。
每个文本区域都应该有几个属性。它们是
label(字符串):文本区域的HTML标签。
name(字符串):文本区域的HTML名称。
initial(字符串):文本区域的初始值。
required(布尔值):标志,告诉我们字段是必需的还是可选的。
让我们将此插件命名为sample_textarea。插件目录应具有以下结构。
path/to/sample_textarea/
├── __init__.py
├── fobi_form_elements.py # Where plugins are defined and registered
├── forms.py # Plugin configuration form
└── widgets.py # Where plugins widgets are defined
表单元素插件应在“fobi_form_elements.py”文件中注册。每个插件模块应放在您的Django项目设置中的INSTALLED_APPS。
在某些情况下,您可能需要特定于插件的可覆盖设置(例如,查看fobi.contrib.form_elements.fields.content.content_image插件)。建议您以这种方式编写设置,使Django项目设置模块中的变量具有FOBI_PLUGIN_前缀。
定义并注册表单元素插件
逐步审查如何创建和注册插件及其小部件。请注意,如果将它们放置在名为fobi_form_elements.py的文件中,django-fobi将自动发现您的插件,该文件位于您的Django项目设置模块中的INSTALLED_APPS中列出的任何Django应用程序中。
path/to/sample_textarea/fobi_form_elements.py
单个表单元素插件通过其UID进行注册。
必需的导入。
from django import forms
from fobi.base import FormFieldPlugin, form_element_plugin_registry
from path.to.sample_textarea.forms import SampleTextareaForm
定义示例文本区域插件。
class SampleTextareaPlugin(FormFieldPlugin):
"""Sample textarea plugin."""
uid = "sample_textarea"
name = "Sample Textarea"
form = SampleTextareaForm
group = "Samples" # Group to which the plugin belongs to
def get_form_field_instances(self,
request=None,
form_entry=None,
form_element_entries=None,
**kwargs):
kwargs = {
'required': self.data.required,
'label': self.data.label,
'initial': self.data.initial,
'widget': forms.widgets.Textarea(attrs={})
}
return [(self.data.name, forms.CharField, kwargs),]
注册SampleTextareaPlugin插件。
form_element_plugin_registry.register(SampleTextareaPlugin)
请注意,如果您想定义一个纯展示元素,请使用fobi.base.FormElementPlugin进行子类化,而不是使用fobi.base.FormFieldPlugin。以内容插件(fobi.contrib.plugins.form_elements.content)的源代码为例。
例如,captcha和honeypot字段作为表单元素实现(子类化fobi.base.FormElementPlugin)。db_store表单处理插件不会保存这些元素的表单数据。如果您想保存表单元素数据,请从fobi.base.FormFieldPlugin继承。
隐藏表单元素插件,也应该将 is_hidden 属性设置为 True。默认情况下,它被设置为 False。这使得隐藏的表单元素在编辑模式下使用 django.forms.widgets.TextInput 小部件进行渲染。在视图模式下,将使用您在表单元素插件中指定的原始小部件。
可能会有这样的情况,当您需要在表单成功提交后对数据进行额外处理。在这种情况下,您需要在插件中定义一个 submit_plugin_form_data 方法,它接受以下参数
form_entry (fobi.models.FormEntry):正在提交的表单条目。
request (django.http.HttpRequest):Django HTTP 请求。
form (django.forms.Form):表单对象(一个有效的对象,其中包含 cleaned_data 属性)。
form_element_entries (fobi.models.FormElementEntry):给定 form_entry 的表单元素条目。
(**)kwargs:其他参数。
示例(来自 fobi.contrib.plugins.form_elements.fields.file)
def submit_plugin_form_data(self,
form_entry,
request,
form,
form_element_entries=None,
**kwargs):
"""Submit plugin form data."""
# Get the file path
file_path = form.cleaned_data.get(self.data.name, None)
if file_path:
# Handle the upload
saved_file = handle_uploaded_file(FILES_UPLOAD_DIR, file_path)
# Overwrite ``cleaned_data`` of the ``form`` with path to moved
# file.
form.cleaned_data[self.data.name] = "{0}{1}".format(
settings.MEDIA_URL, saved_file
)
# It's critically important to return the ``form`` with updated
# ``cleaned_data``
return form
在下面的示例中,原始表单正在被修改。如果您不想修改原始表单,请不要返回任何内容。
查看文件表单元素插件(fobi.contrib.plugins.form_elements.fields.file)以获取完整的示例。
path/to/sample_textarea/forms.py
为什么要有另一个文件来定义表单?只是为了保持代码干净和整洁,尽管您可以在 fobi_form_elements.py 模块中完美地定义所有插件表单,但它建议将其分开。
请注意,forms.py 不是一个自动发现的文件模式。您应该将所有表单元素插件注册在名为 fobi_form_elements.py 的模块中。
必需的导入。
from django import forms
from fobi.base import BasePluginForm
为 SampleTextareaPlugin 表单元素插件定义的表单。
class SampleTextareaForm(forms.Form, BasePluginForm):
"""Sample textarea form."""
plugin_data_fields = [
("name", ""),
("label", ""),
("initial", ""),
("required", False)
]
name = forms.CharField(label="Name", required=True)
label = forms.CharField(label="Label", required=True)
initial = forms.CharField(label="Initial", required=False)
required = forms.BooleanField(label="Required", required=False)
请注意,尽管代码中没有检查,但对于表单字段插件,以下字段应该在插件表单(BasePluginForm)和表单插件(FormFieldPlugin)中存在
name
在某些情况下,您可能想在数据保存之前对数据进行一些操作。为此,引入了 save_plugin_data 方法。
请参见以下 示例。
def save_plugin_data(self, request=None):
"""Saving the plugin data and moving the file."""
file_path = self.cleaned_data.get('file', None)
if file_path:
saved_image = handle_uploaded_file(IMAGES_UPLOAD_DIR, file_path)
self.cleaned_data['file'] = saved_image
path/to/sample_textarea/widgets.py
必需的导入。
from fobi.base import FormElementPluginWidget
定义基本插件小部件。
class BaseSampleTextareaPluginWidget(FormElementPluginWidget):
"""Base sample textarea plugin widget."""
# Same as ``uid`` value of the ``SampleTextareaPlugin``.
plugin_uid = "sample_textarea"
path/to/sample_layout/fobi_form_elements.py
在注册表中注册(在某些模块中,这些模块肯定会加载;在主题模块中这样做很方便)。
必需的导入。
from fobi.base import form_element_plugin_widget_registry
from path.to.sample_textarea.widgets import BaseSampleTextareaPluginWidget
定义特定于主题的插件。
class SampleTextareaPluginWidget(BaseSampleTextareaPluginWidget):
"""Sample textarea plugin widget."""
theme_uid = 'bootstrap3' # Theme for which the widget is loaded
media_js = [
'sample_layout/js/fobi.plugins.form_elements.sample_textarea.js',
]
media_css = [
'sample_layout/css/fobi.plugins.form_elements.sample_textarea.css',
]
注册小部件。
form_element_plugin_widget_registry.register(SampleTextareaPluginWidget)
表单元素插件最终步骤
现在,一切都准备好了,请确保将您的插件模块添加到 INSTALLED_APPS。
INSTALLED_APPS = (
# ...
'path.to.sample_textarea',
# ...
)
之后,转到终端并输入以下命令。
./manage.py fobi_sync_plugins
如果您的 HTTP 服务器正在运行,您将能够在编辑表单界面中看到新的插件。
仪表板 URL: http://127.0.0.1:8000/fobi/
请注意,您必须登录才能使用仪表板。如果您的新的插件没有出现,将 Django 的本地设置模块中的 FOBI_DEBUG 设置为 True,重新运行您的代码并检查控制台以获取错误通知。
创建新的表单处理插件
表单处理插件处理表单数据。django-fobi 随附了几个捆绑的表单处理插件,其中包括 db_store 和 mail 插件,它们负责将提交的表单数据保存到数据库并将数据邮寄给指定的收件人。表单中表单处理器的数量不受限制。某些表单处理器不可配置(例如,db_store 表单处理器不可配置),而其他表单处理器是可配置的(例如,mail、http_repost)。
您应该将表单处理器视为一个 Django 微应用,它可能有自己的模型、管理界面等。
默认情况下,可以在每个表单中使用多次表单处理器插件。如果您希望表单处理器插件在每个表单中只能使用一次,请将插件的 allow_multiple 属性设置为 False。
如上所述,django-fobi 包含几个内置的表单处理器插件。请检查源代码作为示例。
定义和注册表单处理器插件
让我们将这个插件命名为 sample_mail。插件目录的结构应如下所示。
path/to/sample_mail/
├── __init__.py
├── fobi_form_handlers.py # Where plugins are defined and registered
└── forms.py # Plugin configuration form
表单处理器插件应在 "fobi_form_handlers.py" 文件中注册。每个插件模块应放在您的 Django 项目设置中的 INSTALLED_APPS 中。
path/to/sample_mail/fobi_form_handlers.py
通过其 UID 注册单个表单处理器插件。
必需的导入。
import json
from django.core.mail import send_mail
from fobi.base import FormHandlerPlugin, form_handler_plugin_registry
from path.to.sample_mail.forms import SampleMailForm
定义 Sample 邮件处理器插件。
class SampleMailHandlerPlugin(FormHandlerPlugin):
"""Sample mail handler plugin."""
uid = "sample_mail"
name = _("Sample mail")
form = SampleMailForm
def run(self, form_entry, request, form, form_element_entries=None):
"""To be executed by handler."""
send_mail(
self.data.subject,
json.dumps(form.cleaned_data),
self.data.from_email,
[self.data.to_email],
fail_silently=True
)
注册插件
form_handler_plugin_registry.register(SampleMailHandlerPlugin)
某些表单处理器可配置,而另一些则不可配置。为了以用户友好的方式显示表单处理器设置,有时需要引入 plugin_data_repr 方法。其最简单的实现方式如下
def plugin_data_repr(self):
"""Human readable representation of plugin data.
:return string:
"""
return self.data.__dict__
path/to/sample_mail/forms.py
如果插件可配置,则具有配置数据。单个表单可以具有任意数量的相同插件。想象一下,您想要为不同的用户组分配不同的主题和附加正文文本。然后,您可以给表单分配两个表单处理器 mail 插件。当然,多次保存提交的表单数据是没有意义的,但这取决于用户。因此,如果插件可配置,则应有一个表单。
为什么要为定义表单创建另一个文件?只是为了保持代码的整洁和简洁,尽管您可以在模块 fobi_form_handlers.py 中完全定义所有插件表单,但建议将其分开。
请注意,forms.py 不是一个自动发现的文件模式。所有表单处理器插件都应在名为 fobi_form_handlers.py 的模块中注册。
必需的导入。
from django import forms
from django.utils.translation import gettext_lazy as _
from fobi.base import BasePluginForm
定义 Sample 邮件处理器插件的表单。
class MailForm(forms.Form, BasePluginForm):
"""Mail form."""
plugin_data_fields = [
("from_name", ""),
("from_email", ""),
("to_name", ""),
("to_email", ""),
("subject", ""),
("body", ""),
]
from_name = forms.CharField(label=_("From name"), required=True)
from_email = forms.EmailField(label=_("From email"), required=True)
to_name = forms.CharField(label=_("To name"), required=True)
to_email = forms.EmailField(label=_("To email"), required=True)
subject = forms.CharField(label=_("Subject"), required=True)
body = forms.CharField(
label=_("Body"),
required=False,
widget=forms.widgets.Textarea
)
在插件被处理之后,其所有数据都可用在 plugin_instance.data 容器中(例如,plugin_instance.data.subject 或 plugin_instance.data.from_name)。
优先执行顺序
某些表单处理器应该在其他表单处理器之前执行。一个很好的例子是表单的“邮件”和“db_save”表单处理器的组合。在“db_save”执行之后,“邮件”插件执行会导致表单数据提交失败。这就是为什么可以通过 FOBI_FORM_HANDLER_PLUGINS_EXECUTION_ORDER 设置变量来优先排序。
如果没有指定或留空,表单处理器插件将按发现顺序运行。所有未列入 FORM_HANDLER_PLUGINS_EXECUTION_ORDER 的表单处理器插件将在此列表中提及的插件之后运行。
FORM_HANDLER_PLUGINS_EXECUTION_ORDER = (
'http_repost',
'mail',
# The 'db_store' is left out intentionally, since it should
# be the last plugin to be executed.
)
表单处理器插件自定义操作
默认情况下,单个表单处理器插件至少有一个“删除”操作。如果插件可配置,则还有一个“编辑”操作。
对于您的某些插件,您可能想注册一个自定义操作。例如,“db_store”插件有一个,用于显示包含保存表单数据的表单的列表页面的链接。
在这种情况下,在您的表单处理器插件中定义一个 custom_actions 方法。该方法应返回一个三元组列表。在每个三元组中,第一个值是 URL,第二个值是标题,第三个值是 URL 的图标。
以下示例取自“db_store”插件。
def custom_actions(self):
"""Adding a link to view the saved form entries.
:return iterable:
"""
return (
(
reverse('fobi.contrib.plugins.form_handlers.db_store.view_saved_form_data_entries'),
_("View entries"),
'glyphicon glyphicon-list'
),
)
表单处理器插件最后步骤
不要忘记将表单处理器插件模块添加到 INSTALLED_APPS 中。
INSTALLED_APPS = (
# ...
'path.to.sample_mail',
# ...
)
之后,转到终端并输入以下命令。
./manage.py fobi_sync_plugins
如果您的 HTTP 服务器正在运行,您将能够在编辑表单界面中看到新的插件。
创建新的表单导入器插件
表单导入插件可以将来自某些外部数据源的数据导入到 django-fobi 表单格式中。表单导入的数量不受限制。表单导入插件以向导的形式实现(因为它们可能包含多个步骤)。
您应该将表单导入器视为一个 Django 微应用,它可能有自己的模型、管理界面等。
目前,django-fobi 只提供了一个捆绑的表单处理插件,即 mailchimp_importer,该插件负责将现有的 MailChimp 表单导入到 django-fobi。
定义并注册表单导入插件
让我们称该插件为 sample_importer。该插件目录应具有以下结构。
path/to/sample_importer/
├── templates
│ └── sample_importer
│ ├── 0.html
│ └── 1.html
├── __init__.py
├── fobi_form_importers.py # Where plugins are defined and registered
├── forms.py # Wizard forms
└── views.py # Wizard views
表单导入插件应在“fobi_form_importers.py”文件中注册。每个插件模块应放入您的 Django 项目设置中的 INSTALLED_APPS。
path/to/sample_importer/fobi_form_importers.py
单个表单导入插件通过其 UID 进行注册。
必需的导入。
from django.utils.translation import gettext_lazy as _
from fobi.form_importers import BaseFormImporter, form_importer_plugin_registry
from fobi.contrib.plugins.form_elements import fields
from path.to.sample_importer.views import SampleImporterWizardView
定义 Sample 导入插件。
class SampleImporterPlugin(FormHandlerPlugin):
"""Sample importer plugin."""
uid = 'sample_importer'
name = _("Sample importer")
wizard = SampleImporterWizardView
templates = [
'sample_importer/0.html',
'sample_importer/1.html',
]
# field_type (at importer): uid (django-fobi)
fields_mapping = {
# Implemented
'email': fields.email.UID,
'text': fields.text.UID,
'number': fields.integer.UID,
'dropdown': fields.select.UID,
'date': fields.date.UID,
'url': fields.url.UID,
'radio': fields.radio.UID,
# Transformed into something else
'address': fields.text.UID,
'zip': fields.text.UID,
'phone': fields.text.UID,
}
# Django standard: remote
field_properties_mapping = {
'label': 'name',
'name': 'tag',
'help_text': 'helptext',
'initial': 'default',
'required': 'req',
'choices': 'choices',
}
field_type_prop_name = 'field_type'
position_prop_name = 'order'
def extract_field_properties(self, field_data):
field_properties = {}
for prop, val in self.field_properties_mapping.items():
if val in field_data:
if 'choices' == val:
field_properties[prop] = "\n".join(field_data[val])
else:
field_properties[prop] = field_data[val]
return field_properties
form_importer_plugin_registry.register(SampleImporter)
path/to/sample_importer/forms.py
如上所述,表单导入以向导的形式实现。表单是向导步骤。
必需的导入。
from django import forms
from django.utils.translation import gettext_lazy as _
from sample_service_api import sample_api # Just an imaginary API client
定义 Sample 导入插件的表单。
class SampleImporterStep1Form(forms.Form):
"""First form the the wizard."""
api_key = forms.CharField(required=True)
class SampleImporterStep2Form(forms.Form):
"""Second form of the wizard."""
list_id = forms.ChoiceField(required=True, choices=[])
def __init__(self, *args, **kwargs):
self._api_key = None
if 'api_key' in kwargs:
self._api_key = kwargs.pop('api_key', None)
super(SampleImporterStep2Form, self).__init__(*args, **kwargs)
if self._api_key:
client = sample_api.Api(self._api_key)
lists = client.lists.list()
choices = [(l['id'], l['name']) for l in lists['data']]
self.fields['list_id'].choices = choices
path/to/sample_importer/views.py
向导视图。
必需的导入。
from sample_service_api import sample_api # Just an imaginary API client
from django.shortcuts import redirect
from django.urls import reverse
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
# For django LTE 1.8 import from `django.contrib.formtools.wizard.views`
from formtools.wizard.views import SessionWizardView
from path.to.sample_importer.forms import (
SampleImporterStep1Form,
SampleImporterStep2Form,
)
定义 Sample 导入插件的向导视图。
class SampleImporterWizardView(SessionWizardView):
"""Sample importer wizard view."""
form_list = [SampleImporterStep1Form, SampleImporterStep2Form]
def get_form_kwargs(self, step):
"""Get form kwargs (to be used internally)."""
if '1' == step:
data = self.get_cleaned_data_for_step('0') or {}
api_key = data.get('api_key', None)
return {'api_key': api_key}
return {}
def done(self, form_list, **kwargs):
"""After all forms are submitted."""
# Merging cleaned data into one dict
cleaned_data = {}
for form in form_list:
cleaned_data.update(form.cleaned_data)
# Connecting to sample client API
client = sample_client.Api(cleaned_data['api_key'])
# Fetching the form data
form_data = client.lists.merge_vars(
id={'list_id': cleaned_data['list_id']}
)
# We need the first form only
try:
form_data = form_data['data'][0]
except Exception as err:
messages.warning(
self.request,
_('Selected form could not be imported due errors.')
)
return redirect(reverse('fobi.dashboard'))
# Actually, import the form
form_entry = self._form_importer.import_data(
{'name': form_data['name'], 'user': self.request.user},
form_data['merge_vars']
)
redirect_url = reverse(
'fobi.edit_form_entry',
kwargs={'form_entry_id': form_entry.pk}
)
messages.info(
self.request,
_('Form {0} imported successfully.').format(form_data['name'])
)
return redirect("{0}".format(redirect_url))
表单导入插件最终步骤
不要忘记将表单导入插件模块添加到 INSTALLED_APPS。
INSTALLED_APPS = (
# ...
'path.to.sample_importer',
# ...
)
之后,转到终端并输入以下命令。
./manage.py fobi_sync_plugins
如果您的 HTTP 服务器正在运行,您将能够在仪表板表单界面(在所有捆绑主题中实现)中看到新插件。
创建表单回调
表单回调是在表单提交的各种阶段执行的额外钩子。
让我们将回调放在 foo 模块中。该插件目录应具有以下结构。
path/to/foo/
├── __init__.py
└── fobi_form_callbacks.py # Where callbacks are defined and registered
请参阅下面的回调示例。
必需的导入。
from fobi.constants import (
CALLBACK_BEFORE_FORM_VALIDATION,
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
CALLBACK_FORM_VALID,
CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS,
CALLBACK_FORM_INVALID,
)
from fobi.base import FormCallback, form_callback_registry
定义并注册回调
class SampleFooCallback(FormCallback):
"""Sample foo callback."""
stage = CALLBACK_FORM_VALID
def callback(self, form_entry, request, form):
"""Define your callback code here."""
print("Great! Your form is valid!")
form_callback_registry.register(SampleFooCallback)
将回调模块添加到 INSTALLED_APPS。
INSTALLED_APPS = (
# ...
'path.to.foo',
# ...
)
基于类的视图
视图
迁移到基于类的视图很简单。只需更改您项目的 urls.py。
urlpatterns = [
# ...
re_path(r'^fobi/', include('fobi.urls.class_based.view')),
re_path(r'^fobi/', include('fobi.urls.class_based.edit')),
# ...
]
要使用基于函数的视图,只需将上一行替换为
urlpatterns = [
# ...
re_path(r'^fobi/', include('fobi.urls.view')),
re_path(r'^fobi/', include('fobi.urls.edit')),
# ...
]
权限
基于类的权限仅与基于类的视图一起工作。
示例
from fobi.permissions.definitions import edit_form_entry_permissions
from fobi.permissions.generic import BasePermission
from fobi.permissions.helpers import (
any_permission_required_func, login_required,
)
class EditFormEntryPermission(BasePermission):
"""Permission to edit form entries."""
def has_permission(self, request, view) -> bool:
return login_required(request) and any_permission_required_func(
edit_form_entry_permissions
)(request.user)
def has_object_permission(self, request, view, obj) -> bool:
return login_required(request) and any_permission_required_func(
edit_form_entry_permissions
)(request.user) and obj.user == request.user
然后在您的视图中
from fobi.views.class_based import EditFormEntryView
class MyEditFormEntryView(EditFormEntryView):
"""EditFormEntryView."""
permission_classes = (EditFormEntryPermission,)
建议
表单的自定义操作
有时,您可能想为表单指定不同的操作。虽然可以在“表单属性”选项卡中定义自定义表单操作(action 字段),但建议您使用 http_repost 插件,因为这样表单仍然会在本地进行验证,然后再将有效数据原样发送到目标端点。
请注意,在两种情况下,如果目标端点启用了 CSRF 保护,您的 POST 请求将导致错误。
当您想定制太多东西时
django-fobi,凭借其灵活的表单元素、表单处理器和表单回调,非常可定制。然而,可能会有需要覆盖整个视图以满足您需求的情况。请查看 FeinCMS 集成 或 DjangoCMS 集成 作为此类示例。您还可以比较原始视图 fobi.views.view_form_entry 的代码与小部件的代码,以更好地了解您的情况中可以更改的内容。如果您需要好的建议,请随时问我。
主题
django-fobi 带有主题 API。虽然有一些现成的主题
“Bootstrap 3”主题
“Foundation 5”主题
“简单”主题(带有类似Django admin风格的编辑界面)
“DjangoCMS admin风格”主题(这是一种另一个简单主题,其编辑界面采用 djangocms-admin-style 风格)
很明显,在编辑和查看表单时,存在两种视图类型。
“查看视图”,当表单已制作完成后,面向网站最终用户/访客。
“编辑视图”(构建视图),授权用户在此处构建他们的表单。
“Bootstrap 3”和“Foundation 5”主题都为“查看视图”和“编辑视图”使用了相同的样式。
“简单”和“DjangoCMS admin风格”主题只为“编辑视图”进行样式设计。而“查看视图”基本上是空的,如截图所示 [2.6]。
请注意,创建一个全新的主题可能非常耗时。相反,建议扩展现有主题,或者在需要大量自定义的情况下,基于现有主题创建自己的主题(只需将所需的主题复制到您的项目目录中,然后进一步工作)。
可以为所有“查看”和“编辑”操作使用不同的模板(参见“simple”主题的源代码)。“Bootstrap 3”和“Foundation 5”主题看起来都很不错。尽管如果您不能使用这些中的任何一个,那么“简单”主题是最好的起点,因为它看起来就像django-admin。
创建一个新主题
让我们将主题放置在 sample_theme 模块中。主题目录应具有以下结构。
path/to/sample_theme/
├── static
│ ├── css
│ │ └── sample_theme.css
│ └── js
│ └── sample_theme.js
├── templates
│ └── sample_theme
│ ├── _base.html
│ ├── add_form_element_entry.html
│ ├── ...
│ └── view_form_entry_ajax.html
├── __init__.py
├── fobi_form_elements.py
└── fobi_themes.py # Where themes are defined and registered
请参见下面的主题示例。
from django.utils.translation import gettext_lazy as _
from fobi.base import BaseTheme, theme_registry
class SampleTheme(BaseTheme):
"""Sample theme."""
uid = 'sample'
name = _("Sample")
media_css = (
'sample_theme/css/sample_theme.css',
'css/fobi.core.css',
)
media_js = (
'js/jquery-1.10.2.min.js',
'jquery-ui/js/jquery-ui-1.10.3.custom.min.js',
'js/jquery.slugify.js',
'js/fobi.core.js',
'sample_theme/js/sample_theme.js',
)
# Form element specific
form_element_html_class = 'form-control'
form_radio_element_html_class = 'radio'
form_element_checkbox_html_class = 'checkbox'
form_edit_form_entry_option_class = 'glyphicon glyphicon-edit'
form_delete_form_entry_option_class = 'glyphicon glyphicon-remove'
form_list_container_class = 'list-inline'
# Templates
master_base_template = 'sample_theme/_base.html'
base_template = 'sample_theme/base.html'
form_ajax = 'sample_theme/snippets/form_ajax.html'
form_snippet_template_name = 'sample_theme/snippets/form_snippet.html'
form_properties_snippet_template_name = 'sample_theme/snippets/form_properties_snippet.html'
messages_snippet_template_name = 'sample_theme/snippets/messages_snippet.html'
add_form_element_entry_template = 'sample_theme/add_form_element_entry.html'
add_form_element_entry_ajax_template = 'sample_theme/add_form_element_entry_ajax.html'
add_form_handler_entry_template = 'sample_theme/add_form_handler_entry.html'
add_form_handler_entry_ajax_template = 'sample_theme/add_form_handler_entry_ajax.html'
create_form_entry_template = 'sample_theme/create_form_entry.html'
create_form_entry_ajax_template = 'bootstrap3/create_form_entry_ajax.html'
dashboard_template = 'sample_theme/dashboard.html'
edit_form_element_entry_template = 'sample_theme/edit_form_element_entry.html'
edit_form_element_entry_ajax_template = 'sample_theme/edit_form_element_entry_ajax.html'
edit_form_entry_template = 'sample_theme/edit_form_entry.html'
edit_form_entry_ajax_template = 'sample_theme/edit_form_entry_ajax.html'
edit_form_handler_entry_template = 'sample_theme/edit_form_handler_entry.html'
edit_form_handler_entry_ajax_template = 'sample_theme/edit_form_handler_entry_ajax.html'
form_entry_submitted_template = 'sample_theme/form_entry_submitted.html'
form_entry_submitted_ajax_template = 'sample_theme/form_entry_submitted_ajax.html'
view_form_entry_template = 'sample_theme/view_form_entry.html'
view_form_entry_ajax_template = 'sample_theme/view_form_entry_ajax.html'
注册 SampleTheme 插件。
theme_registry.register(SampleTheme)
有时您可能想要将额外的属性附加到主题上,以便以后在模板中使用它们(记住,当前主题对象始终在模板中以 fobi_theme 的名称可用)。
在这种情况下,您需要在项目的设置模块中定义一个变量,称为 FOBI_CUSTOM_THEME_DATA。以下代码作为示例。
# `django-fobi` custom theme data for to be displayed in third party apps
# like `django-registraton`.
FOBI_CUSTOM_THEME_DATA = {
'bootstrap3': {
'page_header_html_class': '',
'form_html_class': 'form-horizontal',
'form_button_outer_wrapper_html_class': 'control-group',
'form_button_wrapper_html_class': 'controls',
'form_button_html_class': 'btn',
'form_primary_button_html_class': 'btn-primary pull-right',
},
'foundation5': {
'page_header_html_class': '',
'form_html_class': 'form-horizontal',
'form_button_outer_wrapper_html_class': 'control-group',
'form_button_wrapper_html_class': 'controls',
'form_button_html_class': 'radius button',
'form_primary_button_html_class': 'btn-primary',
},
'simple': {
'page_header_html_class': '',
'form_html_class': 'form-horizontal',
'form_button_outer_wrapper_html_class': 'control-group',
'form_button_wrapper_html_class': 'submit-row',
'form_button_html_class': 'btn',
'form_primary_button_html_class': 'btn-primary',
}
}
现在,您可以在模板中像下面这样访问定义的额外属性。
<div class="{{ fobi_theme.custom_data.form_button_wrapper_html_class }}">
您可能想要删除页脚文本或更改它。在项目的设置模块中定义一个变量,称为 FOBI_THEME_FOOTER_TEXT。以下代码作为示例。
FOBI_THEME_FOOTER_TEXT = gettext('© django-fobi example site 2014')
以下列出的是主题的属性
base_edit
base_view
为了简化主题化,制作了一些通用模板。其中一些您可能永远不需要覆盖。另一些,您可能希望覆盖。
您可能希望在自己的自定义主题实现中重新编写的模板用三个星号(***)标记。
generic
├── snippets
│ ├── form_ajax.html
│ ├── form_edit_ajax.html
│ ├── *** form_properties_snippet.html
│ ├── *** form_snippet.html
│ ├── --- form_edit_snippet.html (does not exist in generic templates)
│ ├── --- form_view_snippet.html (does not exist in generic templates)
│ ├── form_view_ajax.html
│ └── messages_snippet.html
│
├── _base.html
├── add_form_element_entry.html
├── add_form_element_entry_ajax.html
├── add_form_handler_entry.html
├── add_form_handler_entry_ajax.html
├── base.html
├── create_form_entry.html
├── create_form_entry_ajax.html
├── *** dashboard.html
├── edit_form_element_entry.html
├── edit_form_element_entry_ajax.html
├── edit_form_entry.html
├── *** edit_form_entry_ajax.html
├── edit_form_handler_entry.html
├── edit_form_handler_entry_ajax.html
├── form_entry_submitted.html
├── *** form_entry_submitted_ajax.html
├── *** theme.html
├── view_form_entry.html
└── view_form_entry_ajax.html
在上述所有模板中,_base.html 模板最受 Bootstrap 3 主题的影响。
修改现有主题
如上所述,从头开始创建自己的主题可能成本高昂。相反,您可以通过覆盖/重用现有主题,并最小努力地根据您的需求对其进行更改。请参见 覆盖简单主题 示例。要查看其实际效果,请使用 settings_override_simple_theme 选项运行项目。
./manage.py runserver --settings=settings_override_simple_theme
以下详细说明。
目录结构
override_simple_theme/
├── static
│ └── override_simple_theme
│ ├── css
│ │ └── override-simple-theme.css
│ └── js
│ └── override-simple-theme.js
│
├── templates
│ └── override_simple_theme
│ ├── snippets
│ │ └── form_ajax.html
│ └── base_view.html
├── __init__.py
└── fobi_themes.py # Where themes are defined and registered
fobi_themes.py
覆盖“simple”主题。
__all__ = ('MySimpleTheme',)
from fobi.base import theme_registry
from fobi.contrib.themes.simple.fobi_themes import SimpleTheme
class MySimpleTheme(SimpleTheme):
"""My simple theme, inherited from `SimpleTheme` theme."""
html_classes = ['my-simple-theme',]
base_view_template = 'override_simple_theme/base_view.html'
form_ajax = 'override_simple_theme/snippets/form_ajax.html'
注册覆盖的主题。请注意,将 force 参数设置为 True 是很重要的,以便覆盖原始主题。强制只能应用一次(对于覆盖的元素)。
theme_registry.register(MySimpleTheme, force=True)
templates/override_simple_theme/base_view.html
{% extends "simple/base_view.html" %}
{% load static %}
{% block stylesheets %}
<link
href="{% static 'override_simple_theme/css/override-simple-theme.css' %}"
rel="stylesheet" media="all" />
{% endblock stylesheets %}
{% block main-wrapper %}
<div id="sidebar">
<h2>It's easy to override a theme!</h2>
</div>
{{ block.super }}
{% endblock main-wrapper %}
templates/override_simple_theme/snippets/form_ajax.html
{% extends "fobi/generic/snippets/form_ajax.html" %}
{% block form_html_class %}basic-grey{% endblock %}
表单向导
基础知识
使用表单向导,您可以跨多个页面拆分表单。状态保存在后端之一(目前是会话后端)。数据处理将延迟到最终表单提交时。
在 django-fobi 中,向导的工作方式如下:
表单向导中的表单数量不受限制。
表单回调和处理器在表单向导中被完全忽略。相反,表单向导特定的处理器(表单向导处理器)将接管在最终步骤中处理表单数据。
捆绑的表单向导处理器插件
以下是表单向导处理器插件的简要概述。有关详细信息,请参阅每个插件目录中的 README.rst 文件。
与第三方应用和框架的集成
django-fobi 已成功集成到多个不同的第三方应用和框架中,例如:Django REST 框架、Django CMS、FeinCMS、Mezzanine 和 Wagtail。
当然,集成到 CMS 是一种情况,集成到 REST 框架则是另一种。在 REST 框架中,我们不再有表单本身。上下文非常不同。表单数据的处理显然应该以不同的方式进行。组装表单类是不够的(在 Django REST 框架的情况下,我们组装序列化器类)。
为了处理这种级别的集成,引入了两种额外的插件类型
集成表单元素插件
集成表单处理器插件
这些插件负责以正确的方式表示要集成的包中的表单元素,并处理提交的表单数据。
子包中有可用的 附加文档。
集成表单元素插件示例
示例取自 此处。
base.py
定义表单元素插件。
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import EmailField
from fobi.base import IntegrationFormFieldPlugin
from fobi.contrib.apps.drf_integration import UID as INTEGRATE_WITH_UID
from fobi.contrib.apps.drf_integration.base import (
DRFIntegrationFormElementPluginProcessor,
DRFSubmitPluginFormDataMixin,
)
from fobi.contrib.apps.drf_integration.form_elements.fields.email import UID
class EmailInputPlugin(IntegrationFormFieldPlugin,
DRFSubmitPluginFormDataMixin):
"""EmailField plugin."""
uid = UID
integrate_with = INTEGRATE_WITH_UID
name = _("Decimal")
group = _("Fields")
def get_custom_field_instances(self,
form_element_plugin,
request=None,
form_entry=None,
form_element_entries=None,
**kwargs):
"""Get form field instances."""
field_kwargs = {
'required': form_element_plugin.data.required,
'initial': form_element_plugin.data.initial,
'label': form_element_plugin.data.label,
'help_text': form_element_plugin.data.help_text,
'max_length': form_element_plugin.data.max_length,
}
return [
DRFIntegrationFormElementPluginProcessor(
field_class=EmailField,
field_kwargs=field_kwargs
)
]
fobi_integration_form_elements.py
注册插件。注意名称模式 fobi_integration_form_elements。
from fobi.base import integration_form_element_plugin_registry
from .base import EmailInputPlugin
integration_form_element_plugin_registry.register(EmailInputPlugin)
之后不要忘记在 INSTALLED_APPS 中列出您的插件。
集成表单处理器插件示例
示例取自 此处。
base.py
定义表单处理器插件。
import logging
from mimetypes import guess_type
import os
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from fobi.base import IntegrationFormHandlerPlugin
from fobi.helpers import extract_file_path
from fobi.contrib.apps.drf_integration import UID as INTEGRATE_WITH_UID
from fobi.contrib.apps.drf_integration.base import get_processed_serializer_data
from . import UID
class MailHandlerPlugin(IntegrationFormHandlerPlugin):
"""Mail handler form handler plugin.
Can be used only once per form.
"""
uid = UID
name = _("Mail")
integrate_with = INTEGRATE_WITH_UID
def run(self,
form_handler_plugin,
form_entry,
request,
form_element_entries=None,
**kwargs):
"""Run."""
base_url = form_handler_plugin.get_base_url(request)
serializer = kwargs['serializer']
# Clean up the values, leave our content fields and empty values.
field_name_to_label_map, cleaned_data = get_processed_serializer_data(
serializer,
form_element_entries
)
rendered_data = form_handler_plugin.get_rendered_data(
serializer.validated_data,
field_name_to_label_map,
base_url
)
files = self._prepare_files(request, serializer)
form_handler_plugin.send_email(rendered_data, files)
def _prepare_files(self, request, serializer):
"""Prepares the files for being attached to the mail message."""
files = {}
def process_path(file_path, imf):
"""Processes the file path and the file."""
if file_path:
file_path = file_path.replace(
settings.MEDIA_URL,
os.path.join(settings.MEDIA_ROOT, '')
)
mime_type = guess_type(imf.name)
files[field_name] = (
imf.name,
''.join([c for c in imf.chunks()]),
mime_type[0] if mime_type else ''
)
for field_name, imf in request.FILES.items():
try:
file_path = serializer.validated_data.get(field_name, '')
process_path(file_path, imf)
except Exception as err:
file_path = extract_file_path(imf.name)
process_path(file_path, imf)
return files
fobi_integration_form_handlers.py
注册插件。注意名称模式 fobi_integration_form_handlers。
from fobi.base import integration_form_handler_plugin_registry
from .base import MailHandlerPlugin
integration_form_handler_plugin_registry.register(MailHandlerPlugin)
之后不要忘记在 INSTALLED_APPS 中列出您的插件。
权限
插件系统允许管理员指定每个插件的访问权限。django-fobi 权限基于 Django 用户和用户组。访问权限可以通过 Django 管理员(“/admin/fobi/formelement/”,“/admin/fobi/formhandler/”)进行管理。如果用户没有访问插件的权限,即使它已被添加到表单中,它也不会出现在他的表单上(想象一下,您曾经授予所有用户使用新闻插件的权利,但后来决定仅将其限制为仅限工作人员组)。请注意,超级用户可以访问所有插件。
Plugin access rights management interface in Django admin
┌──────────────────────────┬───────────────────────┬───────────────────────┐
│ `Plugin` │ `Users` │ `Groups` │
├──────────────────────────┼───────────────────────┼───────────────────────┤
│ Text │ John Doe │ Form builder users │
├──────────────────────────┼───────────────────────┼───────────────────────┤
│ Textarea │ │ Form builder users │
├──────────────────────────┼───────────────────────┼───────────────────────┤
│ File │ Oscar, John Doe │ Staff members │
├──────────────────────────┼───────────────────────┼───────────────────────┤
│ URL │ │ Form builder users │
├──────────────────────────┼───────────────────────┼───────────────────────┤
│ Hidden │ │ Form builder users │
└──────────────────────────┴───────────────────────┴───────────────────────┘
管理命令
有多个管理命令可用。
fobi_find_broken_entries。寻找由于系统中不再存在的某个插件而出现的损坏的表单元素/处理器条目。
fobi_sync_plugins。每次向 django-fobi 添加新插件时都应该运行。
fobi_update_plugin_data。一个机制,用于在插件发生变化后更新现有的插件数据,以防数据无效。为了使其正常工作,每个插件都应该实现一个 update 方法,其中包含数据更新的过程。
调整
您可以在Django项目的设置模块中覆盖许多 django-fobi 设置。
FOBI_RESTRICT_PLUGIN_ACCESS(布尔值):如果设置为True,则启用(Django)仪表插件权限系统。默认为True。将此设置为False使所有插件对所有用户可用。
FOBI_DEFAULT_THEME(字符串):活动(默认)主题UID。默认为“bootstrap3”。
FORM_HANDLER_PLUGINS_EXECUTION_ORDER(元组列表):表单处理程序执行的顺序。有关详细信息,请参阅“优先执行顺序”部分。
有关特定贡献插件的调整,请参阅插件目录中的文档。
捆绑插件和主题
django-fobi 包含多个捆绑的表单元素和表单处理程序插件,以及可以立即使用的主题。
捆绑的表单元素插件
以下是对表单元素插件的简要概述。有关详细信息,请参阅每个插件的 README.rst 文件。
字段
带星号(*)的字段属于文本元素。可以为文本元素提供 动态初始值。
内容/展示
内容插件是展示插件,可以使您的表单看起来更完整、内容更丰富。
内容图片:插入图片。
内容图片URL:插入图片URL。
内容Markdown:添加Markdown文本。
内容富文本:添加富文本(基于 django-ckeditor 包)。
内容文本:添加文本。
内容视频:添加嵌入YouTube或Vimeo视频。
安全
CAPTCHA:CAPTCHA集成,需要 django-simple-captcha 包。
ReCAPTCHA:CAPTCHA集成,需要 django-recaptcha 包。
隐形ReCAPTCHA:Google隐形ReCAPTCHA集成,无额外依赖。
MPTT字段
测试
测试插件仅用于开发目的。
假数据:仅用于开发目的。
捆绑表单处理器插件
以下是对表单处理器插件的简要概述。有关详细信息,请参阅每个插件目录中的README.rst文件。
捆绑主题
以下是对主题的简要概述。有关详细信息,请参阅每个主题目录中的README.rst文件。
Bootstrap 3:Bootstrap 3主题。
Foundation 5:Foundation 5主题。
简单:基本主题,表单编辑风格与Django管理后台类似。
DjangoCMS管理风格:基本主题,表单编辑风格与djangocms-admin-style类似。
第三方插件和主题
著名第三方插件列表
fobi-phonenumber - Fobi PhoneNumber表单字段插件。使用 phonenumber_field.formfields.PhoneNumberField 和 phonenumber_field.widgets.PhoneNumberPrefixWidget。
HTML5字段
以下HTML5字段在相应的捆绑插件中得到支持
日期
日期时间
电子邮件
最大值
最小值
数字
网址
占位符
类型
通过 fobi.contrib.plugins.form_elements.fields.input 支持,HTML5字段的兼容性扩展到以下字段
自动完成
自动聚焦
列表
多选
模式
步长
使用GET参数加载初始数据
可以使用GET参数提供表单的初始数据。
在这种情况下,除了字段值外,还应提供一个名为“fobi_initial_data”的额外参数,该参数不必持有值。例如,如果您的表单包含名为“email”和“age”的字段,并且您想使用GET参数为这些字段提供初始值,则应将表单的URL构造如下
http://127.0.0.1:8001/fobi/view/test-form/?fobi_initial_data&email=test@example.com&age=19
动态初始值
可以为任何文本元素提供动态初始值。为了做到这一点,您应该使用内置的上下文处理器或创建自己的处理器。唯一的要求是将所有应公开在表单中的值存储为名为 fobi_dynamic_values 的字典键。请注意,传递原始请求对象可能在许多方面都是不安全的。目前,正在将请求对象的简化版本作为上下文变量传递。
TEMPLATES = [
{
# ...
'OPTIONS': {
# ...
'context_processors': [
# ...
"fobi.context_processors.theme", # Important!
"fobi.context_processors.dynamic_values", # Optional
]
},
},
]
def dynamic_values(request):
return {
'fobi_dynamic_values': {
'request': StrippedRequest(request),
'now': datetime.datetime.now(),
'today': datetime.date.today(),
}
}
在您的GUI中,您应按以下方式引用初始值
{{ request.path }} {{ now }} {{ today }}
请注意,不应提供 fobi_dynamic_values。 作为前缀。目前,以下变量在 fobi.context_processors.dynamic_values 上下文处理器中可用
- request: Stripped HttpRequest object.
- request.path: A string representing the full path to the requested
page, not including the scheme or domain.
- request.get_full_path(): Returns the path, plus an appended query
string, if applicable.
- request.is_secure(): Returns True if the request is secure; that
is, if it was made with HTTPS.
- request.is_ajax(): Returns True if the request was made via an
XMLHttpRequest, by checking the HTTP_X_REQUESTED_WITH header for the
string 'XMLHttpRequest'.
- request.META: A stripped down standard Python dictionary containing
the available HTTP headers.
- HTTP_ACCEPT_ENCODING: Acceptable encodings for the response.
- HTTP_ACCEPT_LANGUAGE: Acceptable languages for the response.
- HTTP_HOST: The HTTP Host header sent by the client.
- HTTP_REFERER: The referring page, if any.
- HTTP_USER_AGENT: The client’s user-agent string.
- QUERY_STRING: The query string, as a single (un-parsed) string.
- REMOTE_ADDR: The IP address of the client.
- request.user: Authenticated user.
- request.user.email:
- request.user.get_username(): Returns the username for the user.
Since the User model can be swapped out, you should use this
method instead of referencing the username attribute directly.
- request.user.get_full_name(): Returns the first_name plus the
last_name, with a space in between.
- request.user.get_short_name(): Returns the first_name.
- request.user.is_anonymous():
- now: datetime.datetime.now()
- today: datetime.date.today()
已提交的表单元素插件值
虽然一些表单元素插件的值是直接提交的,但其他一些需要额外的处理。考虑到以下3种行为类型
“val”:值直接发送。
“repr”:使用(可读)值的表示。
“mix”:值为直接和可读表示的混合。
以下插件已配置为可配置的,以便开发人员可以在项目的设置中选择所需的行为
FOBI_FORM_ELEMENT_CHECKBOX_SELECT_MULTIPLE_SUBMIT_VALUE_AS
FOBI_FORM_ELEMENT_RADIO_SUBMIT_VALUE_AS
FOBI_FORM_ELEMENT_SELECT_SUBMIT_VALUE_AS
FOBI_FORM_ELEMENT_SELECT_MULTIPLE_SUBMIT_VALUE_AS
FOBI_FORM_ELEMENT_SELECT_MODEL_OBJECT_SUBMIT_VALUE_AS
FOBI_FORM_ELEMENT_SELECT_MULTIPLE_MODEL_OBJECTS_SUBMIT_VALUE_AS
请参阅以下每个插件中的 README.rst 以获取更多信息。
使用第三方库渲染表单
您可能希望使用第三方库如 django-crispy-forms、django-floppyforms 或其他替代方案来渲染您的表单。
为此,您应该覆盖您选择的主题中使用的“snippets/form_snippet.html”。然后,您的模板将类似于以下内容(在执行此操作之前,请确保设置/配置您的第三方表单渲染库)。
使用 django-crispy-forms
{% load crispy_forms_tags fobi_tags %}
{% block form_non_field_and_hidden_errors %}
{% get_form_hidden_fields_errors form as form_hidden_fields_errors %}
{% if form.non_field_errors or form_hidden_fields_errors %}
{% include fobi_theme.form_non_field_and_hidden_errors_snippet_template %}
{% endif %}
{% endblock form_non_field_and_hidden_errors %}
{% crispy form %}
使用 django-floppyforms
{% load floppyforms fobi_tags %}
{% block form_non_field_and_hidden_errors %}
{% get_form_hidden_fields_errors form as form_hidden_fields_errors %}
{% if form.non_field_errors or form_hidden_fields_errors %}
{% include fobi_theme.form_non_field_and_hidden_errors_snippet_template %}
{% endif %}
{% endblock form_non_field_and_hidden_errors %}
{% form form %}
请参阅覆盖简单主题示例。
导入/导出表单
可能存在您在多个实例上运行 django-fobi 的情况,并且已经在其中一个实例上花费了一些时间制作表单,而希望在其他实例上重用这些表单。当然,您可以在 GUI 中重新创建整个表单,但我们可以做得更好。可以将表单导出为 JSON 格式并再次导入导出的表单。最好是在相同的 django-fobi 版本上运行这两个实例,否则导入可能会损坏(尽管它可能仍然工作)。处理缺少插件错误的方法有很多,但选择的方法(您目前还没有完全控制)是最安全的(导入所有可能的插件,但警告用户关于错误)。如果两个实例具有相同的表单元素和表单处理插件集合,导入应该会很顺利。不过,也有可能导入忽略缺少的表单元素和表单处理插件。您将收到适当的提醒,但导入将继续,忽略损坏的插件数据。
翻译
可用的翻译
英语是主要语言。以下翻译可用(核心和插件)
覆盖翻译
可能存在您想要覆盖某些翻译的情况。通过在项目中引入自定义区域路径可以轻松实现。
以下是一个覆盖一些英语插件标签的好例子。
按以下方式运行示例项目
cd examples/simple/
./manage.py runserver --settings=settings.alternative_labels
在所给的示例中,“布尔值”和“复选框选择多个”插件名称分别重命名为“复选框”和“多个复选框”。
所有内置插件的 name 值几乎等同于插件的 uid 值。默认情况下,插件按 uid 值排序。当您覆盖插件的 name 时,排序会中断。因此,建议在您的设置模块中将 FOBI_SORT_PLUGINS_BY_VALUE 设置为 True。默认值是 False,这意味着插件按它们的 uid 值排序。
FOBI_SORT_PLUGINS_BY_VALUE = True
调试
默认情况下,调试是关闭的。这意味着无法显示的损坏的表单条目(即带有损坏数据的条目),将直接跳过。在生产环境中这是安全的。尽管如此,您肯定希望在不开发环境中看到这些损坏的条目。将 FOBI_DEBUG 设置为 True,以在项目的 settings.py 中这样做。
大多数错误都被记录(DEBUG)。如果您编写了一个插件,并且它以某种方式没有出现在可用的插件列表中,请运行以下管理命令,因为它不仅同步您的插件到数据库中,而且也是检查可能错误的好方法。
./manage.py fobi_sync_plugins
运行以下命令以识别损坏的插件。
./manage.py fobi_find_broken_entries
如果您有引用已缺失(未注册、删除、未能加载)的表单元素或表单处理程序插件的表单,您将得到适当的异常。尽管在开发过程中获取此类故障的即时错误消息是可以接受的,但在生产环境中并不合适。因此,有两个设置与缺失的(未找到的)表单元素和表单处理程序插件相关。
FOBI_DEBUG:无论如何,请将其设置为True。密切注意错误日志。
FOBI_FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS:如果您希望在缺失表单元素插件时不会显示错误,请在设置模块中将此设置为False。默认值为True。
FOBI_FAIL_ON_MISSING_FORM_HANDLER_PLUGINS:如果您希望在缺失表单元素处理程序时不会显示错误,请在设置模块中将此设置为False。默认值为True。
测试
项目由测试(功能测试和浏览器测试)覆盖。
要测试所有支持的Python/Django版本,请输入
tox
要针对特定环境进行测试,请输入
tox -e py37-django21
要仅测试您的工作环境,请输入
./runtests.py
要运行工作环境中给定测试模块的单个测试类,请输入
./runtests.py src/fobi/tests/test_browser_build_dynamic_forms.py::FobiBrowserBuldDynamicFormsTest -k "test_2004_submit_form"
假设您已安装所有要求。如果没有,请首先安装测试要求
pip install -r examples/requirements/test.txt
浏览器测试
对于浏览器测试,您可以选择Firefox、无头Firefox和PhantomJS。PhantomJS速度更快,无头Firefox也很快,但正常Firefox测试可以告诉您更多(因为您可以看到屏幕上确切发生的事情)。两种情况都需要一些努力,并且在安装方面都有缺点(尽管一旦安装完成,它们都可以正常工作)。
Firefox的最新版本通常不受Selenium支持。Python(2.53.6)的当前Selenium版本可以很好地与Firefox 47一起工作。因此,您最好使用自定义的Firefox而不是系统Firefox。
对于PhantomJS,您需要安装NodeJS。
设置ChromeDriver
下载并安装与您的浏览器匹配的ChromeDriver版本
pip install get-chromedriver-py get-chromedriver-py
在CHROME_DRIVER_EXECUTABLE_PATH设置中指定您的ChromeDriver的完整路径。例如:
from chromedriver_py import binary_path as CHROME_DRIVER_EXECUTABLE_PATH
之后,您的Selenium测试将正常工作。
设置Firefox 47
从这里下载Firefox 47,并将其解压缩到/usr/lib/firefox47/
在FIREFOX_BIN_PATH设置中指定您的Firefox的完整路径。例如:
FIREFOX_BIN_PATH = '/usr/lib/firefox47/firefox'
如果您设置为使用系统Firefox,请删除或取消注释FIREFOX_BIN_PATH设置。
之后,您的Selenium测试将正常工作。
设置无头Firefox
安装用于在无头模式下启动Firefox的xvfb包。
sudo apt-get install xvfb
使用无头Firefox运行测试。
./scripts/runtests.sh
或者使用无头Firefox运行tox测试。
./scripts/tox.sh
或者使用无头Firefox运行特定的tox测试。
./scripts/tox.sh -e py36-django111
设置PhantomJS
您还可以以无头模式运行测试(更快)。为此,您需要PhantomJS。
安装PhantomJS及其依赖项。
curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh sudo bash nodesource_setup.sh sudo apt-get install nodejs sudo apt-get install build-essential libssl-dev sudo npm -g install phantomjs-prebuilt
指定PHANTOM_JS_EXECUTABLE_PATH设置。例如:
PHANTOM_JS_EXECUTABLE_PATH = ""
如果您想使用Firefox进行测试,请删除或取消注释PHANTOM_JS_EXECUTABLE_PATH设置。
编写文档
保持以下层次结构。
=====
title
=====
header
======
sub-header
----------
sub-sub-header
~~~~~~~~~~~~~~
sub-sub-sub-header
##################
sub-sub-sub-sub-header
^^^^^^^^^^^^^^^^^^^^^^
sub-sub-sub-sub-sub-header
++++++++++++++++++++++++++
故障排除
如果您遇到FormElementPluginDoesNotExist或FormHandlerPluginDoesNotExist异常,请确保您已将插件列在项目的settings模块中。
贡献
许可证
GPL-2.0-only OR LGPL-2.1-or-later
支持
有关任何安全问题,请联系作者部分中提供的电子邮件。对于整体问题,请访问GitHub。
屏幕截图
Bootstrap3主题
仪表板
仪表板
创建表单
创建表单
查看/编辑表单
表单元素
编辑表单 - 表单元素选项卡激活,尚未添加元素
编辑表单 - 表单元素选项卡激活,添加表单元素菜单
编辑表单 - 添加表单元素(URL插件)
编辑表单 - 表单元素选项卡激活,包含表单元素
表单处理器
编辑表单 - 表单处理器选项卡激活,尚未添加处理器
编辑表单 - 表单处理器选项卡激活,添加表单处理器菜单
编辑表单 - 添加表单处理器(邮件插件)
编辑表单 - 表单处理器选项卡激活,包含表单处理器
编辑表单 - 表单属性选项卡激活
查看表单
查看表单 - 表单提交(感谢页面)
编辑表单 - 添加表单元素(视频插件)
编辑表单 - 添加表单元素(布尔插件)
编辑表单
查看表单
简单主题
查看/编辑表单
编辑表单 - 表单元素选项卡激活,包含表单元素
编辑表单 - 表单元素选项卡激活,添加表单元素菜单
编辑表单 - 添加表单元素(隐藏插件)
编辑表单 - 表单处理器选项卡激活,包含表单处理器
编辑表单 - 表单属性选项卡激活
查看表单