Hippolyte BRINGER
Hippolyte BRINGER

Reputation: 863

Django : OneToOneField - RelatedObjectDoesNotExist

I have this two following classes in my model:

class Answer(models.Model):
    answer = models.CharField(max_length=300)
    question = models.ForeignKey('Question', on_delete=models.CASCADE)

    def __str__(self):
        return "{0}, view: {1}".format(self.answer, self.answer_number)


class Vote(models.Model):
    answer = models.OneToOneField(Answer, related_name="votes", on_delete=models.CASCADE)
    users = models.ManyToManyField(User)

    def __str__(self):
        return str(self.answer.answer)[:30]

In the shell I take the first Answer:

>>> Answer.objects.all()[0]
<Answer: choix 1 , view: 0>

I want to get the Vote object:

>>> Answer.objects.all()[0].votes
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\Hippolyte\AppData\Roaming\Python\Python38\site-packages\django\db\models\fields\related_descriptors.py", line 420, in __get__
    raise self.RelatedObjectDoesNotExist(
questions.models.Answer.votes.RelatedObjectDoesNotExist: Answer has no votes.

But an error occured.

I don't understand why the related_name is not recognized. Could you help me ?

Upvotes: 7

Views: 8530

Answers (3)

Victor
Victor

Reputation: 2909

Your related_name IS recognized, but it is only assigned to the instance if the related object exists.

In your case, there is no Vote instance in your database where its answer field points to your Answer instance

Just catch the exception and return None if you want to proceed:

answer = Answer.objects.all().first()
try:
   vote = answer.votes
except Answer._meta.model.related_field.RelatedObjectDoesNotExist as e:
   vote = None

If you want to shorten this you can use hasattr(answer, 'vote'), to check, but this will mask ALL exceptions arising from the db lookup if any

answer = Answer.objects.all().first()
vote = answer.votes if hasattr(answer, 'votes') else None

Note that since you used a OneToOneField, answer.votes will always return a single instance if the related Vote exists. As such, it would be more appropriate to use related_name='vote' (without the s)

Upvotes: 14

ceorcham
ceorcham

Reputation: 94

Check if your Answer has a Vote by querying the Vote model without using the OneToOne relationship

Try doing:

ans = Answer.objects.all()[0]
Vote.objects.filter(answer_id = ans.id).all()

Extra tip, you should use a singular word on related_name (vote instead of votes), because it is a OneToOne, meaning an Answer can only can one Vote (and vice-versa)

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477160

You used a OneToOneField here. This thus means that each Vote points to a different Anwer object. There is thus at most one Vote per Answer. A query like some_answer.votes will immedately query for that Vote object, and if it does not exists, it will raise a RelatedObjectDoesNotExist error (which is a subclass of a ObjectDoesNotExist exception). So the related_name itself is recognized, but there is no related Vote object.

It is however suprising to use a OneToOneField here. It means that each vote points to a unique Answer? I think you probably want to use a ForeignKey here, since otherwise a related_name='votes' makes not much sense.

Upvotes: 1

Related Questions