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'.
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]: []