I borrowed ideas from this answer to extend the default Site object created during the initial migration with Django sites framework. The new model, SiteSettings, establishes a OneToOne relationship with the Site model to add additional fields. I then use signals to create the SiteSettings object.
When I made the first migration for the SiteSettings model everything appeared to work fine. A SiteSettings object was created that had a OneToOne relationship with the default Site object.
However, what I didn't notice is that a migration file wasn't created under my local app for this. I was able to makemigrations and migrate just fine, so I'm not sure where that migration went. It's not listed in my migrations table.
Anyway, since it worked I didn't notice. I then proceeded to add additional fields to SiteSettings a day or two later, and noticed when I made those migrations, they were for creating a SiteSettings model, not updating its fields. That's when I noticed that the first migration wasn't created in the right spot. The second migration was created, however it was created in site-packages/django/contrib/sites/migrations/
. It looks like this:
class Migration(migrations.Migration):
dependencies = [
("sites", "0002_alter_domain_unique"), # the initial auto migration
]
operations = [
migrations.CreateModel(
name="SiteSettings",
fields=[
(
"site",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="settings",
serialize=False,
to="sites.site",
verbose_name="site",
),
),
# A bunch of other CharFields that aren't important
],
options={
"verbose_name_plural": "settings",
},
),
]
And my models.py looks like this. I'm assuming the issue may be with the app_label
matching the name of django.contrib.sites
, but I'm not sure. The reason I named the label that was so it shows up under sites in the admin.
class SiteSettings(models.Model):
"""
Extension of the Sites model that holds more info about the site.
"""
site = models.OneToOneField(
Site,
on_delete=models.CASCADE,
primary_key=True,
related_name="settings",
verbose_name="site",
)
# A bunch of other fields that aren't important
def __str__(self):
return self.site.name
class Meta:
app_label = "sites"
verbose_name_plural = "settings"
Here's what the apps.py looks like:
from django.apps import AppConfig
from django.conf import settings
from django.db.models.signals import post_migrate, post_save
def create_default_site_settings(sender, **kwargs):
"""Creates default site settings after migration"""
# App config must be ready for import to work
from django.contrib.sites.models import Site
from .models import SiteSettings
site = Site.objects.get(id=getattr(settings, "SITE_ID", 1))
if not SiteSettings.objects.exists():
SiteSettings.objects.create(site=site)
class CoreConfig(AppConfig):
name = "apps.core"
label = "core"
default_auto_field = "django.db.models.BigAutoField"
def ready(self):
# App config must be ready for import to work
from django.contrib.sites.models import Site
post_migrate.connect(create_default_site_settings, sender=self)
from .signals import create_site_settings
post_save.connect(create_site_settings, sender=Site)
And lastly, signals.py.
from django.contrib.sites.models import Site
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import SiteSettings
@receiver(post_save, sender=Site)
def create_site_settings(sender, instance, **kwargs):
"""
Creates/updates a SiteSettings object after a Site object.
"""
site_settings, created = SiteSettings.objects.update_or_create(site=instance)
if not created:
site_settings.save()
Is this simply an issue with the app_label
being the same? I'm trying to wrap my head around why that is if so.
I figured out the issue. Since I was running my app in a Docker container and had the app_label
in my new model set to the same app_label
as django.contrib.sites, the migrations were being created in path/to/site-packages/django/contrib/sites/migrations/
in the Docker container. Not in the app copied to the container, but the actual Django install inside the container.
So that initial migration was created in the container and not locally which I didn't notice at first. Once my container was rebuilt at some point, the migration file was wiped since it freshly reinstalls all Python packages onto the new image. When I went to make the second migration, it was like the model was never created despite the table existing in the db.
To fix it, I reverted the app_label
of the new model back to its default. Then I had to I had to rebuild the container, delete the old table site_sitesettings and remove the "lost" migration record in django_migrations table. During the next round of migrations a new table was created for core_sitesettings and I could access my OneToOne relationships as expected.