Paullo
Paullo

Reputation: 2127

How to implement Singleton in Django

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

Answers (4)

ViaTech
ViaTech

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

Durodola Opemipo
Durodola Opemipo

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

jpnauta
jpnauta

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

Ramkishore M
Ramkishore M

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

Related Questions