3

I'm using Django 1.8.3 with Rest Framework and json-api (https://github.com/django-json-api/django-rest-framework-json-api). I have this OneToOne Relationship:

class CalendarBlock(models.Model):
       vehiclecheck = models.OneToOneField('vehiclecheck.VehicleCheck',
                                        null=True, blank=True,
                                        related_name='calendar_block'
    )

    [...]


 class VehicleCheck(models.Model):
        [...]

Now the problem is, the relationship can be "empty". Which works, when going from CalendarBlock to Vehiclecheck, but not in the reverse relationship:

In [1]: from vehiclecheck.models import VehicleCheck

In [2]: from dispo_calendar.models import CalendarBlock

In [3]: CalendarBlock.objects.first().vehiclecheck

In [4]: # no problem here

In [5]: VehicleCheck.objects.first().calendar_block
Out[5]: <CalendarBlock: CalendarBlock object>

In [6]: VehicleCheck.objects.get(pk=398).calendar_block
---------------------------------------------------------------------------
RelatedObjectDoesNotExist                 Traceback (most recent call last)
<ipython-input-6-65d3178686f5> in <module>()
----> 1 VehicleCheck.objects.get(pk=398).calendar_block

/home/sh/gitty/work/tcs_cardispo2_backend/.venv/lib/python3.5/site-packages/django/db/models/fields/related.py in __get__(self, instance, instance_type)
    468                 "%s has no %s." % (
    469                     instance.__class__.__name__,
--> 470                     self.related.get_accessor_name()
    471                 )
    472             )

RelatedObjectDoesNotExist: VehicleCheck has no calendar_block.

Edit: My main problem is that since I'm using rest_framework, I can't use exception handling or the like because I don't call access the data explicitly.

arsenbonbon
  • 748
  • 9
  • 22

2 Answers2

12

Django has some issues regarding OneToOne and ForeignKey related fields. Concretely assuming:

class A(Model):
   # assume these models exist
   b = ForeignKey(B, null=True)
   c = ForeignKey(C)
   d = OneToOneField(D, null=True, related_name="a")
   e = OneToOneField(E, related_name="a")
  • If you want to retrieve A().b or A().d, you get None.
  • If you want to retrieve A().c, a C.DoesNotExist error is raised.
  • If you want to retrieve A().e, an E.DoesNotExist error is raised.
  • If you want to retrieve B().a_set.all() or C().a_set.all(), you get a queryset with no elements.
  • If you want to retrieve D().a, or E().a, an A.DoesNotExist error is raised.

The examples I gave you are related to design decision in the framework, and not bugs. Also, the important fact is not that they are new instances, but that they have empty references (or no O2O is referencing them, respectively). Summary:

  • Retrieving a null=False (default value for named argument) FK field will raise a target.DoesNotExist if the field is None (and of course the instance is not yet saved, since None would not be allowed for such field). This is specially true for newly created models you're validating their fields in clean(self) and perhaps the user did not populate such field, and you retrieve it. The same applies for OneToOne.
  • Retrieving the reverse side of a OneToOne, when the direct side has (would have) a value of None will raise a source.DoesNotExist error.
Luis Masuelli
  • 12,079
  • 10
  • 49
  • 87
  • 1
    Great summary. As a side note, if the OneToOneField exception behavior is undesirable, you can do `c=ForeignKey(C, unique=True)` to get the same database structure as a OneToOneField, but the behavior of `C().a_set.all()` above. (If you do that, you'll probably want to add the line `SILENCED_SYSTEM_CHECKS = ["fields.W342"]` to your settings.py. Otherwise, on every startup Django will print a "Hint" that tries to sell you on using OneToOneField.) – Luke Oct 03 '19 at 17:34
2

Every CalendarBlock instance will have vehiclecheck either null or not null according to your definition, the same doesn't apply for VehicleCheck instances, you are gonna have to check for that first so you can avoid the RelatedObjectDoesNotExist. You can do something like this:

vehicle_check = VehicleCheck.objects.get(pk=398)
if hasattr(vehicle_check, 'calendar_block'):
    calendar_block = vehicle_check.calendar_block
César
  • 9,939
  • 6
  • 53
  • 74
  • I can't do that because I'm using the serializers and ModelViewSet, so the data is fetched by those libraries "automatically". – arsenbonbon Nov 09 '15 at 16:02