diff --git a/bootstrap3/templates/bootstrap3/index.html b/bootstrap3/templates/bootstrap3/index.html
index 085f1850..1433775e 100644
--- a/bootstrap3/templates/bootstrap3/index.html
+++ b/bootstrap3/templates/bootstrap3/index.html
@@ -15,9 +15,11 @@
diff --git a/bootstrap4/templates/bootstrap4/index.html b/bootstrap4/templates/bootstrap4/index.html
index 883f0799..d7ebfa37 100644
--- a/bootstrap4/templates/bootstrap4/index.html
+++ b/bootstrap4/templates/bootstrap4/index.html
@@ -14,8 +14,11 @@
Bootstrap 4(current)
-
- Semantic UI(current)
+
+ Bulma
+
+
+ Semantic UI
diff --git a/bulma/__init__.py b/bulma/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bulma/admin.py b/bulma/admin.py
new file mode 100644
index 00000000..8c38f3f3
--- /dev/null
+++ b/bulma/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/bulma/forms.py b/bulma/forms.py
new file mode 100644
index 00000000..32f85c40
--- /dev/null
+++ b/bulma/forms.py
@@ -0,0 +1,347 @@
+# -*- coding: utf-8 -*-
+import datetime
+
+from django import forms
+from django.forms import modelform_factory
+from crispy_forms.helper import FormHelper
+from crispy_forms.layout import Layout, Div, HTML, Field
+from crispy_forms.bootstrap import AppendedText, PrependedText, PrependedAppendedText
+from django.utils import timezone
+
+from crispy_bulma.bulma import InlineCheckboxes, InlineRadios
+from crispy_bulma.layout import Button, Column, IconField, FormGroup, Row, Submit
+from crispy_bulma.widgets import FileUploadInput
+
+from bulma.models import WithFileField
+
+
+class MessageForm(forms.Form):
+ text_input = forms.CharField(
+ help_text="help on a text_input",
+ )
+ text_input_a = forms.CharField()
+ text_input_b = forms.CharField()
+ text_input_c = forms.CharField()
+
+ integer_input = forms.IntegerField()
+ decimal_input = forms.DecimalField()
+ url_input = forms.URLField()
+ time_input = forms.TimeField()
+ date_input = forms.DateField()
+ datetime_input = forms.DateTimeField()
+ password_input = forms.CharField(widget=forms.PasswordInput, label="Password")
+
+ textarea = forms.CharField(
+ widget=forms.Textarea(),
+ help_text="help on a textarea",
+ )
+
+ radio_buttons = forms.ChoiceField(
+ choices=(
+ ('option_one',
+ "Option one is this and that be sure to include why it's great"),
+ ('option_two',
+ "Option two can is something else and selecting it will deselect option one")
+ ),
+ widget=forms.RadioSelect,
+ initial='option_two',
+ help_text="help on a radio_buttons",
+ )
+
+ inline_radio_buttons = forms.ChoiceField(
+ choices=(
+ ('option_one', 'option_one'),
+ ('option_two', 'option_two')
+ ),
+ widget=forms.RadioSelect,
+ initial='option_two',
+ help_text="help on a inline_radio_buttons",
+ )
+
+ checkboxes = forms.MultipleChoiceField(
+ choices=(
+ ('option_one',
+ "Option one is this and that be sure to include why it's great"),
+ ('option_two',
+ 'Option two can also be checked and included in form results'),
+ ('option_three',
+ 'Option three can yes, you guessed it also be checked and included in form results')
+ ),
+ initial='option_one',
+ widget=forms.CheckboxSelectMultiple,
+ help_text="Note: Labels surround all the options for much larger click areas and a more usable form.",
+ )
+
+ inline_checkboxes = forms.MultipleChoiceField(
+ choices=(
+ ('bird',
+ "it's a bird"),
+ ('plane',
+ "it's a plane"),
+ ('dunno',
+ "it's something else !")
+ ),
+ initial='option_one',
+ widget=forms.CheckboxSelectMultiple,
+ help_text="help on a inline_checkboxes",
+ )
+
+ grouped_checkboxes = forms.MultipleChoiceField(
+ choices=(
+ ('Group 1',
+ ((1, "Option one"),
+ (2, "Option two"),
+ (3, "Option three"))),
+ ('Group 2',
+ ((4, "Option four"),
+ (5, "Option five"),
+ (6, "Option six"))),
+ ),
+ initial=(1,),
+ widget=forms.CheckboxSelectMultiple,
+ help_text="help on a grouped_checkboxes",
+ )
+
+ icon_field = forms.CharField(
+ help_text="Here's more help text this time on an icon field"
+ )
+
+ appended_text = forms.CharField(
+ help_text="Here's more help text"
+ )
+
+ appended_text2 = forms.CharField(
+ help_text="And a bigger appended text field"
+ )
+
+ appended_select = forms.ChoiceField(
+ label="Select field with appended text",
+ choices=[(1, "Choice 1"), (2, "Choice 2")],
+ help_text="Some help text"
+ )
+
+ prepended_appended_select = forms.ChoiceField(
+ label="Select field with both preprended and appended text",
+ choices=[(1, "Choice 1"), (2, "Choice 2")],
+ help_text="Some help text"
+ )
+
+ prepended_select = forms.ChoiceField(
+ label="Select field with prepended text",
+ choices=[(1, "Choice 1"), (2, "Choice 2")],
+ help_text="Some help text"
+ )
+
+ prepended_text = forms.CharField()
+
+ prepended_text_two = forms.CharField()
+
+ select = forms.ChoiceField(
+ choices=(('1', 'North'), ('2', 'South'), ('3', 'East'), ('4', 'West')),
+ help_text='Direction to go'
+ )
+
+ multicolon_select = forms.MultipleChoiceField(
+ choices=(('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')),
+ help_text=(
+ 'This strange option climbing out of the box is in the examples too '
+ 'Only without Flexbox '
+ 'https://v4-alpha.getbootstrap.com/components/forms/#form-controls'),
+ )
+
+ split_datetime_field = forms.SplitDateTimeField(
+ initial=timezone.now()
+ )
+ boolean_field = forms.BooleanField()
+
+ file_field = forms.FileField(
+ label="file_field",
+ widget=FileUploadInput(),
+ help_text='with widgets.FileInput()'
+ )
+
+ file_field_raw = forms.FileField(
+ label="file_field_raw",
+ help_text='with default widget',
+ )
+
+ # Bulma
+ helper = FormHelper()
+ helper.layout = Layout(
+ Field('text_input', css_class='form-control-lg'),
+ Field('textarea', rows="3", css_class='is-primary is-large'),
+ 'radio_buttons',
+ InlineRadios('inline_radio_buttons'),
+ Field('checkboxes', style="background: #FAFAFA"),
+ InlineCheckboxes('inline_checkboxes'),
+ IconField("icon_field", icon_prepend="fa fa-user"),
+ 'select',
+ Field('multicolon_select', size="5"),
+ 'boolean_field',
+ Field('file_field', css_class='is-primary is-large'),
+ 'file_field_raw',
+ # TODO
+ # 'grouped_checkboxes',
+ Row(
+ Column('text_input_a','text_input_b'),
+ Column('text_input_c'),
+ ),
+ "integer_input",
+ "decimal_input",
+ "url_input",
+ "time_input",
+ "date_input",
+
+ # TODO
+ #'split_datetime_field',
+ FormGroup(
+ Submit('save_changes', 'Save changes', css_class="is-primary is-large"),
+ Button('Cancel'),
+ ),
+ "datetime_input",
+ "password_input",
+
+ FormGroup(
+ Submit('save_changes', 'Save changes', css_class="is-primary"),
+ Button('Cancel'),
+ ),
+
+ )
+
+
+class HorizontalMessageForm(forms.Form):
+ text_input = forms.CharField()
+ text_input_a = forms.CharField()
+ text_input_b = forms.CharField()
+ text_input_c = forms.CharField()
+
+ textarea = forms.CharField(
+ widget=forms.Textarea(),
+ )
+
+ radio_buttons = forms.ChoiceField(
+ choices=(
+ ('option_one',
+ "Option one is this and that be sure to include why it's great"),
+ ('option_two',
+ "Option two can is something else and selecting it will deselect option one")
+ ),
+ widget=forms.RadioSelect,
+ initial='option_two',
+ help_text="help on a radio_buttons",
+ )
+
+ inline_radio_buttons = forms.ChoiceField(
+ choices=(
+ ('option_one', 'option_one'),
+ ('option_two', 'option_two')
+ ),
+ widget=forms.RadioSelect,
+ initial='option_two',
+ help_text="help on a inline_radio_buttons",
+ )
+
+ checkboxes = forms.MultipleChoiceField(
+ choices=(
+ ('option_one',
+ "Option one is this and that be sure to include why it's great"),
+ ('option_two',
+ 'Option two can also be checked and included in form results'),
+ ('option_three',
+ 'Option three can yes, you guessed it also be checked and included in form results')
+ ),
+ initial='option_one',
+ widget=forms.CheckboxSelectMultiple,
+ help_text="Note: Labels surround all the options for much larger click areas and a more usable form.",
+ )
+
+ inline_checkboxes = forms.MultipleChoiceField(
+ choices=(
+ ('bird',
+ "it's a bird"),
+ ('plane',
+ "it's a plane"),
+ ('dunno',
+ "it's something else !")
+ ),
+ initial='option_one',
+ widget=forms.CheckboxSelectMultiple,
+ help_text="help on a inline_checkboxes",
+ )
+
+ appended_text = forms.CharField(
+ help_text="Here's more help text"
+ )
+
+ appended_text2 = forms.CharField(
+ help_text="And a bigger appended text field"
+ )
+
+ prepended_text = forms.CharField()
+
+ prepended_text_two = forms.CharField()
+
+ select = forms.ChoiceField(
+ choices=(('1', 'North'), ('2', 'South'), ('3', 'East'), ('4', 'West')),
+ help_text='Direction to go'
+ )
+
+ multicolon_select = forms.MultipleChoiceField(
+ choices=(('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5')),
+ help_text=(
+ 'This strange option climbing out of the box is in the examples too '
+ 'Only without Flexbox '
+ 'https://v4-alpha.getbootstrap.com/components/forms/#form-controls'),
+ )
+
+ boolean_field = forms.BooleanField()
+ file_field = forms.FileField(
+ widget=FileUploadInput(),
+ help_text='with FileInput widget',
+ required=True,
+ )
+
+ file_field_raw = forms.FileField(
+ help_text='with default widget',
+ required=True,
+ )
+
+ # Bulma
+ helper = FormHelper()
+ helper.layout = Layout(
+ Field('text_input', css_class='form-control-lg'),
+ Field('textarea', rows="3", css_class='form-control-lg'),
+ Field('radio_buttons'),
+ InlineRadios('inline_radio_buttons'),
+ Field('checkboxes', style="background: #FAFAFA"),
+ InlineCheckboxes('inline_checkboxes'),
+ Field('select'),
+ Field('multicolon_select'),
+ Field('boolean_field'),
+ Field('file_field'),
+ Field('file_field_raw'),
+ FormGroup(
+ Submit('save_changes', 'Save changes', css_class="is-primary"),
+ Button('Cancel'),
+ ),
+ )
+ helper.form_horizontal = True
+
+
+
+
+class WithFileForm(forms.ModelForm):
+ class Meta:
+ fields = ['my_file', 'my_char']
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['my_file'].widget = FileUploadInput()
+
+
+FormWithFileField = modelform_factory(WithFileField, form=WithFileForm)
+class HorizontalModelForm(forms.ModelForm):
+ class Meta:
+ model = WithFileField
+ fields = '__all__'
+ helper = FormHelper()
+ helper.form_horizontal = True
diff --git a/bulma/migrations/__init__.py b/bulma/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bulma/models.py b/bulma/models.py
new file mode 100644
index 00000000..a435b0d3
--- /dev/null
+++ b/bulma/models.py
@@ -0,0 +1,6 @@
+from django.db import models
+
+# Create your models here.
+class WithFileField(models.Model):
+ my_file = models.FileField(null=True, blank=True, help_text="help")
+ my_char = models.CharField(null=True, blank=True, help_text="help", max_length=32)
diff --git a/bulma/templates/bulma/base.html b/bulma/templates/bulma/base.html
new file mode 100644
index 00000000..e119d023
--- /dev/null
+++ b/bulma/templates/bulma/base.html
@@ -0,0 +1,16 @@
+
+
+
+ Bulma | Crispy Forms Test Project
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
diff --git a/bulma/templates/bulma/index.html b/bulma/templates/bulma/index.html
new file mode 100644
index 00000000..3cc92220
--- /dev/null
+++ b/bulma/templates/bulma/index.html
@@ -0,0 +1,69 @@
+{% extends 'bulma/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+
+
+
+{% endblock %}
diff --git a/bulma/tests.py b/bulma/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/bulma/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/bulma/views.py b/bulma/views.py
new file mode 100644
index 00000000..efcfee8f
--- /dev/null
+++ b/bulma/views.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+import os
+
+import errno
+from django.conf import settings
+from django.shortcuts import render
+
+from bulma.models import WithFileField
+from bulma.forms import FormWithFileField, HorizontalMessageForm, HorizontalModelForm, MessageForm
+
+
+def index(request):
+ settings.CRISPY_TEMPLATE_PACK = 'bulma'
+
+ filename = '.' + ("/somedire" * 10)
+ try:
+ os.makedirs(filename)
+ except FileExistsError as e:
+ assert e.errno is errno.EEXIST
+ filename += "/" + ("myfile-" * 10) + ".txt"
+ with open(filename, "w") as f:
+ f.write('Hello world')
+ instance = WithFileField(my_file=filename)
+
+ # This view is missing all form handling logic for simplicity of the example
+ return render(request, 'bulma/index.html',
+ {
+ 'model_form': FormWithFileField(
+ instance=instance,
+ prefix='model_form',
+ ),
+ 'horizontal_model_form': HorizontalModelForm(
+ instance=instance,
+ prefix='horizontal_model_form',
+ ),
+ 'default_form': MessageForm(
+ data=request.POST if request.method == "POST" else None,
+ prefix='default_form',
+ ),
+ 'horizontal_form': HorizontalMessageForm(
+ data=request.POST if request.method == "POST" else None,
+ prefix='horizontal_form',
+ ),
+ 'default_form_failing': MessageForm(
+ data={},
+ prefix='default_form_failing',
+ ),
+ 'horizontal_form_failing': HorizontalMessageForm(
+ data={},
+ prefix='horizontal_form_failing',
+ ),
+ })
diff --git a/django_rendering/templates/django_rendering/index.html b/django_rendering/templates/django_rendering/index.html
index 75b7cdcb..b3267cea 100644
--- a/django_rendering/templates/django_rendering/index.html
+++ b/django_rendering/templates/django_rendering/index.html
@@ -12,10 +12,13 @@
Bootstrap 3
- Bootstrap 4(current)
+ Bootstrap 4
-
- Semantic UI(current)
+
+ Bulma
+
+
+ Semantic UI
diff --git a/requirements.txt b/requirements.txt
index 378c9da6..7a47cf97 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ django-crispy-forms>=1.9.0 # Use pip install -e ../django-crispy-forms for
Django>=3.0.7
gunicorn>=20.0.4
pytz>=2019.3
+crispy-bulma>=0.8.0
diff --git a/semantic/templates/semantic/index.html b/semantic/templates/semantic/index.html
index 7f516d9a..2277c027 100644
--- a/semantic/templates/semantic/index.html
+++ b/semantic/templates/semantic/index.html
@@ -5,8 +5,10 @@
diff --git a/test_project/settings.py b/test_project/settings.py
index bca6eb8f..5ef47eb1 100644
--- a/test_project/settings.py
+++ b/test_project/settings.py
@@ -43,10 +43,12 @@
# dependencies
'crispy_forms',
'crispy_forms_semantic_ui',
+ 'crispy_bulma',
# internal apps
'bootstrap3',
'bootstrap4',
'semantic',
+ 'bulma',
'django_rendering',
)
@@ -78,7 +80,7 @@
},
]
-CRISPY_ALLOWED_TEMPLATE_PACKS = ('bootstrap', 'uni_form', 'bootstrap3', 'bootstrap4', 'semantic-ui',)
+CRISPY_ALLOWED_TEMPLATE_PACKS = ('bootstrap', 'uni_form', 'bootstrap3', 'bootstrap4', 'bulma', 'semantic-ui',)
WSGI_APPLICATION = 'test_project.wsgi.application'
diff --git a/test_project/urls.py b/test_project/urls.py
index 2081a646..347519dc 100644
--- a/test_project/urls.py
+++ b/test_project/urls.py
@@ -17,6 +17,7 @@
from bootstrap3.views import index as bootstrap_3_preview
from bootstrap4.views import index as bootstrap_4_preview
+from bulma.views import index as bulma_preview
from semantic.views import index as semantic_preview
from django_rendering.views import index as django_rendering_preview
@@ -25,5 +26,6 @@
path('django', django_rendering_preview, name='django_rendering.views.index'),
path('bootstrap3', bootstrap_3_preview, name='bootstrap3.views.index'),
path('bootstrap4', bootstrap_4_preview, name='bootstrap4.views.index'),
+ path('bulma', bulma_preview, name='bulma.views.index'),
path('semantic', semantic_preview, name='semantic.views.index'),
]