Manually laying out fields from an inline formset in Django - django

I have created an inline formset for the profile information which is added to the user form:
UserSettingsFormSet = inlineformset_factory(
User,
Profile,
form=ProfileForm,
can_delete=False,
fields=(
"title",
...
),
)
class SettingsView(UpdateView):
model = User
template_name = "dskrpt/settings.html"
form_class = UserForm
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs):
context = super(SettingsView, self).get_context_data(**kwargs)
context["formset"] = UserSettingsFormSet(
instance=self.request.user, prefix="user"
)
return context
This works and calling {formset} in the template file renders the complete form for both User and Profile.
Yet, I would like to lay out the individual inputs myself. This works for fields belonging to User:
<input
type="text"
name="{{form.last_name.html_name}}"
id="{{form.last_name.auto_id}}" value="{{form.last_name.value}}">
But doing the same for fields of the formset does not work. Here the attribute formset.form.title.value appears to be empty. All other attributes, e.g. formset.form.title.auto_id exist though.
Why does {{formset}} render completely with values but values are missing individually?

To answer your questions shortly:
The reason why profile fields are not showing is that they belong to a context data formset instead of form.
If you check the source code of UpdateView, it inherits from a ModelFormMixin, which defines methods related to forms, such as get_form_class.
Why this matter?
Because get_form_class grab the attribute form_class = UserForm or from your model attribute, and pass an context variable to your template called form. Thus in your template, the form variable only refers to UserForm, not your formset.
What is a context variable?
To make it simple that is a variable passed to your template, when your view is rendering the page, it will use those variables to fill in those {{}} slots.
I am sure you also use other generic views such as ListView, and probably you have overwritten the get_context_data method. That basically does the same thing to define what data should be passed to your template.
So how to solve?
Solution A:
You need to use formset in your template instead of form, however this would be quite complicated:
<form action="" method="post" enctype="multipart/form-data">
{{ form_set.management_form }}
{{ form_set.non_form_errors }}
{% for form in formset.forms %}
{% for field in form.visible_fields %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
{% endfor %}
{{ form.non_field_errors }}
{% endfor %}
{# show errors #}
{% for dict in formset.errors %}
{% for error in dict.values %}
{{ error }}
{% endfor %}
{% endfor %}
</form>
Solution B:
Pass correct variable in your view, which will be easier to use:
class SettingsView(UpdateView):
model = User
#...
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data["formset"] = UserSettingsFormset(self.request.POST, instance=self.object)
else:
data["formset"] = UserSettingsFormset(instance=self.object)
return data
Then in your template you can simply do:
<h1>User Profile</h1>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<h2>Profile</h2>
{{ formset.as_p }}
<input type="submit" value="Save">
</form>
I think in your formest the form attribute is not necessary form=ProfileForm,, by default inlineformset_factory will look for a ModelForm as form, and in this case is your User model.
Solution C: Is that really necessary to use a formset? Formest is usually used to generate multiple forms related to an object, which means should be considered to use in a many-to-one relationship.
Usually, for the case of User and Profile, they are inOneToOne relationship, namely, each user only has one profile object. In this case, you can just pass your profile form into your template:
class SettingsView(UpdateView):
model = User
template_name = "dskrpt/settings.html"
form_class = UserForm
#...
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
profile = self.request.user.profile
# your Profile model should have an one-to-one field related with user, and related name should be 'profile'
# or profile = Profile.objects.get(user=self.request.user)
data['profile_form'] = ProfileForm(instance=profile)
return data
Then in your template, you can refer to the profile form by profile_form only.
As a conclusion, I will suggest using solution C.
More detailed example for inline formsets: check this project

Related

Generating forms based on user choices in Django

I am working on a Django project whose purpose is to allow the user to fill in some forms. In some of these forms, the user must make a choice between several options and, based on the choice made, a particular form must be generated. At the end of all these forms, the data entered must be used to write a pdf file.
As for the functionality related to generating the pdf, what I'm interested in for the purposes of the question is the use of data entered in one view in another view using them as context.
Here's what I tried to do.
First of all I created some forms in forms.py:
class ChoiceForm(forms.Form):
CHOICES = [
('1', 'Choice-One'),
('2', 'Choice Two'),
]
choice = forms.ChoiceField(choices=CHOICES)
class ChoiceOneForm(forms.Form):
name_one = forms.CharField(max_length=200)
class ChoiceTwoForm(forms.Form):
name_two = forms.CharField(max_length=200)
Then I created this view in views.py:
def contact(request):
if request.method == 'POST':
num_people = int(request.POST.get('num_people'))
people_formset = [forms.ChoiceForm() for i in range(num_people)]
return render(request, 'home.html', {'people_formset': people_formset})
else:
return render(request, 'home.html')
def generate_pdf(request):
context = {}
return render(request, 'pdf.html', context)
And finally I have this HTML file called 'home.html':
<h1>Contact</h1>
<form method="post">
{% csrf_token %}
People number: <input type="number" name="num_people" required>
<input type="submit" value="Submit">
</form>
{% if people_formset %}
{% for form in people_formset %}
<form>
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endfor %}
{% endif %}
what I've been able to achieve so far is to generate as many 'choice' fields as the value of the number entered in the 'num_people' field.
What I'm missing is:
1. Being able to create, for each 'choiche' field in the formset, a ChoiceOneForm or ChoicheTwoForm form based on the choice made in the 'choice' field;
2. Being able to use all this data in the 'generate_pdf' view (for now what interests me is being able to include this data in the context of this view).

queryset when building django form

I am trying to get specific querysets based when a customer-specific form loads, showing only that customer's name (embedded as an ID field), its respective locations and users.
The idea is to select one user and any number of locations from a multichoice box.
I've tried to pass the ID as a kwarg but am getting a KeyError. I've tried the kwarg.pop('id') as found on the web and same issue. Any advice?
forms.py
class LocGroupForm(forms.ModelForm):
class Meta:
model = LocationsGroup
fields = ('group_name', 'slug', 'customer', 'location', 'user_id',)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('id')
super(LocGroupForm, self).__init__(*args, **kwargs)
self.fields['customer'].queryset = Customers.objects.get(pk=qs)
self.fields['location'].queryset = CustomerLocations.objects.filter(customer_id=qs)
self.fields['user_id'].queryset = CustomerUsers.objects.filter(customer_id=qs)
here is my view. it's just a generic view
views.py
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
the template is a dry template I use for all my forms- admittedly something I mostly stole from a tutorial
{% extends "ohnet/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{% load static %}
<div class="container">
<h1>{{ title }}</h1>
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="submit" value="Submit">
</form>
</div>
{% endblock content %}
This is the KeyError from the form load.
You need to pass a value for the id when constructing the LocGroupForm, you can do that by overriding get_form_kwargs:
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['id'] = …
return kwargs
You will need to fill in the … that specifies the value passed as id to the form. This might for example be self.request.user.pk, or a URL parameter with self.kwargs['name-of-url-parameter']

Modify one form instance in a Django formset?

I have a Django formset in which I render four instances of a form. Each form in the formset has two fields, but I want the last (4th) instance to only show/input one field. How can I do this, or is there a better way? I tried limiting the formset to three fields and making the fourth instance its own form, but I need to validate that field1 against the field1 fields in the formset. I couldn't see how to validate a form against a simultaneously submitted formset.
views.py:
FormSet = formset_factory(MyForm, formset=BaseMyFormSet, extra=4)
.html:
<form action="" method="post">{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors }}
<div>
{% for form in formset %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
{% endfor %}
{% endfor %}
</div>
<p><input type="submit" value="Submit" /></p>
</form>
The formset has two fields in forms.py:
class MyForm(forms.Form):
# field1
# field2
How do I make it so the first three forms in the formset have two fields, but the last only contains field1?
Try explicitly removing the field for the last form in your views.py:
def view_func(request):
FormSet = formset_factory(MyForm, formset=BaseMyFormSet, extra=4)
if request.method == 'POST':
formset = FormSet(request.POST, request.FILES)
# assuming this is a required field for the other forms
formset.forms[-1].fields['field2'].required = False
if formset.is_valid():
...
else:
formset = FormSet()
del formset.forms[-1].fields['field2']
return render(request, 'form.html', {'formset': formset})
Any further adjustments depend on your form and validation logic. BaseMyFormSet.clean() is the appropriate place for formset-wide validation and it sounds like you already have code there to compare field1 across forms.
(From a purist sense, it might be better to have the FormSet class handle this entirely, but this is easier. FormSet code is decently complex. It'd make sense to override BaseFormSet.forms() but with that #cached_property bit, you're involved with implementation details best left to Django.)

django display ManyToManyField as a form field

I am trying to display a ManyToManyField in my template:
class GvtCompo(models.Model):
startDate=models.DateField(max_length=10, blank=False, null=False)
endDate=models.DateField(max_length=10, blank=False, null=False)
gvtCompo= models.CharField(max_length=1000, blank=False, null=False)
class Act(models.Model):
gvtCompo= models.ManyToManyField(GvtCompo)
In my view, I can display the object, no problem:
for gvtCompo in act.gvtCompo.all():
print gvtCompo.gvtCompo
In my template, I have a list of "GvtCompo Object" with this code (not nice):
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field }}
</div>
{% endfor %}
I have tried to make it nicer, but the following code just not work (nothing appears):
{% for field in form %}
{% if field.name == "gvtCompo" %}
{% for gvtCompo in field.gvtCompo.all %}
{{ gvtCompo.gvtCompo }}
{% endfor %}
{% endif %}
{% endfor %}
What's wrong?
*Edit: *
If I don't use the form but an instance of the model (act) passed to render_to_response it displays the ManyToManyField values
{% for gvtCompo in field.gvtCompo.all %}
changed to
{% for gvtCompo in act.gvtCompo.all %}
However there is not form field anymore, so it can't be modified, validated and saved!
You are skipping a step. You first need to create a form.
In forms.py you can create a ModelForm. A ModelForm is a form based on your model:
from django.forms import ModelForm
from myapp.models import Act
class ActForm(ModelForm):
class Meta:
model = Act
Then your view:
from myapp.models import Act
from myapp.forms import ActForm
def add_view(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
else:
form = ActForm() # Creating a empty form.
return render_to_response("template.html", {
"form": form,
})
def edit_view(request):
obj = Act.objects.get(pk=1)
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
else:
form = ActForm(instance=obj) # Creating form pre-filled with obj.
return render_to_response("template.html", {
"form": form,
})
If you want to implement this situation more than once. DRY: https://docs.djangoproject.com/en/1.5/topics/class-based-views/intro/#handling-forms-with-class-based-views
In your template.html:
{{ form }}
Disclaimer: This code is not tested.
https://docs.djangoproject.com/en/1.5/ref/forms/
https://docs.djangoproject.com/en/1.5/topics/forms/modelforms/
Update:
You can pass multiple forms to one <form>...</form> in your template. So create two forms. One Act form (see above) and one GvtCompo formset. The formset contains all GvtCompo's that have a relation to Act.
from django.forms.models import modelformset_factory
act = Act.objects.get(pk=1) #The same act as you used to create the form above.
GvtFormSet = modelformset_factory(GvtCompo)
formset = GvtFormSet(queryset=act.gvtCompo.all())
Template can be:
<form ...>
{% for field in form %}
{% if field.name == "gvtCompo" %}
{{ formset }}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
</form>
Note: If your form and formset have colliding field names use prefix="some_prefix":
Django - Working with multiple forms
When looping over the form, it should be:
{% for field in form %}
{% if field.name == "gvtCompo" %}
{% for gvtCompo in form.instance.gvtCompo.all %}
{{ gvtCompo.gvtCompo }}
{% endfor %}
{% endif %}
{% endfor %}
field itself has no related field.gvtCompo.

Edit a Key/Value Parameters list Formset

I'm looking for a convenient solution to create an 'edit settings' key/values page.
Parameters model :
class Parameter(models.Model):
key = models.CharField(max_length=50)
value = models.CharField(max_length=250)
showInUI = models.SmallIntegerField()
Initial Keys/Values are already inserted in table.
I load them and send them using a model formset factory using these lines :
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('key', 'value'))
parameterFormSet = ParameterFormSet(queryset=Parameter.objects.filter(showInUI=1))
return render_to_response('config.html', {'parameterFormSet': parameterFormSet}, context_instance=RequestContext(request))
Template side, when formset is displayed, keys and values are shown as inputs.
I'd like to find a convenient way to display form keys as readonly labels and values as inputs. And, when submited, validate them according django standards.
I've read a lot of stuff, I guess the solution may be a custom widget, but I could find a reliable solution.
Thanks for reading.
EDIT :
Working solution
views.py
def config(request):
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',))
if request.method == "POST":
try:
formset = ParameterFormSet(request.POST, request.FILES)
except ValidationError:
formset = None
return HttpResponse("ko")
if formset.is_valid():
formset.save()
return HttpResponse("ok")
#ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',))
parameterFormSet = ParameterFormSet(queryset=Parameter.objects.filter(showInUI=1))
return render_to_response('config.html', {'parameterFormSet': parameterFormSet}, context_instance=RequestContext(request))
template
<form method="post">
{% csrf_token %}
{{ parameterFormSet.management_form }}
{% for form in parameterFormSet %}
<div>
{{ form.instance.key }}
{{ form }}
</div>
{% endfor %}
<input type="submit" />
</form>
If you do not want the value to be editable, don't include it in fields when creating the form set.
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',)) # don't forget the trailing comma after 'value' otherwise it's not a tuple!
In your template, you can then loop through the forms in the form set, and display the key at the same time.
{% for form in parameter_form_set %}
{{ form.instance.key }}{# display the key related to this form #}
{{ form }}{# display the form #}
{% endfor %}