Monday, 8 August 2022

Django standalone apps with one app depending on another app - django.db.migrations.exceptions.NodeNotFoundError

I am using Django 4.0

I have written two standalone apps. Foo and FooBar.

Application Foo uses package django_nyt. Application FooBar uses functionality from application Foo.

I am able to makemigrations and migrate models in application Foo, however, when working with application FooBar, when I attempt to makemigrations, I get the following error trace:

Traceback (most recent call last):
  File "manage.py", line 14, in <module>
    execute_from_command_line(sys.argv)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/__init__.py", line 446, in execute_from_command_line
    utility.execute()
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/__init__.py", line 440, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/base.py", line 414, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/base.py", line 98, in wrapped
    res = handle_func(*args, **kwargs)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/core/management/commands/makemigrations.py", line 100, in handle
    loader = MigrationLoader(None, ignore_no_migrations=True)
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/db/migrations/loader.py", line 58, in __init__
    self.build_graph()
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/db/migrations/loader.py", line 276, in build_graph
    self.graph.validate_consistency()
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/db/migrations/graph.py", line 198, in validate_consistency
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/db/migrations/graph.py", line 198, in <listcomp>
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/path/to/project/foobar/env/lib/python3.8/site-packages/django/db/migrations/graph.py", line 60, in raise_error
    raise NodeNotFoundError(self.error_message, self.key, origin=self.origin)
django.db.migrations.exceptions.NodeNotFoundError: Migration foo.0001_initial dependencies reference nonexistent parent node ('django_nyt', '0010_auto_20220802_2211')

contents of /path/to/proj/foo/env/lib/python3.8/site-packages/django_nyt/migrations/0010_auto_20220802_2211

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('django_nyt', '0009_alter_notification_subscription_and_more'),
    ]

    operations = [
        migrations.AlterField(
            model_name='notification',
            name='id',
            field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
        ),
        migrations.AlterField(
            model_name='settings',
            name='id',
            field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
        ),
        migrations.AlterField(
            model_name='subscription',
            name='id',
            field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
        ),
    ]

Since these are standalone apps, I am using the design pattern advocated for in Django Standalone Reusable Libraries.

common.py (foo application)

from pathlib import Path    
from django.conf import settings
    
SAMPLE_PROJECT_DIR_NAME='sample_project'    
BASE_DIR = f"{Path(__file__).parent.resolve()}/{SAMPLE_PROJECT_DIR_NAME}"        
STANDALONE_APP_NAME='foo'

STATIC_URL = '/static/'

INSTALLED_APPS = [
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.admin',
    'django.contrib.messages',
    'django.contrib.humanize',
    'django.contrib.sites',
    
    'django_nyt.apps.DjangoNytConfig',

    STANDALONE_APP_NAME,
    'myapp',
]


SECRET_KEY="lskfjklsdfjalkfslfjslfksdjfslkfslkfj"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEBUG=True


# AUTH_USER_MODEL='testdata.CustomUser',
DATABASES={
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': f"{BASE_DIR}/db.sqlite3",
    },
}

SITE_ID=1
ROOT_URLCONF=f"{SAMPLE_PROJECT_DIR_NAME}.urls"

TEMPLATES=[
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.messages.context_processors.messages',
            ]
        },
    },
]

USE_TZ=True

MIDDLEWARE=[
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]


settings.configure(
    SECRET_KEY=SECRET_KEY,
    DEFAULT_AUTO_FIELD = DEFAULT_AUTO_FIELD,  
    DEBUG=DEBUG,
    # AUTH_USER_MODEL=AUTH_USER_MODEL,
    DATABASES=DATABASES,

    SITE_ID=SITE_ID,
    ROOT_URLCONF=ROOT_URLCONF,
    INSTALLED_APPS=INSTALLED_APPS,

    STATIC_URL=STATIC_URL,

    TEMPLATES=TEMPLATES,
    USE_TZ=USE_TZ,
    MIDDLEWARE=MIDDLEWARE,
)

common.py (foobar)

import sys
from pathlib import Path
from django.conf import settings

SAMPLE_PROJECT_DIR_NAME='sample_project'
PARENT_DIR = f"{Path(__file__).parent.parent.resolve()}"
BASE_DIR = f"{Path(__file__).parent.resolve()}/{SAMPLE_PROJECT_DIR_NAME}"

STANDALONE_APP_NAME='foobar'
STATIC_URL = '/static/'

sys.path.insert(0, f"{PARENT_DIR}/django-foo")


INSTALLED_APPS = [
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.admin',
    'django.contrib.messages',
    'django.contrib.humanize',
    'django.contrib.sites',

    'django_nyt.apps.DjangoNytConfig',
    'notifications',
    STANDALONE_APP_NAME,
    'myapp',
]


SECRET_KEY="lskfjklsdfjalkfslfjslfksdjfslkfslkfj"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEBUG=True

# AUTH_USER_MODEL='testdata.CustomUser',
DATABASES={
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': f"{BASE_DIR}/db.sqlite3",
    },
}

SITE_ID=1
ROOT_URLCONF=f"{SAMPLE_PROJECT_DIR_NAME}.urls"

TEMPLATES=[
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.messages.context_processors.messages',
            ]
        },
    },
]

USE_TZ=True

MIDDLEWARE=[
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]

FOOBAR_DATA={}

settings.configure(
    SECRET_KEY=SECRET_KEY,
    DEFAULT_AUTO_FIELD = DEFAULT_AUTO_FIELD,  
    DEBUG=DEBUG,
    # AUTH_USER_MODEL=AUTH_USER_MODEL,
    DATABASES=DATABASES,

    SITE_ID=SITE_ID,
    ROOT_URLCONF=ROOT_URLCONF,
    INSTALLED_APPS=INSTALLED_APPS,

    STATIC_URL=STATIC_URL,

    TEMPLATES=TEMPLATES,
    USE_TZ=USE_TZ,
    MIDDLEWARE=MIDDLEWARE,

    FOOBAR_DATA = FOOBAR_DATA,
)

manage.py (same for both foo and foobar standalone applications)

import os
import sys

import django

from common import *

sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), SAMPLE_PROJECT_DIR_NAME))

django.setup()

if __name__ == '__main__':
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

I have looked in the migrations folders for both applications and this is what I found:

  • foo app has the migration 0010_auto_20220802_2211 generated, and applied to django_nyt, to change the PK auto fields to use BigInteger.
  • foobar app does not have the migration file 0010_auto_20220802_2211 generated for django_nyt (hence the migration error). I can't for the life of me, work out why foobar is not using the migrations generated by foo, since it has a dependency on foo.

A no-brainer hack would be to simply copy over the missing migration file, but that is too fragile, and I want to actually understand what is going on, and why foobar is not using/applying the migrations created by a dependency.

This is what I have tried so far (did not resolve the issue):

  1. I checked in the migration files for foo. Initially, I had kept them out of the repository
  2. Created a dummy model class in foobar, that explicitly had a dependency on a model in dyango_nyt - the reasoning being that, if a django_nyt model existed in foobar, i would force migrations to be created for django_nyt.

This is what my dummy class in foobar/models.py looked like:

from django_nyt.models import Notification

class FooBarMigrationHack(Notification):
    name = models.CharField(max_length=10)

None of the above has worked, and I'm still getting the same error stack trace as shown above, in my question.

How do I resolve this without having to manually copy the 'missing' migration file from foo/migrations/ to foobar/migrations/ ?



from Django standalone apps with one app depending on another app - django.db.migrations.exceptions.NodeNotFoundError

No comments:

Post a Comment