Monday, August 21, 2017

Re: 1.11: Value error on related user name during save of user model



On Mon, Aug 21, 2017 at 7:50 AM, Axel Rau <Axel.Rau@chaos1.de> wrote:
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.

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



<snip>
 
  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

Any help to resolve this appreciated,
Axel


You've set your USERNAME_FIELD to a field that is an FK to another table. I've never seen that before, but apparently the docs say it is supported. To be fair, that's the extent of the documentation on how to utilize an FK as the username field. 


It makes sense to me that this particular operation doesn't work. You're asking (by way of the auth model definition through a ModelForm, I'm assuming) for the string value of 'unpriv@framailx.de' to be set as the value of an FK field on the Account model (which I assume is the one that inherits from AbstractEmailUser) for the localemail field, when in reality you would want to set a value that matches the PK of the related row in the Mailbox model. Django will perform this PK magic automatically if you provide a Mailbox object as the value for Account.localemail, rather than the email string itself (per the docs). It will not automatically convert an email address to a Mailbox object, or determine which row should be referenced by Account.localemail without help, as far as I know.

TL;DR; the error occurs when 'unpriv@framailx.de' is set on Account.localemail, when Account.localemail is expecting either a Mailbox object (that has a PK), or an integer matching a PK in the Mailbox table. It doesn't know how to translate a raw email address string to a Mailbox object.

I suppose the 'fix' would be to retrieve a Mailbox object based on the email address provided, and use that as the value for Account.localmail. You should be able to implement that in a custom ModelForm as part of clean_localemail(). 

If this was working under 1.9 (I'd be surprised if it was), then I would file this as a backward-incompatibility bug with the bug tracker. It's possible that 1.9 did do the type/field conversion using some black magic, but with the information in your model, I don't know how it could have made that determination. 

From a design perspective, the relation you presented seems backwards to me. I would think the Account model would hold the canonical username, and Mailbox would have the FK back to the Account. That design would not require the use of a username field as an FK, simplifying everything (authentication, forms, views, etc.). Your current design would implement excessive SQL JOIN's to pull relatively simple data on every request. 

Or, I could be completely wrong. :-D

-James


--
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/CA%2Be%2BciVstk8eDLSkJijVaxMU8%3DXxjJ35AXZcu4yWh8M4gkxcdw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment