Django 1.8 custom manager and .create() method is not used by .update_or_create()
Asked Answered
R

3

8

I have two related instances that should always be created together. I'd like to do this without using signals or override the model's save() method.

class Car(models.Mode):
    make = models.CharField(max_length=32)
    model = models.CharField(max_length=32)

    class Meta:
        unique_together = ('make', 'model',)

    objects = CarManager()

class CarProfile(models.Model):
    car = models.OneToOneField(Car)
    last_checkup = models.DateTimeField(blank=True, null=True)        

I have created a custom CarManager that overrides models.Manager.create() to ensure the creation of CarProfile when a Car is created:

class CarManager(models.Manager):
    def create(self, **kwargs):
        with transaction.atomic():
            car = self.model(**kwargs)
            car.save(force_insert=True)
            CarProfile.objects.create(car=car)
        return car

When I call Car.objects.create(make='Audi', model='R8'), a new Car instance and its corresponding CarProfile is created as expected. However when I try creating a new Car using Car.objects.update_or_create(make='Audi', model='R8') or Car.objects.get_or_create(make='Audi', model='R8'), in both cases a Car instance is created but no corresponding CarProfile is created.

Why doesn't update_or_create and get_or_create produce the expected CarProfile instance when I've specified that behaviour in the custom create() method?

It seems that both these methods are calling the create() from QuerySet class instead of my custom one.

Roughneck answered 29/10, 2015 at 5:46 Comment(0)
S
4

Django's Manager class is actually a QuerySet that has been reconstituted into a Manager. What if you implement create on a QuerySet and then build a Manager from that?

class CarQuerySet(models.QuerySet):

    def create(self, **kwargs):
        with transaction.atomic():
            car = self.model(**kwargs)
            car.save(force_insert=True)
            CarProfile.objects.create(car=car)
        return car

Then the Car is:

class Car(models.Mode):
    make = models.CharField(max_length=32)
    model = models.CharField(max_length=32)

    class Meta:
        unique_together = ('make', 'model',)

    objects = CarQuerySet.as_manager()
Socage answered 29/10, 2015 at 6:8 Comment(2)
I've noticed how a Manager is really inherited from BaseManager.from_queryset(QuerySet). I may be wrong, but it seems from_queryset and _get_queryset_methods create an ad-hoc Class for Manager to subclass which copies over any methods in QuerySet not defined in BaseManager. My CarManager class then subclasses that ad-hoc class so I'm confused as to why create is not overriding the one in QuerySet only for update_and_create and get_or_create.Roughneck
Also, @Nathan Villaescusa, thank you for your response. I did end up creating a custom CarQuerySet and return an instance of it in get_queryset of CarManager (I've got other manager methods in there). update_and_create and get_or_create now have the correct behaviour. But I'm still wondering why my previous implementation did not work.Roughneck
W
2

The actual reason that overriding create is not working in this instance is because you need to override both the update_and_create and the get_or_create methods as well as create to affect their behaviour.

Weighin answered 4/5, 2017 at 3:9 Comment(1)
Why not overwrite save as it seems to be what they all have in common.Planer
Y
1

Just want to clarify that this issue was addressed in Django v1.8a1. So now both get_or_create and update_or_create will call the managers create method as expected.

https://github.com/django/django/commit/0dce44e16be2d4850120cd333380ab9171c5964a

You answered 27/10, 2020 at 1:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.