When a Django project runs its database migrations, then django.contrib.sites will ensure that a default Site (pk=1) is created for the domain example.com:
This typically means that developers and deployments alike all have to modify the default site with the correct domain name.
Could it be possible that we have a setting for this, for instance to define DEFAULT_SITE_DOMAIN = "localhost:8000" for development and DEFAULT_SITE_DOMAIN = "my-actual-website.com" ?
from django.core.management.base import BaseCommand
from django.conf import settings
from django.contrib.sites.models import Site
class Command(BaseCommand):
help = 'Update the default site with the domain and name from settings.'
def handle(self, *args, **kwargs):
domain = getattr(settings, 'DEFAULT_SITE_DOMAIN', 'example.com')
name = getattr(settings, 'DEFAULT_SITE_NAME', 'example.com')
site, created = Site.objects.update_or_create(
pk=getattr(settings, 'SITE_ID', 1),
defaults={'domain': domain, 'name': name}
)
if created:
self.stdout.write(self.style.SUCCESS(f"Created site with domain {domain}"))
else:
self.stdout.write(self.style.SUCCESS(f"Updated site to domain {domain}"))
and in your settings,py:
DEFAULT_SITE_DOMAIN = "localhost:8000" # for development
DEFAULT_SITE_NAME = "My Development Site"
@anefta this is a nice recipe (thank you for sharing that idea), and the problem is definitely solvable with conventional methods. I’ve normally just used the Django Admin or a shell.
However since you posed this challenge: I think the fact that “example.com” is hard-coded in the current implementation really invites for a new setting. If that setting exists, we can easily remove this typical “developer onboarding” or “project bootstrapping” papercut and avoid having this problem re-solved in so many Django projects (whether you solve it through the shell, the Django Admin, a custom management command or database seeds).
Fixing the issue in django.contrib.sites means changing 1 line of code and documenting a new setting. But fixing it outside of django.contrib.sites not only requires more code per project, but also duplicating that effort many times.
I’ve solved this problem with a custom post_migrate handler to create the default site, doing a create-or-update on this default site. The advantage of this approach has been extra customizability - multiple sites when necessary, or switching the domain based on environment.
We’re pretty resistant to new settings in Django because they bloat your settings file. Not everything needs configuring there. Maybe documenting the custom post_migrate handler approach is better.
When a user starts a brand new project, they will have this default:
SITE_ID=1
They’ll then run migrate and have example.com. Later, they’ll figure out that example.com does not work for various reasons and for convenience, they want localhost:8000 for development and staging.icecream.com for the staging environment etc.
This won’t work for any existing databases, as it this will not overwrite existing rows but is only used when pk=1 is created. The reason we want to do this is so that repetitive bootstrapping of environments become smoother.
But on a whole other level, we have just added a convention for a canonical domain in the settings via SITE_DEFAULT_DOMAIN. Thus, we can now start using the project’s domain without hitting the database. That can also be very handy IMHO, I think 99% of Django projects have a canonical domain that they refer to in for instance emails and when generating content outside of the request-response loop (async jobs, management commands etc).
It’s more expressive since environment-based logic usually lives in the settings of a project. And indeed, it uses a post_migrate handler since that’s already what django.contrib.sites does
Thus, aside from having defaults for multiple sites, I think that your use-case is covered by implementing this 1 additional setting @adamchainz ? (edit: I re-read and understood that you want to be able to update the site domain even after its creation, which is interesting. It means the single-source-of-truth is our settings/code and not actually in the database).