I created an app (survey) then created a form using a model with model
form interface view and template. Everything works except when I added a
form for editing existing model values, the form fields with default
values don't get populated. Here's a snippet from my implementation:
{{{
#!div style="font-size: 80%"
models.py
{{{#!python
from django.db import models
class Survey(models.Model):
class Meta:
"""Set composite key for file_number/location fields"""
unique_together = (('file_number', 'court_location', ))
file_number = models.CharField(max_length=127)
location = models.ForeignKey(Location, on_delete=models.PROTECT)
}}}
}}}
{{{
#!div style="font-size: 80%"
forms.py
{{{#!python
from django import forms
class SurveyForm(forms.ModelForm):
"""Survey Form along with customizations"""
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Only show locations available to the user
locations =
Location.objects.filter(contract__user_id=self.user.id)
self.fields['location'].queryset = locations
class Meta:
model = Survey
fields = '__all__'
}}}
}}}
{{{
#!div style="font-size: 80%"
views.py
{{{#!python
class SurveyEdit(View):
"""Edit form for SurveyForm class"""
def get(self, request, survey_id):
survey_obj = Survey.objects.get(id=survey_id)
survey_form = SurveyForm(
request.GET, user=request.user, instance=survey_obj)
return render(
request,
'survey_edit_form.html',
{'survey_form': survey_form, 'survey_id': survey_id}
)
def post(self, request, survey_id):
sf = SurveyForm(
request.POST,
user=request.user,
instance=Survey.objects.get(id=survey_id))
if sf.is_valid():
sf.save()
messages.add_message(
request,
messages.SUCCESS,
"Survey {} was
updated".format(sf.cleaned_data['file_number'])
)
return HttpResponseRedirect('/survey/list')
error_message(sf, request)
return render(
request,
'survey_edit_form.html',
{'survey_form': sf, 'survey_id': survey_id}
)
}}}
}}}
{{{
#!div style="font-size: 80%"
survey_edit_form.html
{{{#!jinja
{% extends "base.html" %}
{% block title %}
{% block head_title %}
Edit Survey
{% endblock head_title %}
{% endblock title %}
{% block content %}
<div class="row">
<div class="col-md-6 offset-md-3">
<form action="{% url "survey:edit" survey_id=survey_id %}"
method=POST>
{% csrf_token %}
{% for field in survey_form %}
<div class='form-group'>
<div class='label'>{{ field.label }}</div>
{{ field }}
</div>
{% endfor %}
<input type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}
}}}
}}}
{{{
#!div style="font-size: 80%"
url.py
{{{#!python
path('edit/<int:survey_id>', login_required(SurveyEdit.as_view()),
name='edit'),
}}}
}}}
I also have the following test case, which verifiees that the data is
loaded into the form
{{{
#!div style="font-size: 80%"
tests.py
{{{#!python
def test_006_edit_data_is_loaded(self):
"""When editing a survey through SurveyForm, verify Survey data is
loaded"""
client = Client()
client.force_login(self.user)
# create survey object from generated data
edit_survey_data = copy(gen_survey_data(self))
edit_survey = Survey(**edit_survey_data)
edit_survey.save()
# go to edit page
edit_url = '/survey/edit/{}'.format(edit_survey.id)
resp = client.get(edit_url)
# verify that field values were loaded
content = str(resp.content)
self.assertIn(edit_survey_data['file_number'], content)"
}}}
}}}
The problem seems to be somewhere either in django.forms.boundfield
{{{
#!div style="font-size: 80%"
{{{#!python
def value(self):
data = self.initial
if self.form.is_bound:
data = self.field.bound_data(self.data, data)
return self.field.prepare_value(data)
}}}
}}}
Where data is correctly assigned from self.initial value (which is taken
from instance param passed to SurveyForm). However, self.field.bound_data
method seems to return wrong value,
{{{
#!div style="font-size: 80%"
In this code snippet:
{{{#!python
if self.disabled:
return initial
return data
}}}
}}}
The initial value returned only when the field is disabled, which should
not be the case, I want default data to be displayed when request.GET is
passed to render ModelForm, in my case the check should be, if there's no
updated data, return initial data i.e.
{{{
#!div style="font-size: 80%"
Code highlighting:
{{{#!python
if data:
return data
return initial
}}}
}}}
This seems to fix the issue I have and when I make the change the default
values are displayed in edit field, however I looked at history of these
two files (git blame) and didn't find anything that's been changed
recently (all the changes are from 2-3 years ago), so I'm not sure if this
is something I'm doing wrong or there was a bug introduced in django.forms
in some other way?
--
Ticket URL: <https://code.djangoproject.com/ticket/29407>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* status: new => assigned
* owner: nobody => oliver
--
Ticket URL: <https://code.djangoproject.com/ticket/29407#comment:1>
* owner: oliver => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/29407#comment:2>
* status: new => assigned
* owner: (none) => oliver
--
Ticket URL: <https://code.djangoproject.com/ticket/29407#comment:3>
* owner: oliver => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/29407#comment:4>
* status: new => closed
* resolution: => invalid
Comment:
Thanks for the report, but I would suggest you first search help in
[https://code.djangoproject.com/wiki/TicketClosingReasons/UseSupportChannels
Django support channels] and ensure that Django is at fault before opening
a ticket.
I'm sure reading https://docs.djangoproject.com/en/2.0/topics/class-based-
views/generic-editing/ could already help you.
--
Ticket URL: <https://code.djangoproject.com/ticket/29407#comment:1>