Skip to content

When accessing missing reverse OneToOne, hasattr raises an exception #1588

@noamkush

Description

@noamkush

When two models have history and a HistoricOneToOneField relation, using hasattr (or getattr with a default) on the reverse side raises an error.

To Reproduce

class A(Model):
    history = HistoricalRecords()


class B(Model):
    history = HistoricalRecords()
    a = HistoricOneToOneField(A, on_delete=models.CASCADE)


a = A.objects.create()
assert not hasattr(a, 'b')  # Works fine

historic_a = a.history.as_of(now())
assert not hasattr(historic_a, 'b')  # Raises an error
  • Django Simple History Version: 3.11.0
  • Django Version: 5.2.11

The error I get is

    assert not hasattr(historic_a, 'b')
               ~~~~~~~^^^^^^^^^^^^^^^^^
  File "lib/python3.14/site-packages/django/db/models/fields/related_descriptors.py", line 523, in __get__
    rel_obj = self.get_queryset(instance=instance).get(**filter_args)
  File "lib/python3.14/site-packages/django/db/models/query.py", line 635, in get
    raise self.model.DoesNotExist(
        "%s matching query does not exist." % self.model._meta.object_name
    )
app_name.HistoricalB.DoesNotExist: HistoricalB matching query does not exist.

After looking into this, the source of the bug is Django's ReverseOneToOneDescriptor.__get__:

try:
    rel_obj = self.get_queryset(instance=instance).get(**filter_args)
except self.related.related_model.DoesNotExist:
    rel_obj = None

As you can see, it assumes that the model for get_queryset is self.related.related_model which is incorrect for HistoricReverseOneToOneDescriptor which inherits HistoricDescriptorMixin that overrides it.

I worked around this by inheriting HistoricReverseOneToOneDescriptor and reimplementing the __get__ but replacing this part with

queryset = self.get_queryset(instance=instance)
try:
    rel_obj = queryset.get(**filter_args)
except queryset.model.DoesNotExist:
    ...

So, should I:

  • Open a PR with my workaround?
  • Try to upstream the fix in Django?

Other suggestions are welcome

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions