gilhad
gilhad

Reputation: 609

get any instance (if exist) or create new for any child of abstract model

I found myself repeatedly finding record matching some criteria and if none exists, then creating new one with those criteria matched. Basically that does get_or_create() method, but it fails, if more than one such record exists.

I am doing this so often, that I do not want write the same similar code all over and instead use some simple function for that

All my models are derived from one abstract BaseModel, so it would seem logical, to implement method get_first_or_create there, which would then return object of actual model fullfilling given criteria. In case of more such objects, it would return any one of them. It would also take care about all Exception checking and some other common stuff too in one place.

I do not know, how to make it in the BaseModel. Something like

class BaseModel(models.model):
  active = models.BooleanField(default=True)
  ...
  class Meta:
    abstract = True
  def get_first_or_create(*args,**kwargs):
    retval=ChildModel.objects.filter(*args,**kwargs).first()
    # ChildModel is ofcourse wrong, but what to use?
    if retval:
       return retval
    else:
       retval = ChildModel(*args,**kwargs)
       retval.save()
       return retval
    # actually make more Exceptions/sanity/whatever checks here
    # but this is core of the idea

class Car(BaseModel):
   name = models.TextField(blank=True)
   color = models.TextField(blank=True)
   ...
class Animal(BaseModel):
   cute=models.BooleanField(default=True)
   ...

and then call something like this:

# lets get some blue car and cute pet, I do not care which one,
# just ANY one would be good
some_blue_car=Car.get_first_or_create(color="blue")
some_cute_pet=Animal.get_first_or_create(cute=True)

Is it possible to do it this way, or I am totally wrong and what other approach I should get?


Solution

This works for me :)

class MyManager(models.Manager):  # Stolen from get_or_create() and modified
    def get_first_or_create(self, defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, creating one if necessary.
        Return a tuple of (object, created), where created is a boolean
        specifying whether an object was created.
        If more objects are found, returns first() of them
        """
        query=self.get_queryset()
        lookup, params = query._extract_model_params(defaults, **kwargs)
        query._for_write = True
        try:
            clone = query.filter(**lookup)
            num = len(clone)
            if num >= 1:
                return (clone._result_cache[0], False)
            else:
                return query._create_object_from_params(lookup, params)
        except self.model.DoesNotExist:
            return query._create_object_from_params(lookup, params)

class myModel(models.Model):
    # --- common data
    active = models.BooleanField(default=True)
    ....
    objects = MyManager()

Upvotes: 0

Views: 95

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48982

The idea is fine, but the right place to implement this is in a custom Manager.

from django.db import models

class MyManager(models.Manager):
    def get_first_or_create(self, *args, **kwargs):
        retval = self.get_queryset().filter(*args, **kwargs).first()
        ...

class Car(BaseModel):
    ...
    objects = MyManager()

class Animal(BaseManager):
    ...
    objects = MyManager()

Note that the method doesn't need to know the name of the model it's being used with, it can just use self.get_queryset().

Upvotes: 1

Related Questions