Commit 934a16dc authored by Konrad Świat's avatar Konrad Świat Committed by Tim Graham
Browse files

Fixed #23468 -- Added checks for duplicate fixtures directories in loaddata.

If settings.FIXTURE_DIRS contains duplicates or a default fixture
directory (app_name/fixtures), ImproperlyConfigured is raised.

Thanks to Berker Peksag and Tim Graham for review.
parent e548d08f
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import zipfile
from django.apps import apps
from django.conf import settings
from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS,
@@ -238,13 +239,23 @@ class Command(BaseCommand):
        current directory.
        """
        dirs = []
        fixture_dirs = settings.FIXTURE_DIRS
        if len(fixture_dirs) != len(set(fixture_dirs)):
            raise ImproperlyConfigured("settings.FIXTURE_DIRS contains duplicates.")
        for app_config in apps.get_app_configs():
            if self.app_label and app_config.label != self.app_label:
                continue
            app_label = app_config.label
            app_dir = os.path.join(app_config.path, 'fixtures')
            if app_dir in fixture_dirs:
                raise ImproperlyConfigured(
                    "'%s' is a default fixture directory for the '%s' app "
                    "and cannot be listed in settings.FIXTURE_DIRS." % (app_dir, app_label)
                )

            if self.app_label and app_label != self.app_label:
                continue
            if os.path.isdir(app_dir):
                dirs.append(app_dir)
        dirs.extend(list(settings.FIXTURE_DIRS))
        dirs.extend(list(fixture_dirs))
        dirs.append('')
        dirs = [upath(os.path.abspath(os.path.realpath(d))) for d in dirs]
        return dirs
+4 −0
Original line number Diff line number Diff line
@@ -284,6 +284,10 @@ Management Commands
* The :djadminopt:`--name` option for :djadmin:`makemigrations` allows you to
  to give the migration(s) a custom name instead of a generated one.

* The :djadmin:`loaddata` command now prevents repeated fixture loading. If
  :setting:`FIXTURE_DIRS` contains duplicates or a default fixture directory
  path (``app_name/fixtures``), an exception is raised.

Migrations
^^^^^^^^^^

+43 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import re
import warnings

from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.base import DeserializationError
from django.core import management
from django.core.management.base import CommandError
@@ -486,6 +487,48 @@ class TestFixtures(TestCase):
            verbosity=0,
        )

    @override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1'),
                                     os.path.join(_cur_dir, 'fixtures_1')])
    def test_fixture_dirs_with_duplicates(self):
        """
        settings.FIXTURE_DIRS cannot contain duplicates in order to avoid
        repeated fixture loading.
        """
        self.assertRaisesMessage(
            ImproperlyConfigured,
            "settings.FIXTURE_DIRS contains duplicates.",
            management.call_command,
            'loaddata',
            'absolute.json',
            verbosity=0,
        )

    @override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures')])
    def test_fixture_dirs_with_default_fixture_path(self):
        """
        settings.FIXTURE_DIRS cannot contain a default fixtures directory
        for application (app/fixtures) in order to avoid repeated fixture loading.
        """
        self.assertRaisesMessage(
            ImproperlyConfigured,
            "'%s' is a default fixture directory for the '%s' app "
            "and cannot be listed in settings.FIXTURE_DIRS."
            % (os.path.join(_cur_dir, 'fixtures'), 'fixtures_regress'),
            management.call_command,
            'loaddata',
            'absolute.json',
            verbosity=0,
        )

    @override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1'),
                                     os.path.join(_cur_dir, 'fixtures_2')])
    def test_loaddata_with_valid_fixture_dirs(self):
        management.call_command(
            'loaddata',
            'absolute.json',
            verbosity=0,
        )


class NaturalKeyFixtureTests(TestCase):