Reputation: 417
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?
Upvotes: 0
Views: 678
Reputation: 322
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
.
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
.
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.
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
Upvotes: 1