Hello everyone,
I'm having some problem with django.forms edit dialog and I believe it might be a bug in django that's causing this.
I created a project with Django 2.0.4 running on Python 3.6.5 with cookiecutter django template.
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:
#models.py
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)
# forms.py
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__'
# views.py
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}
)
# survey_edit_form.html template
{% 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 %}
# urls.py
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
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
https://github.com/django/django/blob/c591bc3ccece1514d6b419826c7fa36ada9d9213/django/forms/boundfield.py#L126
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,
https://github.com/django/django/blob/c591bc3ccece1514d6b419826c7fa36ada9d9213/django/forms/fields.py#L161
In this code snippet:
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.
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.
Please help.
-- I'm having some problem with django.forms edit dialog and I believe it might be a bug in django that's causing this.
I created a project with Django 2.0.4 running on Python 3.6.5 with cookiecutter django template.
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:
#models.py
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)
# forms.py
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__'
# views.py
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}
)
# survey_edit_form.html template
{% 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 %}
# urls.py
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
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
https://github.com/django/django/blob/c591bc3ccece1514d6b419826c7fa36ada9d9213/django/forms/boundfield.py#L126
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,
https://github.com/django/django/blob/c591bc3ccece1514d6b419826c7fa36ada9d9213/django/forms/fields.py#L161
In this code snippet:
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.
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.
Please help.
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/ef9f9d61-cd62-4c20-b9e6-cc7279cb33fc%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
No comments:
Post a Comment