18

Before Django 1.7, when using the Django Sites Framework one could/should define the initial data using Initial Fixtures.

myproject/fixtures/initial_data.json

[
{
    "pk": 1, 
    "model": "sites.site", 
    "fields": {
        "domain": "domain1", 
        "name": "name1"
    }
},
{
    "pk": 2, 
    "model": "sites.site", 
    "fields": {
        "domain": "domain2", 
        "name": "name2"
    }
},
{
    "pk": 3, 
    "model": "sites.site", 
    "fields": {
        "domain": "domain3", 
        "name": "name3"
    }
}
]

Since it is a global project setting, I added a "fixtures" folder to the project root, and added it to FIXTURE_DIRS.

# Used to search fixture files directories.
# Fixture files are files that provide initial data to be
# inserted in the database. (>python manage.py loaddata)

    FIXTURE_DIRS = [
        os.path.join(PROJECT_ROOT, "fixtures"),
    ]

Now, I'm using Django 1.7, and it is recommended to use migrations. Quoting Django documentation:

To set the correct name and domain for your project, you can use a data migration.

The problem is Migrations are app-specific:

python manage.py makemigrations --empty yourappname

So, what is the recommended approach to add the Site information to my project, using a data migration? Where should this migration live?

Running python manage.py makemigrations --empty sites creates the migration in the third party app folder, so we don't want that.

Shouldn't be possible to define a MIGRATION_DIRS as FIXTURE_DIRS existed for the initial_data?

I found MIGRATION_MODULES in settings documentation, but the problem still remains, it is app-specific.

JCJS
  • 3,031
  • 3
  • 19
  • 25

3 Answers3

18

First, configure MODULE_MIGRATIONS in your django settings:

MIGRATION_MODULES = {
    'sites': 'myproject.fixtures.sites_migrations',
}

Then, run ./manage.py makemigrations sites to have django create the directory and create 0001_intitial.py in the myproject.fixtures.sites_migrations package.

Then, do ./manage.py makemigrations --empty sites. The migration file should be created in the specified package.

My file 0002_initialize_sites.py looks like this:

-*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations


def insert_sites(apps, schema_editor):
    """Populate the sites model"""
    Site = apps.get_model('sites', 'Site')
    Site.objects.all().delete()

    # Register SITE_ID = 1
    Site.objects.create(domain='create.tourtodo.com', name='create')
    # Register SITE_ID = 2
    Site.objects.create(domain='www.tourtodo.com', name='www')


class Migration(migrations.Migration):

    dependencies = [
        ('sites', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(insert_sites)
    ]
Thom Wiggers
  • 6,938
  • 1
  • 39
  • 65
  • It is perhaps a bit hackish since I'm adding this to an existing app. You could perhaps combine this with `MIGRATION_MODULES`. – Thom Wiggers Nov 15 '14 at 15:21
  • 2
    The thing is, you also had to choose one of your apps to receive the data migration to insert the list of Sites. Truth be told, this is more a conceptual problem, since from my perspective, one should be able to provide Sites initial data configuration from a global project environment, and not from one of the user defined apps (as it was possible in Django<1.7). – JCJS Nov 18 '14 at 11:25
  • I agree, I'm running into similar problems with `django-modeltranslation`. A neater solution would be nice. – Thom Wiggers Nov 18 '14 at 12:33
  • I've found a better way to populate the sites framework. See the edited answer. – Thom Wiggers Nov 28 '14 at 14:06
  • 1
    getting django.db.migrations.graph.NodeNotFoundError: Migration socialaccount.0001_initial dependencies reference nonexistent parent node (u'sites', u'0001_initial') – dranxo Aug 10 '15 at 21:55
  • @ThomWiggers, I just reviewed this thread and we had the solution right in front of us. My only concern with your answer was that the 'sites' migration folder was located inside 'myapp'. Hence, the conceptual problem I was referring. Well, it's simple: just move that location to the main project module: MIGRATION_MODULES = { 'sites': 'myproject.fixtures.sites_migrations', } – JCJS Aug 13 '15 at 15:34
  • @ThomWiggers, I just edited and accepted your answer. – JCJS Aug 13 '15 at 16:54
  • 1
    @dranxo you'll also have to add the socialaccount app and any other app that has the Sites app dependency in its migration. MIGRATION_MODULES = { 'sites': 'myproject.fixtures.sites_migrations', 'socialaccount': 'myproject.fixtures.socialaccount_migrations', } – harristrader Mar 14 '16 at 22:24
  • This solution don't keep my PK equals to 1. It changes the `domain` and `name`, but my entry's PK = 1. – FTM Nov 22 '18 at 21:44
6

You just need to reference the highest-numbered sites migration as a dependency.

def forward(apps, schema_editor):
    Site = apps.get_model("sites", "Site")
    db_alias = schema_editor.connection.alias
    s, created = Site.objects.using(db_alias).get_or_create(pk=1)
    s.name = APP_NAME
    s.domain = APP_NAME
    s.save()


def reverse(apps, schema_editor):
    Site = apps.get_model("sites", "Site")
    db_alias = schema_editor.connection.alias
    s = Site.objects.using(db_alias).get(pk=1)
    s.name = ORIG_APP_NAME
    s.domain = ORIG_APP_NAME
    s.save()


class Migration(migrations.Migration):

    dependencies = [

        # `core` is the app containing this migration
        ('core', '0001_initial'),

        # `0002_alter_domain_unique` is the highest-numbered migration for
        # the sites framework
        ('sites', '0002_alter_domain_unique'),

    ]

    operations = [
        migrations.RunPython(forward, reverse)
    ]

This was tested on Django 1.11.2.

Fwiw, the MODULE_MIGRATIONS solution above does not work for me.

Jason McVetta
  • 1,383
  • 11
  • 11
  • You can improve this somewhat for a single site by setting `pk = settings.SITE_ID or 1`, similarly `s.name=settings.SITE_NAME or "Example"` and `s.domain = settings.SITE_URL or "example.com"` then specify `SITE_NAME` and `SITE_URL` accordingly. One can freely select `SITE_ID` it seems. I can't find a reference in the docs to a variable in settings that is explicitly for storing the domain name, perhaps the first value in 'ALLOWED_HOSTS' ? The reverse function then should necessarily remove the site. – Carel Jan 22 '19 at 07:38
1

A couple of tweaks to @Jason's answer. Last tested with Django 2.2.

from django.conf import settings
from django.db import migrations


def update_site_domain(apps, schema_editor):
    Site = apps.get_model("sites", "Site")
    s, _ = Site.objects.get_or_create(pk=settings.SITE_ID)
    s.domain = settings.HOST
    s.save()


class Migration(migrations.Migration):

    dependencies = [
        ("your_app", "last_migration_name"),
        ("sites", "0002_alter_domain_unique"),  # highest-numbered migration for the sites framework
    ]

    operations = [migrations.RunPython(update_site_domain)]
karuhanga
  • 3,010
  • 1
  • 27
  • 30
  • You can check the migrations of any app (including `sites`) by using https://docs.djangoproject.com/en/4.1/ref/django-admin/#showmigrations – pongi Nov 21 '22 at 12:04