#34918 closed Bug (invalid)

Assigning model instance to `_id`/attname field saves correctly, but breaks accessing the field

Reported by: Ricardo Busquet Owned by: nobody
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Model definition:

class Author(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

class Book(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    author = models.ForeignKey(
        Author, null=True, blank=True, on_delete=models.CASCADE
    )
In [1]: book = Book.objects.create()

In [2]: author = Author.objects.create()

In [3]: book.author_id

In [4]: book.author

In [5]: book.author_id = author

In [6]: book.save()

In [7]: book.author
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
...
ValidationError: ['“Author object (caaf8f9a-f934-44e9-8e6b-00d71d116acf)” is not a valid UUID.']

Similarly, if someone tries to use book.author_id to fetch an author using Author.objects.get(id=book.author_id) will also fail with a similar error.

Change History (3)

comment:1 by Rahul Beniwal, 13 months ago

Hi Ricardo Busquet

I checked this, and you are right. But instead of assigning book.author_id=author to book.author=author it seems to be working fine.

In [23]: book.author = author

In [24]: book.save()

In [25]: book.author_id
Out[25]: UUID('857acf04-960a-40ae-9c85-8c363b0a0dfd')

In [26]: book.author
Out[26]: <Author: Author object (857acf04-960a-40ae-9c85-8c363b0a0dfd)>

In [27]: Author.objects.get(id=book.author_id)
Out[27]: <Author: Author object (857acf04-960a-40ae-9c85-8c363b0a0dfd)>

Can we update the documentation for the UUID field to aware reader about this situation or code level changes are needed?

Please feel free to assign this to me

comment:2 by Tim Graham, 13 months ago

Django generally doesn't validate model attribute assignments. For example, if Book has a title CharField, you can assign it the wrong type, like datetime and it'll be silently coerced to string. I don't think we're going to add this sort of validation or document it, but I will leave this ticket open for a second opinion in case I'm overlooking something.

Incidentally, similar steps with id as an AutoField also save correctly but subsequently fail on access:

Traceback (most recent call last):
  File "/home/tim/code/django/django/db/models/fields/related_descriptors.py", line 235, in __get__
    rel_obj = self.field.get_cached_value(instance)
  File "/home/tim/code/django/django/db/models/fields/mixins.py", line 15, in get_cached_value
    return instance._state.fields_cache[cache_name]
KeyError: 'reporter'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tim/code/django/django/db/models/fields/__init__.py", line 2116, in get_prep_value
    return int(value)
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'Reporter'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/tim/code/django/tests/many_to_one/tests.py", line 856, in test_cached_relation_invalidated_on_save
    self.assertEqual(self.a.reporter, self.r2)
  File "/home/tim/code/django/django/db/models/fields/related_descriptors.py", line 253, in __get__
    rel_obj = self.get_object(instance)
  File "/home/tim/code/django/django/db/models/fields/related_descriptors.py", line 216, in get_object
    return qs.get(self.field.get_reverse_related_filter(instance))
  File "/home/tim/code/django/django/db/models/query.py", line 633, in get
    clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
  File "/home/tim/code/django/django/db/models/query.py", line 1476, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "/home/tim/code/django/django/db/models/query.py", line 1494, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/home/tim/code/django/django/db/models/query.py", line 1501, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1599, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1631, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1458, in build_filter
    return self._add_q(
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1631, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1545, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/home/tim/code/django/django/db/models/sql/query.py", line 1375, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "/home/tim/code/django/django/db/models/lookups.py", line 30, in __init__
    self.rhs = self.get_prep_lookup()
  File "/home/tim/code/django/django/db/models/lookups.py", line 362, in get_prep_lookup
    return super().get_prep_lookup()
  File "/home/tim/code/django/django/db/models/lookups.py", line 88, in get_prep_lookup
    return self.lhs.output_field.get_prep_value(self.rhs)
  File "/home/tim/code/django/django/db/models/fields/__init__.py", line 2118, in get_prep_value
    raise e.__class__(
TypeError: Field 'id' expected a number but got <Reporter: Paul Jones>.

comment:3 by Mariusz Felisiak, 13 months ago

Resolution: invalid
Status: newclosed

I agree with Tim.

Note: See TracTickets for help on using tickets.
Back to Top