Opened 8 years ago

Last modified 7 weeks ago

#28594 new Bug

Value error on related user name during save of user model

Reported by: Axel Rau Owned by:
Component: contrib.auth Version: 1.11
Severity: Normal Keywords: Value error, user model, normalize_username
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Tim Graham)

problem:
While upgrading from 1.9 to 1.11, ForwardManyToOneDescriptor.__set__() tries to assign a string (description of the related instance) to the related user name field of user model.
diagnosis:
In 1.10 normalize_username(), a class method of AbstractBaseUser, has been introduced. This class method forces the related user name instance to a string.
Fix:
Overwriting this with a method which just returns the 'username' fixes the problem.

Details:

class AbstractEmailUser(AbstractBaseUser, PermissionsMixin, FieldlistForDetailTemplateMixin):
   localemail = models.OneToOneField('Mailbox', verbose_name=_('Local E-mail'),
           related_name='localemail', db_column='localemail',
           editable=('UR', 'UE', 'UL'))

   objects = UserManager()

   USERNAME_FIELD = 'localemail'
   REQUIRED_FIELDS = []

   class Meta:
       abstract = True
       ordering = ['localemail']

   def get_username(self):
      return getattr(self, self.USERNAME_FIELD)

class Mailbox(models.Model):
   id = models.AutoField(primary_key=True)
   localpart = models.CharField(_('Localpart'), max_length=40)
   localdomainfk = models.ForeignKey(Localdomain,  verbose_name=_('Domain'), db_column='localdomainfk', editable=('AL',))
	…

   def __str__(self):
	return self.localpart+ '@'+self.localdomainfk.name


Internal Server Error: /admin/erdb/account/19/change/
Traceback (most recent call last):
 File "...python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
   response = get_response(request)
 File "...python3.5/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
   response = self._get_response(request)
 File "...python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
   response = self.process_exception_by_middleware(e, request)
 File "...python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
   response = wrapped_callback(request, *callback_args, **callback_kwargs)
 File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/contextlib.py", line 30, in inner
   return func(*args, **kwds)
 File "...python3.5/site-packages/django/contrib/admin/options.py", line 551, in wrapper
   return self.admin_site.admin_view(view)(*args, **kwargs)
 File "...python3.5/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
   response = view_func(request, *args, **kwargs)
 File "...python3.5/site-packages/django/views/decorators/cache.py", line 57, in _wrapped_view_func
   response = view_func(request, *args, **kwargs)
 File "...python3.5/site-packages/django/contrib/admin/sites.py", line 224, in inner
   return view(request, *args, **kwargs)
 File "...python3.5/site-packages/django/contrib/admin/options.py", line 1511, in change_view
   return self.changeform_view(request, object_id, form_url, extra_context)
 File "...python3.5/site-packages/django/utils/decorators.py", line 67, in _wrapper
   return bound_func(*args, **kwargs)
 File "...python3.5/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
   response = view_func(request, *args, **kwargs)
 File "...python3.5/site-packages/django/utils/decorators.py", line 63, in bound_func
   return func.__get__(self, type(self))(*args2, **kwargs2)
 File "...python3.5/site-packages/django/contrib/admin/options.py", line 1408, in changeform_view
   return self._changeform_view(request, object_id, form_url, extra_context)
 File "...python3.5/site-packages/django/contrib/admin/options.py", line 1440, in _changeform_view
   if form.is_valid():
 File "...python3.5/site-packages/django/forms/forms.py", line 183, in is_valid
   return self.is_bound and not self.errors
 File "...python3.5/site-packages/django/forms/forms.py", line 175, in errors
   self.full_clean()
 File "...python3.5/site-packages/django/forms/forms.py", line 386, in full_clean
   self._post_clean()
 File "...python3.5/site-packages/django/forms/models.py", line 408, in _post_clean
   self.instance.full_clean(exclude=exclude, validate_unique=False)
 File "...python3.5/site-packages/django/db/models/base.py", line 1234, in full_clean
   self.clean()
 File "...python3.5/site-packages/django/contrib/auth/base_user.py", line 77, in clean
   setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))
 File "...python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 216, in __set__
   self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "'unpriv@framailx.de'": "Account.localemail" must be a "Mailbox" instance.
[21/Aug/2017 16:08:37] "POST /admin/erdb/account/19/change/ HTTP/1.1" 500 166385

Discussion:
https://www.mail-archive.com/django-users@googlegroups.com/msg178769.html

Attachments (1)

28594.diff (844 bytes ) - added by Axel Rau 8 years ago.
git diff of documentation patch

Download all attachments as: .zip

Change History (7)

comment:1 by Tim Graham, 8 years ago

Description: modified (diff)

Can you propose a patch for Django?

comment:2 by Axel Rau, 8 years ago

The attached trivial patch 28594.diff works for me with Django 1.11.4 on Python 3.5.3

comment:3 by Melvyn Sopacua, 8 years ago

Has patch: set
Needs documentation: set
Patch needs improvement: set
Triage Stage: UnreviewedAccepted

At the very least this should use six.string_types on 1.x branches. But I think this is a documentation issue. If you change the type of the username field, then you should reimplement normalize_username or prevent it being called.
However, Django's documentation says:

:attr:USERNAME_FIELD now supports
:class:~django.db.models.ForeignKey\s. Since there is no way to pass
model instances during the :djadmin:createsuperuser prompt, expect the
user to enter the value of :attr:~django.db.models.ForeignKey.to_field
value (the :attr:~django.db.models.Field.primary_key by default) of an
existing instance.

The current behavior of clean() and UserManager._create_user() does not support foreign keys as it should expect to be handed model instances and not call normalize_username or as the patch suggests, normalize_username should only act on strings.

by Axel Rau, 8 years ago

Attachment: 28594.diff added

git diff of documentation patch

comment:4 by Axel Rau, 8 years ago

I agree, that this is a corner case and can be prevented by a hint in the documentaion.
My proposal of the doc enhancement is attached. It replaces my original code patch.

comment:5 by Michal Mládek, 7 weeks ago

Owner: changed from nobody to Michal Mládek
Status: newassigned

I will do some research here, but it seems done or old or unclosed ...

comment:6 by Michal Mládek, 7 weeks ago

Owner: Michal Mládek removed
Status: assignednew

Hi, and thanks for the opportunity to look into this ticket.

It seems that the issue has already been addressed in some form (via documentation updates), and it may have just been left open by oversight. In any case, the problem is no longer present in supported Django versions — as of 3.0, using a ForeignKey for USERNAME_FIELD is unsupported.

Given that, I would suggest closing this ticket as either resolved or invalid, unless there’s still something outstanding I may have missed.

Last edited 7 weeks ago by Michal Mládek (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top