Django Sites Framework: Initial Data Migration Location
Asked Answered
C

3

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.

Consolation answered 30/9, 2014 at 18:40 Comment(0)
I
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:

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)
    ]
Impersonalize answered 15/11, 2014 at 15:20 Comment(9)
It is perhaps a bit hackish since I'm adding this to an existing app. You could perhaps combine this with MIGRATION_MODULES.Impersonalize
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).Consolation
I agree, I'm running into similar problems with django-modeltranslation. A neater solution would be nice.Impersonalize
I've found a better way to populate the sites framework. See the edited answer.Impersonalize
getting django.db.migrations.graph.NodeNotFoundError: Migration socialaccount.0001_initial dependencies reference nonexistent parent node (u'sites', u'0001_initial')Vincentvincenta
@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', }Consolation
@ThomWiggers, I just edited and accepted your answer.Consolation
@Vincentvincenta 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', }His
This solution don't keep my PK equals to 1. It changes the domain and name, but my entry's PK = 1.Hemstitch
H
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.

Hans answered 27/6, 2017 at 6:58 Comment(1)
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.Ozell
S
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)]
Surplusage answered 19/11, 2019 at 19:58 Comment(1)
You can check the migrations of any app (including sites) by using docs.djangoproject.com/en/4.1/ref/django-admin/#showmigrationsLiverish

© 2022 - 2024 — McMap. All rights reserved.