Reputation: 2127
I have an object that need to be instantiated ONLY ONCE. Tried using redis for caching the instance failed with error cache.set("some_key", singles, timeout=60*60*24*30)
but got serialization error, due the other thread operations:
TypeError: can't pickle _thread.lock objects
But, I can comfortably cache others instances as need.
Thus I am looking for a way to create a Singleton object, I also tried:
class SingletonModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
# self.pk = 1
super(SingletonModel, self).save(*args, **kwargs)
# if self.can_cache:
# self.set_cache()
def delete(self, *args, **kwargs):
pass
class Singleton(SingletonModel):
singles = []
@classmethod
def setSingles(cls, singles):
cls.singles = singles
@classmethod
def loadSingles(cls):
sins = cls.singles
log.warning("*****Found: {} singles".format(len(sins)))
if len(sins) == 0:
sins = cls.doSomeLongOperation()
cls.setSingles(sins)
return sins
In the view.py I call on Singleton.loadSingles()
but I notice that I get
Found: 0 singles
after 2-3 requests. Please what is the best way to create Singleton on Djnago without using third party library that might try serialising and persisting the object (which is NOT possible in my case)
Upvotes: 8
Views: 19531
Reputation: 2813
I didn't see this idea posted directly, so I figured I'd post a different option that allows you to utilize django's get_or_create()
pretty easily for this purpose.
You can simply assign the Model's pk to 1 each time a new item is saved, which overrides the previous one. No need to make anything complex, it is normal logic.
Here is an example I use for a ReadOnly model.
class ReadOnlyStatus(models.Model):
id = models.AutoField(primary_key=True)
is_read_only = models.BooleanField(default=False)
def __str__(self):
return f'read only status = {self.is_read_only}'
def save(self, *args, **kwargs):
# SINGLETON...
if self.pk is not None:
# saving same object be normal.
# someone could save a pk=2 validly here but it is caught in next iteration and changed
super().save(*args, **kwargs)
else:
# new entry, so let's overwrite the old one
self.pk = 1
# the pk is set for override so let's save it now
# this will still save new attrs
self.save()
Now you can gather or create the object simply like so as a main call.
status, created = ReadOnlyStatus.objects.get_or_create(pk=1)
And if you create a secondary one randomly, for whatever reason, it will simply override the previous entry.
It's kind of a shortcut, like saving a new object with a pk of None
will duplicate it, assignment of the pk/id overrides the previous object
Upvotes: 0
Reputation: 409
The code below simply prevents the creation of a new instance of the Revenue model if one exists. I believe this should point you in the right direction.
Best of luck !!!
class RevenueWallet(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class Meta:
verbose_name = "Revenue"
def save(self, *args, **kwargs):
"""
:param args:
:param kwargs:
:return:
"""
# Checking if pk exists so that updates can be saved
if not RevenueWallet.objects.filter(pk=self.pk).exists() and RevenueWallet.objects.exists():
raise ValidationError('There can be only one instance of this model')
return super(RevenueWallet, self).save(*args, **kwargs)
Upvotes: 1
Reputation: 61
I found it easier to use a unique index to accomplish this
class SingletonModel(models.Model):
_singleton = models.BooleanField(default=True, editable=False, unique=True)
class Meta:
abstract = True
Upvotes: 6
Reputation: 389
This is my Singleton Abstract Model.
class SingletonModel(models.Model):
"""Singleton Django Model"""
class Meta:
abstract = True
def save(self, *args, **kwargs):
"""
Save object to the database. Removes all other entries if there
are any.
"""
self.__class__.objects.exclude(id=self.id).delete()
super(SingletonModel, self).save(*args, **kwargs)
@classmethod
def load(cls):
"""
Load object from the database. Failing that, create a new empty
(default) instance of the object and return it (without saving it
to the database).
"""
try:
return cls.objects.get()
except cls.DoesNotExist:
return cls()
Upvotes: 5