django-betterforms builds on the built-in django forms.
Install the package:
$ pip install django-betterforms
Add
betterforms
to yourINSTALLED_APPS
.
Making forms suck less
Home Page: https://django-betterforms.readthedocs.org/
License: BSD 2-Clause "Simplified" License
Hi,
I'm having an issue with input fields not rendering for a MultiModelForm. Here's my forms.py:
# forms.py
from django import forms
from betterforms.multiform import MultiModelForm
from .models import Person, Alias, PrimaryEmailAddress
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('last_name', 'middle_name_or_initial', 'first_name')
class PrimaryEmailAddressForm(forms.ModelForm):
class Meta:
model = PrimaryEmailAddress
fields = ('email_address',)
class AliasForm(forms.ModelForm):
class Meta:
model = Alias
fields = ('alias',)
class PersonMultiForm(MultiModelForm):
form_class = {
'person': PersonForm,
'primary_email': PrimaryEmailAddressForm,
'alias': AliasForm
}
def save(self, commit=True):
objects = super(PersonMultiForm, self).save(commit=False)
if commit:
person = objects['person']
person.save()
primary_email = objects['primary_email']
primary_email.person = objects['primary_email']
alias = objects['alias']
alias.person = person
return objects
Here's my template:
<!--add_person.html-->
<form action="" method="POST">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
And here's my views.py:
# views.py
from django.views.generic import CreateView
from .forms import PersonForm, PersonMultiForm
from .models import Person, PrimaryEmailAddress, Alias
class CreatePerson(CreateView):
form_class = PersonMultiForm
template_name = 'add_person.html'
The result doesn't render any of the input fields:
However, when I change the line form_class = PersonMultiForm
in my views.py to form_class = PersonForm
, the form renders properly:
I'm new to Django, so it's quite possible I'm doing Django wrong, not using MultiModelForm wrong.
Any help would be appreciated!
class NewSamplesheetForm(BetterForm):
class Meta:
fieldsets = (
('', {'fields':('experiment', 'plate_id')}),
('Plate Description', {'fields': ('description')}),
('Nanoparticle', {'fields':('np_vol_ul', 'nanoparticle_diluent')}),
('Biosample', {'fields':(('biosample_id', 'biosample_type'), ('biosample_dilution_factor', 'biosample_diluent'))}),
('Assay', {'fields':(('assay_instrument', 'cleanup_instrument'), 'assay_wash_buffer', ('incubation_time_hr', 'incubation_temp_c'),
'digestion_method', 'sample_vol_ul')})
)
When I do this, I get the above error message. It seems to be looping over the letters of "description", thus getting two "i's" rather than looping through each field.
Is this possible using django-betterforms?
There is a straight self.cleaned_data = self.clean()
assignment in is_valid()
here: https://github.com/fusionbox/django-betterforms/blob/2.0.0/betterforms/multiform.py#L103
However, the docs allow for clean() functions to not return anything, in which case the existing .cleaned_data
should be used: https://docs.djangoproject.com/en/4.2/ref/forms/validation/
The call to super().clean() in the example code ensures that any validation logic in parent classes is maintained. If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assign cleaned_data to the result of the super() call and use self.cleaned_data instead:
def clean(self):
super().clean()
cc_myself = self.cleaned_data.get("cc_myself")
...
It looks like is_valid was originally implemented this way but was changed in 10a9c6c. Maybe this change should be partially reverted. A side note with that change, if an app does form.cleaned_data = {}
the existing @cleaned_data.setter
will not update any child forms, when I'd expect unreferenced forms' cleaned_data
to be cleared.
Our CI isn't able to run anymore due to not being able to install django-betterforms==1.2
.
Since November 10th, 2021, setuptools won't allow the parameter description
, given to setuptools.setup()
to have multiple lines, due to some malformed PKG-INFO parsing
The fix rolled out 4 days ago, which ended up breaking the package's setup script.
Hi,
There appears to be a bug when a fieldset contains a field called name
which causes the fieldset's name to be overwritten by that fields; as a result, {% fieldset.name %}
causes a field to be displayed instead of the fieldset's actual name.
Should fields really be accessible through {% fieldset.FIELD_NAME %}
? Would it not be preferable for them to be accessed through {% fieldset.fields['FIELD_NAME'] %}
?
I'm using quite a few layers of different forms tools. On top I'm using WizardForm
, where one of the steps is MultiForm
which has some dynamic parts (based on kwargs
I pass it) and one of the form is actually Formset
. I don't like the fact, that when you pass some kwarg
to MultiForm
, that they are simply copied to ALL the subforms. I could work around that in simple subforms, which inherit from forms.Form
, but using the forms.formset_factory
complicates the matter.
I propose the following (just as an initial idea):
When you want to pass some kwarg
to all the forms of MultiForm
you would use
MultiForm(all_subforms={"<kwarg>": <value>, ...})
And when you want to pass some kwarg
only to one of the forms, with "firstform"
as the key in MultiForm.form_classes
you would do:
MultiForm(firstform={"<kwarg>":value,...})
If you would use both all_subforms
and firstform
with the same kwarg
the latter will take precedence (quite logically).
I will probably implement something like this anyway, but want to share the idea here as well. I can make the pull request
later.
Also, this is rough idea, there is usually some catch to my ideas.. This also might be related to issuse: #48
Since the project use python_2_unicode_compatible decorator and django3 dropped support for python2 maybe it's time to remove it or if the aim is to make it still compatible with older project maybe the solution could be something like create a doing-nothing decorator if import fails:
try:
from django.utils.encoding import python_2_unicode_compatible
except ImportError:
def python_2_unicode_compatible(klass):
"""
Since python2 no more supported in django3
it would suffice to create a fake decorator if import fails
"""
return klass
This is the issue I am facing :
https://stackoverflow.com/questions/68556218/add-packages-outside-of-pip-to-elastic-beanstalk.
I am trying to install django-betterforms on elastic beanstalk using the setup.py.
On the local machine,its fine,but I cant seem to get elastic beanstalk to install it. I have added this line in the requirements.txt: -e git://github.com/jpic/django-git.betterforms
I get this in the logs: Extracting django_betterforms-1.2.2-py3.8.egg to /var/app/venv/staging-LQM1lest/lib/python3.8/site-packages django-betterforms 1.2.2 is already the active version in easy-install.pth
Which states that django-betterforms is installed.
Also, on doing cat easy-install.pth I get : ./django_betterforms-1.2.2-py3.8.egg
But,later in the logs AWS throws : ModuleNotFoundError: No module named 'betterforms'
Also, if I try to ssh into the environment and try to import betterforms,python can't find it.
https://docs.djangoproject.com/en/1.7/releases/1.7/#improvements-to-form-error-handling
Django 1.7 added a new method called add_error
which does what the NonBrainDamagedErrorMixin
's field_error
method does. We should rename the field_error method to add_error
(but leave an alias behind). NonBrainDamagedErrorMixin
can be set for removal after Django 1.6 support is dropped.
https://django-betterforms.readthedocs.io/en/latest/intro.html#installation
In the "Quickstart" paragraph it says to import BaseForm but it's actually BetterForm
It seems like this plugin is not designed to work well with multiple instances of the same form on the same page. It relies on IDs which makes the page's markup invalid if more than one instance of a form exist.
(Heck, even two forms that happen to share the same field name would make the page invalid HTML as well)
I have a following setup:
SessionWizardView
|_MultiForm (Step 1)
|_forms.Form
|_forms.ModelForm
|_forms.BaseFormSet
|_ Step 2
...
It sort of works, but I had to do one tweak on the forms.BaseFormSet
:
@forms.BaseFormSet.cleaned_data.setter
def cleaned_data(self, value):
# this setter is needed, because MultiForm sets cleaned_data during
# vallidation and it's not defined on BaseFormSet by default in Django 1.8
for i, data in enumerate(value):
self.forms[i].cleaned_data = data
because MultiForm
sets the cleaned_data
during its own cleaning.
The other issue I'm having is during tests. When I use
response = self.client.get(reverse(<SessionWizardView>))
then it's quite a digging, to get all the form data (management_form
of the BaseFormSet
). Would it be possible to make using BaseFormSet
with MultiForm
easier? I haven't checked the code, so I don't know myself, what would be needed.
Trying to override __init__
using
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
print(self.request.user)
super(MyForm, self).__init__(*args, **kwargs)
got error __init__() got an unexpected keyword argument 'request'
betterforms\multiform.py in __init__, line 46
The v1.2 release has broken the ability to override the clean
method on MultiForm
, even if super()
is called as the first line.
Example use/test:
tests.txt
Example generated using Python 3.5.0 and Django 2.1.1, but the issue has also been observed on other combinations.
Error:
File "\py35env\lib\site-packages\django\template\base.py", line 1040, in render
output = self.filter_expression.resolve(context)
File "\py35env\lib\site-packages\django\template\base.py", line 736, in resolve
new_obj = func(obj, *arg_vals)
File "\py35env\lib\site-packages\form_utils\templatetags\form_utils.py", line 43, in render
return tpl.render(template.Context({'form': form}))
File "\py35env\lib\site-packages\django\template\backends\django.py", line 64, in render
context = make_context(context, request, autoescape=self.backend.engine.autoescape)
File "\py35env\lib\site-packages\django\template\context.py", line 285, in make_context
raise TypeError('context must be a dict rather than %s.' % context.class.name)
TypeError: context must be a dict rather than Context.
Possible fix is to change:
render() in templatetags/forms_utils.py
from
return tpl.render(template.Context({'form': form}))
to
return tpl.render({'form': form}))
When I use the PasswordChangeForm (with django 1.9.6), I have this error:
__init__() missing 1 required positional argument: 'user'
class CustomUserChangeForm(ModelForm):
class Meta:
model = models.User
fields = ['first_name', 'last_name', 'profile_image']
class UserForm(MultiModelForm):
form_classes = {
'user': CustomUserChangeForm,
'password': PasswordChangeForm
}
class UserUpdate(LoginRequiredMixin, UpdateView):
model = get_user_model()
form_class = forms.UserForm
success_url = reverse_lazy('account_profile')
def get_object(self, **kwargs):
return self.request.user
def get_form_kwargs(self):
kwargs = super(UserUpdate, self).get_form_kwargs()
kwargs.update(
instance={
'user': self.object,
'password': self.object,
},
initial={
'password': {
'user': self.object
}
}
)
return kwargs
No problem using your lib with CreateView. Saved me time, thank you guys. But following http://django-betterforms.readthedocs.org/en/latest/multiform.html for DetailView did not make it.
All details on stackoverflow.
Django version : 4.1
I used a MultiModelForm
and everything seems to work, except that it does not use the right rendering engine.
My project is configured in settings.py with :
# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
That way, any normal Django Form
or ModelForm
object is rendered, using the template found in django/forms/default.html
(what I want).
But I don't get that behaviour using a MultiModelForm
, it seems it uses a as_table()
Django rendering.
How should I configure my project sothat all forms (django and multiforms) use my default template?
I've tried three different strategies, none works:
in the field definition:
date_from = DateTimeField(label=_('Date from'),
initial='{:%Y-%m-%d}'.format(datetime.date.today()),
required=True
)
In the form init:
def init(self, *args, **kwargs):
updated_initial = {}
updated_initial['date_from'] = '{:%Y-%m-%d}'.format(datetime.date.today())
kwargs.update(initial=updated_initial)
super().init(*args, **kwargs)
In the get_initial of the BrowseView that uses this form:
def get_initial(self):
initial = super().get_initial()
initial['date_from'] = '{:%Y-%m-%d}'.format(datetime.date.today())
return initial
None of them works. Any ideas on how to populate the FilterForm of the BrowseView with initial values?
django-crispy-forms
provides a Layout
helper which allows further control over how forms are presented. However, during rendering, the implementation of MultiForm
causes crispy forms to raise an unfindable field error.
When using MultiForm for editing models (= putting 'instance' in the kwargs), if one or several of the forms contained inside the MultiForm are FormSet, a TypeError with message "init() got an unexpected keyword argument 'instance'" is raised.
I think this error is due to the "fkwargs = kwargs.copy()" line in the "get_form_args_kwargs" method of multiform.py. If the MultiForm kwargs are copied for any of the child, they will all contain the 'instance' argument, instead of the 'queryset' needed for FormSet.
Django Version: 1.9.4
Python Version: 2.7.6
After submit empty form I am getting this error:
File "/home/truhlik/.virtualenvs/reality/local/lib/python2.7/site-packages/django/views/generic/base.py" in dispatch
return handler(request, _args, *_kwargs)
File "/home/truhlik/.virtualenvs/reality/local/lib/python2.7/site-packages/django/views/generic/edit.py" in post
return super(BaseCreateView, self).post(request, _args, *_kwargs)
File "/home/truhlik/.virtualenvs/reality/local/lib/python2.7/site-packages/django/views/generic/edit.py" in post
if form.is_valid():
File "/home/truhlik/.virtualenvs/reality/local/lib/python2.7/site-packages/betterforms/multiform.py" in is_valid
self.forms[key].cleaned_data = data
Exception Type: AttributeError at /backend/opportunities/create/
Exception Value: can't set attribute
I check in debug mode that the form is correct, the key is correct and the cleaned_data attribute exists. So it looks like .cleaned_data would be read only attribute.
Have any idea what's going on there?
Hello
I'm working with a multimodel form, 3 model, in one i have a checkbox widget defined in Meta class.
So, when i use the shell i obtain on this form correctly the html, but when i use the betterform it give me the default.
What can i do? how?
Thanks!
Hello,
It seems last release is from 2016, is it possible to make a new release ?
Thanks
This is not an issue but more of a question.
How would you deal with the case of having two separate forms and two models but sharing the same CreateView?
For example if you have two models that inherit one abstract model, and in the CreateView it shows both forms to the the user, and which ever form the user submits, it should create an instance of its associated model.
Is this doable with MultiForm?
I think it might be an idea to release 1.2.0 based on the current HEAD, and then remove support for Python 2.6 and Django <1.7 , to make it easier to support Django 1.8 and to allow improvements to the form api such as #14 to be adopted.
I'm trying to view two ModelForms through MultiModelForm with a FormView.
In the template I can't view single fields using e.g. {{ form.username }}
while using a loop like below works.
{% for field in form %}
{{ field }}
{% endfor %}
Edit:
{{ form.user.username }} works. Is this intended?
Hi,
with betterforms can possible to use form_classes with more one field (like extra from inlineformset_factory) ?
Thank's a lot.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.