Bug? ModelChoiceField with to_field_name incorrectly returns True on has_changed() function

25 views
Skip to first unread message

Tim Poffenbarger

unread,
Nov 5, 2016, 9:29:13 AM11/5/16
to Django users
Should this be considered a bug? When you provide the to_field_name to a ModelChoiceField, the instance object's field (the primary key) will not match the form's field (the to_field_name field name).  Therefore the has_changed() function will return True.  In my example, I created an app called 'app'.

############ app/models.py ###########
from django.conf import settings
from django.db import models


class Account(models.Model):
""" Each user has an account """
name = models.CharField(max_length=64)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
description = models.TextField()


############ app/forms.py ###########
from django import forms
from django.apps import apps
from django.contrib.auth import get_user_model

class AccountForm(forms.ModelForm):

class Meta:
model = apps.get_model('app.Account')
fields = ['user', 'name', 'description']

user = forms.ModelChoiceField(get_user_model().objects, to_field_name='username')


############ FROM DJANGO SHELL ###########
In [1]: from app.models import Account
In [2]: from app.forms import AccountForm
In [3]: from django.contrib.auth import get_user_model
In [4]: username = 'mynewname'
In [5]: usr = get_user_model().objects.get_or_create(username=username)[0]
In [6]: obj = Account.objects.get_or_create(name='abc', user=usr, description='aaa')[0]
In [7]: new_form = {'name': obj.name, 'user': username, 'description': 'aaa'}
In [8]: frm = AccountForm(new_form, instance=obj)
In [9]: assert frm.is_valid()
In [10]: assert frm.cleaned_data['user'] == usr
In [11]: assert frm.cleaned_data['user'] == obj.user
In [12]: frm.has_changed()
Out[12]: True
In [13]: frm.changed_data
Out[13]: ['user']


##########################################
# My Override of the ModelChoiceField #
##########################################


############# app/forms.py ############
from django import forms
from django.apps import apps
from django.contrib.auth import get_user_model


class UsernameToObjectField(forms.ModelChoiceField):
""" Take a the value of a name and return the object. """

def has_changed(self, initial, data):
if self.to_field_name is not None:
data_value = self.to_python(data) if data is not None else ''
if data_value is not None:
data_value = data_value.pk
else:
data_value = data
return super(UsernameToObjectField, self).has_changed(initial, data_value)


class AccountForm(forms.ModelForm):

class Meta:
model = apps.get_model('app.Account')
fields = ['user', 'name', 'description']

user = UsernameToObjectField(get_user_model().objects, to_field_name='username')

############ FROM DJANGO SHELL ###########
In [1]: from app.models import Account
In [2]: from app.forms import AccountForm
In [3]: from django.contrib.auth import get_user_model
In [4]: username = 'mynewname'
In [5]: usr = get_user_model().objects.get_or_create(username=username)[0]
In [6]: obj = Account.objects.get_or_create(name='abc', user=usr, description='aaa')[0]
In [7]: new_form = {'name': obj.name, 'user': username, 'description': 'aaa'}
In [8]: frm = AccountForm(new_form, instance=obj)
In [9]: assert frm.is_valid()
In [10]: assert frm.cleaned_data['user'] == usr
In [11]: assert frm.cleaned_data['user'] == obj.user
In [12]: frm.has_changed()
Out[12]: False
In [13]: frm.changed_data
Out[13]: []
Reply all
Reply to author
Forward
0 new messages