lazybird/django-solo

Django migrations don't work if you use the model in code without making migrations first

smark-1 opened this issue · 6 comments

if I take my existing model

class SiteConfiguration(SingletonModel):
     site_name = models.CharField(max_length=255, default='Administration')

then add to it a additional field

class SiteConfiguration(SingletonModel):
     site_name = models.CharField(max_length=255, default='Administration')
     message = models.TextField(default="this is a standard message")

then inside of django views.py

...
site_config = SiteConfiguration.get_solo()
def view(request):
      site_config.message
     return HttpResponse("some response")
...

then try to run a migration i get a error
django.db.utils.OperationalError: no such column: siteconfiguration.message

this is a very big problem for me because I often write my code first to see how it looks first before making the migrations. Also this does work in a standard model so it should work in a SingletonModel

@dragoncommits I might be late to the party, but the problem is how you use it.

You are using SiteConfiguration.get_solo() (which does a DB/ORM call) in the base of file/module views.py and when Django is getting initialised, it will do that call but will fail because it's not yet ready.
This is a general problem with code that gets run at import time (an example: https://stackoverflow.com/questions/43326132/how-to-avoid-import-time-database-access-in-django).

What I would suggest to do is create a function with lru_cache that returns the configuration:

from functools import lru_cache

@lru_cache(maxsize=1)
def get_config():
    return SiteConfiguration.get_solo()
...
def view(request):
    site_config = get_config() 
    site_config.message
    return HttpResponse("some response")
...

Thank you @foobarna , I will have to try this and report back.

Hi,

Just a little improvement:

class SiteConfiguration(SingletonModel):
    site_name = models.CharField(max_length=255, default='Site Name')
    maintenance_mode = models.BooleanField(default=False)

    def __str__(self):
        return "Site configuration"

    @classmethod
    @lru_cache(maxsize=1)
    def get_solo(cls):
        return super().get_solo()

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.get_solo.cache_clear()

    class Meta:
        verbose_name = "Site configuration"

Like this, it is completely transparent.