These formsets are exhibiting exactly the opposite behavior that I want.
My view is set up like this:
def post(request): # TODO: handle vehicle formset
VehicleFormSetFactory = formset_factory(VehicleForm, extra=1)
if request.POST:
vehicles_formset = VehicleFormSetFactory(request.POST)
else:
vehicles_formset = VehicleFormSetFactory()
And my template looks like this:
<div id="vehicle_forms">
{{ vehicles_formset.management_form }}
{% for form in vehicles_formset.forms %}
<h4>Vehicle {{forloop.counter}}</h4>
<table>
{% include "form.html" %}
</table>
{% endfor %}
</div>
That way it initially generates only 1 form, like I want. But I want that one form to be required!
When I dynamically add blank forms with JavaScript and vehicles_formset.empty_form all those extra forms are required, which I don't want.
From the docs:
The formset is smart enough to ignore extra forms that were not changed.
This is the behavior the first form is exhibiting (not what I want) but not the behavior that the extra forms are exhibiting (what I do want).
Is there some attribute I can can change to at least make one form required?
Found a better solution:
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
Then create your formset like this:
MyFormSet = formset_factory(MyForm, formset=RequiredFormSet)
I really don't know why this wasn't an option to begin with... but, whatever. It only took a few hours of my life to figure out.
This will make all the forms required. You could make just the first one required by setting self.forms[0].empty_permitted to False.
New in Django 1.7: you can specify this behaviour with your formset_factory
https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min
VehicleFormSetFactory = formset_factory(VehicleForm, min_num=1, validate_min=True, extra=1)
Well... this makes the first form required.
class RequiredFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
return
if not self.forms[0].has_changed():
raise forms.ValidationError('Please add at least one vehicle.')
Only "problem" is that if there are 0 forms, then the clean method doesn't seem to get called at all, so I don't know how to check if there are 0. Really...this should never happen though (except that my JS has a bug in it, allowing you to remove all the forms).
Oh I think I see. Try this:
from django.forms.formsets import BaseFormSet, formset_factory
class OneExtraRequiredFormSet(BaseFormSet):
def initial_form_count(self):
return max(super(OneExtraRequiredFormSet,self).initial_form_count() - 1,0)
VehicleFormSetFactory = formset_factory(VehicleForm, formset=OneExtraRequiredFormSet, extra=1)
== Original answer below ==
When you say "at least make one form required", I assume you mean "make only one extra form required, regardless of how many have been added via javascript".
You will need to have hidden input on your page which contains the number of forms that have been added via javascript, and then use that number, minus 1, as the value to pass in as the extra attribute to your formsets constructor.
Related
I'm using Django 2.1.
I'm having a problem with a CreateView because I need to redirect to the update url, but that url contains one argument that is created manually after verifying that the form is valid.
This is the view code:
class ProjectCreateInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign created!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign',
args=(self.kwargs['pk'], self.object.campaign.pk, self.object.pk))
def form_valid(self, form):
project = Project.objects.get(pk=self.kwargs['pk'])
form.instance.investment_type = "A"
form.instance.contract_type = "CI"
form.instance.history_change_reason = 'Investment campaign created'
valid = super(ProjectCreateInvestmentCampaignView, self).form_valid(form)
if valid:
campaign = CampaignBase.objects.create(project=project, )
form.instance.campaign = campaign
form.instance.campaign.project = project
form.instance.campaign.creation_date = timezone.now()
form.save()
return valid
As you can see, on the form_valid I validate first the form, and then I create the object campaign and assign all the related data. This is working fine.
The problem came when I changed the get_success_url to fit my use case, that is redirecting to the update view.
I debugged and saw that at the moment I create the variable valid on the form_valid, it checks the success url, and that triggers me the following error:
Exception Type: AttributeError
Exception Value:
'NoneType' object has no attribute 'pk'
Exception Location: /Volumes/Archivos/work/i4b/webplatform/views/investor_campaign_views.py in get_success_url, line 25
I asume that the error is because the campaign is not created yet so it's trying to get the pk from a non existing object.
The thing is that I cannot create the campaign if the form is not validated, but I need the campaign to make the url working (that url is working as it is on the UpdateView that I already have).
It will only invoke get_success_url after form_valid. So it's up to form_valid to create and save the objects needed. If it's valid for them not to be created, you need a different approach. Maybe initialize (say) self.campaign_pk = 0, update it if a campaign can be created with the pk of the campaign object, and let the next view sort out what to do when pk==0. Or,
...
args=(self.kwargs['pk'],
self.object.campaign.pk if self.object.campaign else 0,
self.object.pk))
(I don't fully follow your code so I might be barking up the wrong tree here)
It may be that you don't want CreateView but FormView, which doesn't handle object creation for you, so you may find greater flexibility over how to process a valid form that nevertheless cannot be fully honoured all the time. Or even, just a plain old function-based view, in which you can process two or more forms and be far more able to decide on conditions that constitute non-validity even after all the forms have technically validated.
This is a function-based view structure I have used where I have two forms to process, and a fairly long but boring set of operations to do after BOTH forms validate:
def receive_view( request):
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args, layout=CreateUncWaferBatchLayout )
po_form = CreateUncWaferPOForm( *args, layout = CreateUncWaferPOLayout, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#it's a POST, everything is valid, do the work
...
return redirect('appname:viewname', ...)
For me, get_success_url was not invoked as the form was not valid (was invalid) and I didn't know. You can override form_invalid(self, form) to control the behavior.
Also, consider this block of code to show any errors in your template
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<b>{{ field }}</b>: {{ error }}
{% endfor %}
{% endfor %}
</div>
{% endif %}
Background
I have two models, Runs and Orders. One run will complete many orders, so I have a Many-to-one relation between my orders and runs, represented as a foreignkey on my orders.
I want to build a UI to create a run. It should be a form in which someone selects orders to run. I'd like to display a list of checkboxes alongside information about each order. I'm using django crispy forms right now.
views.py
class createRunView(LoginRequiredMixin, CreateView):
model = Run
form_class = CreateRunForm
template_name = 'runs/create_run.html'
forms.py
class CreateRunForm(forms.ModelForm):
class Meta:
model = Run
fields = ['orders',]
orders = forms.ModelMultipleChoiceField(queryset=Order.objects.filter(is_active=True, is_loaded=False))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Field('orders', template="runs/list_orders.html"),
Submit('save', 'Create Run'),
Button('cancel', 'Cancel'),
)
Questions
I'm not sure what locals are available to me in the list_orders.html template. It seems like there's {{ field }} and maybe form.visible_fields but if I dig to deeply into either I get a TypeError: 'SubWidget' object is not iterable, which is barely documented online.
The above suggests I might still be getting a widget in the template, despite the fact that Field('orders', template="runs/list_orders.html"), should prevent that, per the crispy docs:
Field: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
I've seen this answer which suggests using label_from_instance. However I'm not sure how to stuff a bunch of html into label_from_instance. Instead of having a different label, I really want to have a template which generates a bunch of html which shows details about the entire order object, so I'm not sure this approach will work.
The answers in this question mostly confused me, but the accepted answer didn't work, it seems. (maybe a django version issue, or a crispy forms issue?)
TL;DR
How do I render templates with data from each model in ModelMultipleChoiceField?
Widgets control how fields are rendered in HTML forms. The Select widget (and its variants) have two attributes template_name and option_template_name. The option_template_name supplies the name of a template to use for the select options. You can subclass a select widget to override these attributes. Using a subclass, like CheckboxSelectMultiple, is probably a good place to start because by default it will not render options in a <select> element, so your styling will be easier.
By default the CheckboxSelectMultiple option_template_name is 'django/forms/widgets/checkbox_option.html'.
You can supply your own template that will render the details of the orders how you want. IE in your forms.py
class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'myapp/detail_options.html'
class CreateRunForm(forms.ModelForm):
...
orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
Suppose that myapp/detail_options.html contained the following
{# include the default behavior #}
{% include "django/forms/widgets/input_option.html" %}
{# add your own additional div for each option #}
<div style="background-color: blue">
<h2>Additional info</h2>
</div>
You would see that blue div after each label/input. Something like this
Now, the trick will be how you get the object available to the widget namespace. By default, only certain attributes are present on a widget, as returned by the widget's get_context method.
You can use your own subclass of MultipleModelChoiceField and override label_from_instance to accomplish this. The value returned by label_from_instance is ultimately made available to the widgets as the label attribute, which is used for the visible text in your model form, when it renders {{ widget.label }}.
Simply override label_from_instance to return the entire object then use this subclass for your field.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj
class CreateRunForm(forms.ModelForm):
...
orders = MyModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
So now in the myapp/detail_options template you can use widget.label to access the object directly and format your own content as you please. For example, the following option template could be used
{% include "django/forms/widgets/input_option.html" %}
{% with order=widget.label %}
<div style="background-color: blue">
<h2>Order info</h2>
<p style="color: red">Order Active: {{ order.is_active }}</p>
<p style="color: red">Order Loaded: {{ order.is_loaded }}</p>
</div>
{% endwith %}
And it would produce the following effect.
This also will not disrupt the default behavior of the widget label text wherever widget.label is used. Note that in the above image the label texts (e.g. Order object (1)) are the same as before we applied the change to label_from_instance. This is because the default in template rendering is to use str(obj) when presented with a model object; the same thing that would have previously been done by the default label_from_instance.
TL;DR
Make your own subclasses of ModelMultiplechoiceField to have label_from_instance return the object.
Make your own SelectMultiple widget subclass to specify a custom option_template_name.
The template specified will be used to render each option, where widget.label will be each object.
I am using django-forms-builder in my project and there is just one thing i cant get my head around. How do i set up an update form to edit a filled out form?
Here is my attempt at making an update form for django-forms-builder
urls.py
url('forms/update/(?P<pk>\d+)/$', FormUpdateView.as_view(),
name='form-update'),
views.py
class FormUpdateView(UpdateView):
model = FieldEntry
template_name = 'form/update_form.html'
form_class = FormForForm
success_url = '/assessments/all/'
update-form.py
{% render_built_form id=form_instance.id %}
I haven't used this software yet but it could fill a spot in my arsenal so I took a stab at it. I'm not using CBV because I'm still just poking around. This is the get part only, I can expand on how to handle the post part (and preferably do it with CBV) on the weekend. It's relatively straightforward to implement post with view functions (if request.METHOD == "POST":#update the row). The render is of course just plain raw HTML but forms_builder doesn't offer any styling input. You probably want to tack the user onto the FormEntry, I don't have a good idea about authorization yet. I'll give it some thought and amend the answer.
views.py
from forms_builder.forms.models import Form
def get_form(request, slug, pk):
form_obj = Form.objects.get(slug=slug)
form_instance = form_obj.entries.get(pk=pk)
form = FormForForm(form=form_obj, instance=form_instance, context=request)
return render_to_response('update_form.html', {'form':form_instance.form, 'request':request})
templates/update_form.html
{{ form.as_p }}
I have django form, but in my HTML i added one extra input field (directly added in html page) which i can access it using request.POST.get('extra_field_name') in my django views.
If form.is_valid() is false i can get the form as HTML with the data displayed in the HTML but with empty value for the extra added field( directly added in html page)
How can i get the bounded form data for this newly added extra html field after validation fials.
Please provide your suggestions?
View:
html_added_field = ''
error_added_field = None
if request.method == 'POST':
html_added_field = request.POST.get('extra_field_name')\
if form.is_valid():
pass
else:
error_added_field = _('Error')
context = {'html_added_field':html_added_field,'error_added_field':error_added_field}
HTML:
<input type="text" value="{{ html_added_field }}" />{% if error_added_field %}<div class="error">{{ error_added_field }}</div>{% endif %}
This article by Bennett may help you.
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/
Briefly, you have to override the __init__ method of your form, adding there the new "extra_field_name". The fields are included in the self.fields list, so doing:
self.fields['extra_field_name'] = forms.CharField(put_here_definitions_for_your_field)
should do the trick.
Honestly the best way would be for your base django form to handle the extra field. Not using extra fields in the html. However if you can't/don't want, as xbello states, in http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ you have various tricks for handling dynamic fields.
I found more robust to use the form factory method. A function that generates a dynamic form, prepared for more future changes. Still, you have to decide the best approach :-)
I'd like to create a form that includes fields from two separate models, along with some other regular (non-model) fields. The form will create an instance of each model. I don't think I can use inline formsets for this, since I don't want to include all the fields from both models.
I'd like to create the form field without hard-coding the type of the model fields.
I know I can get a form field from a model field using model_field.formfield(). But how can I get the specific model field?
My first solution:
def get_fields(model_class):
fields = {}
for f in model_class._meta.fields:
fields[f.name] = f
class MyForm(forms.Form):
foo_name = get_fields(Foo)['name'].formfield()
bar_name = get_fields(Bar)['name'].formfield()
other_field = ...
Is there an equivalent of get_fields already? Is this a bad idea? I'm uncomfortable relying on the model _meta attribute. Or am I going about this the completely wrong way?
You also can take a look at django.forms.models.fields_for_model.
That should give you a dictionary of fields, and then you can add the fields of the form
You should never have to build the fields yourself unless you want some special behavior.
This should be as simple as using two ModelForms and an extra Form inside one <form> tag in your template with one submit button.
in forms.py:
class Model1Form(forms.ModelForm):
class Meta:
model = Model1
fields = ('fields', 'you', 'want')
class Model2Form(forms.ModelForm):
class Meta:
model = Model2
fields = ('fields', 'you', 'want')
class ExtraFieldsForm(forms.Form):
extra_field = form.TextField() # or whatever field you're looking for
in views.py:
form1 = Model1Form(request.POST or None)
form2 = Model2Form(request.POST or None)
form3 = ExtraFieldsForm(request.POST or None)
if form1.is_valid() and form2.is_valid() and form3.is_valid():
form1.save()
form2.save()
form3.save()
...do other stuff like a redirect...
and in the template:
<form method="POST" action="">{% csrf_token %}
<fieldset>
{{ form1|as_uni_form }}
{{ form2|as_uni_form }}
{{ form3|as_uni_form }}
<div class="form_block">
<input type="submit" value="Save both models"/>
</div>
</fieldset>
</form>
I'm used to using django-uni-form, but you can render the form fields however you like. Good luck with your site.
There is now a documented API for getting the model field from a model class:
my_model_field = MyModel._meta.get_field('my_model_field_name')
Although it's not officially documented until Django 1.8, this should work with earlier Django versions too.
Once you have this, you can get the form field like so:
form_field = my_model_field.formfield()
Another solution can be to create one 'uber'-form that aggregates the concrete modelforms. The form supports the methods that a form normally provides and it forward them to all the child forms. Some will be simple, other complicated. The big advantage of that approach is that no code beyond the form is affected (client validation code and alike). The concept isn't really revolutionary but i guess complicated to add afterwards.
Paul
It's still undocumented as far as I know, but since Django 3.0 you can make a form field from a single model field called field_name with:
MyModel.field_name.field.formfield()
If your model is a parler.models.TranslatableModel, and the field_name is inside translations, you can use:
MyModel.translations.field.model.field_name.field.formfield()
Or, if you prefer a bit more verbose, but better documented ways, you can use:
MyModel._meta.get_field("field_name").formfield()
and
MyModel._parler_meta.get_model_by_related_name("translations")._meta.get_field("field_name").formfield()
See:
Django get_field
django-parler get_model_by_related_name