Reputation: 609
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?
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
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