Best Python code snippet using localstack_python
test_autodetector.py
Source:test_autodetector.py  
...451    ], {452        "unique_together": {("parent", "knight")},453        "indexes": [models.Index(fields=["parent", "knight"], name='rabbit_circular_fk_index')],454    })455    def repr_changes(self, changes, include_dependencies=False):456        output = ""457        for app_label, migrations_ in sorted(changes.items()):458            output += "  %s:\n" % app_label459            for migration in migrations_:460                output += "    %s\n" % migration.name461                for operation in migration.operations:462                    output += "      %s\n" % operation463                if include_dependencies:464                    output += "      Dependencies:\n"465                    if migration.dependencies:466                        for dep in migration.dependencies:467                            output += "        %s\n" % (dep,)468                    else:469                        output += "        None\n"470        return output471    def assertNumberMigrations(self, changes, app_label, number):472        if len(changes.get(app_label, [])) != number:473            self.fail("Incorrect number of migrations (%s) for %s (expected %s)\n%s" % (474                len(changes.get(app_label, [])),475                app_label,476                number,477                self.repr_changes(changes),478            ))479    def assertMigrationDependencies(self, changes, app_label, position, dependencies):480        if not changes.get(app_label):481            self.fail("No migrations found for %s\n%s" % (app_label, self.repr_changes(changes)))482        if len(changes[app_label]) < position + 1:483            self.fail("No migration at index %s for %s\n%s" % (position, app_label, self.repr_changes(changes)))484        migration = changes[app_label][position]485        if set(migration.dependencies) != set(dependencies):486            self.fail("Migration dependencies mismatch for %s.%s (expected %s):\n%s" % (487                app_label,488                migration.name,489                dependencies,490                self.repr_changes(changes, include_dependencies=True),491            ))492    def assertOperationTypes(self, changes, app_label, position, types):493        if not changes.get(app_label):494            self.fail("No migrations found for %s\n%s" % (app_label, self.repr_changes(changes)))495        if len(changes[app_label]) < position + 1:496            self.fail("No migration at index %s for %s\n%s" % (position, app_label, self.repr_changes(changes)))497        migration = changes[app_label][position]498        real_types = [operation.__class__.__name__ for operation in migration.operations]499        if types != real_types:500            self.fail("Operation type mismatch for %s.%s (expected %s):\n%s" % (501                app_label,502                migration.name,503                types,504                self.repr_changes(changes),505            ))506    def assertOperationAttributes(self, changes, app_label, position, operation_position, **attrs):507        if not changes.get(app_label):508            self.fail("No migrations found for %s\n%s" % (app_label, self.repr_changes(changes)))509        if len(changes[app_label]) < position + 1:510            self.fail("No migration at index %s for %s\n%s" % (position, app_label, self.repr_changes(changes)))511        migration = changes[app_label][position]512        if len(changes[app_label]) < position + 1:513            self.fail("No operation at index %s for %s.%s\n%s" % (514                operation_position,515                app_label,516                migration.name,517                self.repr_changes(changes),518            ))519        operation = migration.operations[operation_position]520        for attr, value in attrs.items():521            if getattr(operation, attr, None) != value:522                self.fail("Attribute mismatch for %s.%s op #%s, %s (expected %r, got %r):\n%s" % (523                    app_label,524                    migration.name,525                    operation_position,526                    attr,527                    value,528                    getattr(operation, attr, None),529                    self.repr_changes(changes),530                ))531    def assertOperationFieldAttributes(self, changes, app_label, position, operation_position, **attrs):532        if not changes.get(app_label):533            self.fail("No migrations found for %s\n%s" % (app_label, self.repr_changes(changes)))534        if len(changes[app_label]) < position + 1:535            self.fail("No migration at index %s for %s\n%s" % (position, app_label, self.repr_changes(changes)))536        migration = changes[app_label][position]537        if len(changes[app_label]) < position + 1:538            self.fail("No operation at index %s for %s.%s\n%s" % (539                operation_position,540                app_label,541                migration.name,542                self.repr_changes(changes),543            ))544        operation = migration.operations[operation_position]545        if not hasattr(operation, 'field'):546            self.fail("No field attribute for %s.%s op #%s." % (547                app_label,548                migration.name,549                operation_position,550            ))551        field = operation.field552        for attr, value in attrs.items():553            if getattr(field, attr, None) != value:554                self.fail("Field attribute mismatch for %s.%s op #%s, field.%s (expected %r, got %r):\n%s" % (555                    app_label,556                    migration.name,557                    operation_position,558                    attr,559                    value,560                    getattr(field, attr, None),561                    self.repr_changes(changes),562                ))563    def make_project_state(self, model_states):564        "Shortcut to make ProjectStates from lists of predefined models"565        project_state = ProjectState()566        for model_state in model_states:567            project_state.add_model(model_state.clone())568        return project_state569    def get_changes(self, before_states, after_states, questioner=None):570        return MigrationAutodetector(571            self.make_project_state(before_states),572            self.make_project_state(after_states),573            questioner,574        )._detect_changes()575    def test_arrange_for_graph(self):576        """Tests auto-naming of migrations for graph matching."""577        # Make a fake graph578        graph = MigrationGraph()579        graph.add_node(("testapp", "0001_initial"), None)580        graph.add_node(("testapp", "0002_foobar"), None)581        graph.add_node(("otherapp", "0001_initial"), None)582        graph.add_dependency("testapp.0002_foobar", ("testapp", "0002_foobar"), ("testapp", "0001_initial"))583        graph.add_dependency("testapp.0002_foobar", ("testapp", "0002_foobar"), ("otherapp", "0001_initial"))584        # Use project state to make a new migration change set585        before = self.make_project_state([])586        after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable])587        autodetector = MigrationAutodetector(before, after)588        changes = autodetector._detect_changes()589        # Run through arrange_for_graph590        changes = autodetector.arrange_for_graph(changes, graph)591        # Make sure there's a new name, deps match, etc.592        self.assertEqual(changes["testapp"][0].name, "0003_author")593        self.assertEqual(changes["testapp"][0].dependencies, [("testapp", "0002_foobar")])594        self.assertEqual(changes["otherapp"][0].name, "0002_pony_stable")595        self.assertEqual(changes["otherapp"][0].dependencies, [("otherapp", "0001_initial")])596    def test_arrange_for_graph_with_multiple_initial(self):597        # Make a fake graph.598        graph = MigrationGraph()599        # Use project state to make a new migration change set.600        before = self.make_project_state([])601        after = self.make_project_state([self.author_with_book, self.book, self.attribution])602        autodetector = MigrationAutodetector(before, after, MigrationQuestioner({'ask_initial': True}))603        changes = autodetector._detect_changes()604        changes = autodetector.arrange_for_graph(changes, graph)605        self.assertEqual(changes['otherapp'][0].name, '0001_initial')606        self.assertEqual(changes['otherapp'][0].dependencies, [])607        self.assertEqual(changes['otherapp'][1].name, '0002_initial')608        self.assertCountEqual(609            changes['otherapp'][1].dependencies,610            [('testapp', '0001_initial'), ('otherapp', '0001_initial')],611        )612        self.assertEqual(changes['testapp'][0].name, '0001_initial')613        self.assertEqual(changes['testapp'][0].dependencies, [('otherapp', '0001_initial')])614    def test_trim_apps(self):615        """616        Trim does not remove dependencies but does remove unwanted apps.617        """618        # Use project state to make a new migration change set619        before = self.make_project_state([])620        after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable, self.third_thing])621        autodetector = MigrationAutodetector(before, after, MigrationQuestioner({"ask_initial": True}))622        changes = autodetector._detect_changes()623        # Run through arrange_for_graph624        graph = MigrationGraph()625        changes = autodetector.arrange_for_graph(changes, graph)626        changes["testapp"][0].dependencies.append(("otherapp", "0001_initial"))627        changes = autodetector._trim_to_apps(changes, {"testapp"})628        # Make sure there's the right set of migrations629        self.assertEqual(changes["testapp"][0].name, "0001_initial")630        self.assertEqual(changes["otherapp"][0].name, "0001_initial")631        self.assertNotIn("thirdapp", changes)632    def test_custom_migration_name(self):633        """Tests custom naming of migrations for graph matching."""634        # Make a fake graph635        graph = MigrationGraph()636        graph.add_node(("testapp", "0001_initial"), None)637        graph.add_node(("testapp", "0002_foobar"), None)638        graph.add_node(("otherapp", "0001_initial"), None)639        graph.add_dependency("testapp.0002_foobar", ("testapp", "0002_foobar"), ("testapp", "0001_initial"))640        # Use project state to make a new migration change set641        before = self.make_project_state([])642        after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable])643        autodetector = MigrationAutodetector(before, after)644        changes = autodetector._detect_changes()645        # Run through arrange_for_graph646        migration_name = 'custom_name'647        changes = autodetector.arrange_for_graph(changes, graph, migration_name)648        # Make sure there's a new name, deps match, etc.649        self.assertEqual(changes["testapp"][0].name, "0003_%s" % migration_name)650        self.assertEqual(changes["testapp"][0].dependencies, [("testapp", "0002_foobar")])651        self.assertEqual(changes["otherapp"][0].name, "0002_%s" % migration_name)652        self.assertEqual(changes["otherapp"][0].dependencies, [("otherapp", "0001_initial")])653    def test_new_model(self):654        """Tests autodetection of new models."""655        changes = self.get_changes([], [self.other_pony_food])656        # Right number/type of migrations?657        self.assertNumberMigrations(changes, 'otherapp', 1)658        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])659        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Pony")660        self.assertEqual([name for name, mgr in changes['otherapp'][0].operations[0].managers],661                         ['food_qs', 'food_mgr', 'food_mgr_kwargs'])662    def test_old_model(self):663        """Tests deletion of old models."""664        changes = self.get_changes([self.author_empty], [])665        # Right number/type of migrations?666        self.assertNumberMigrations(changes, 'testapp', 1)667        self.assertOperationTypes(changes, 'testapp', 0, ["DeleteModel"])668        self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")669    def test_add_field(self):670        """Tests autodetection of new fields."""671        changes = self.get_changes([self.author_empty], [self.author_name])672        # Right number/type of migrations?673        self.assertNumberMigrations(changes, 'testapp', 1)674        self.assertOperationTypes(changes, 'testapp', 0, ["AddField"])675        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")676    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',677                side_effect=AssertionError("Should not have prompted for not null addition"))678    def test_add_date_fields_with_auto_now_not_asking_for_default(self, mocked_ask_method):679        changes = self.get_changes([self.author_empty], [self.author_dates_of_birth_auto_now])680        # Right number/type of migrations?681        self.assertNumberMigrations(changes, 'testapp', 1)682        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField", "AddField"])683        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now=True)684        self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now=True)685        self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now=True)686    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',687                side_effect=AssertionError("Should not have prompted for not null addition"))688    def test_add_date_fields_with_auto_now_add_not_asking_for_null_addition(self, mocked_ask_method):689        changes = self.get_changes([self.author_empty], [self.author_dates_of_birth_auto_now_add])690        # Right number/type of migrations?691        self.assertNumberMigrations(changes, 'testapp', 1)692        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField", "AddField"])693        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)694        self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)695        self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)696    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_auto_now_add_addition')697    def test_add_date_fields_with_auto_now_add_asking_for_default(self, mocked_ask_method):698        changes = self.get_changes([self.author_empty], [self.author_dates_of_birth_auto_now_add])699        # Right number/type of migrations?700        self.assertNumberMigrations(changes, 'testapp', 1)701        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField", "AddField"])702        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)703        self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)704        self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)705        self.assertEqual(mocked_ask_method.call_count, 3)706    def test_remove_field(self):707        """Tests autodetection of removed fields."""708        changes = self.get_changes([self.author_name], [self.author_empty])709        # Right number/type of migrations?710        self.assertNumberMigrations(changes, 'testapp', 1)711        self.assertOperationTypes(changes, 'testapp', 0, ["RemoveField"])712        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")713    def test_alter_field(self):714        """Tests autodetection of new fields."""715        changes = self.get_changes([self.author_name], [self.author_name_longer])716        # Right number/type of migrations?717        self.assertNumberMigrations(changes, 'testapp', 1)718        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])719        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=True)720    def test_supports_functools_partial(self):721        def _content_file_name(instance, filename, key, **kwargs):722            return '{}/{}'.format(instance, filename)723        def content_file_name(key, **kwargs):724            return functools.partial(_content_file_name, key, **kwargs)725        # An unchanged partial reference.726        before = [ModelState("testapp", "Author", [727            ("id", models.AutoField(primary_key=True)),728            ("file", models.FileField(max_length=200, upload_to=content_file_name('file'))),729        ])]730        after = [ModelState("testapp", "Author", [731            ("id", models.AutoField(primary_key=True)),732            ("file", models.FileField(max_length=200, upload_to=content_file_name('file'))),733        ])]734        changes = self.get_changes(before, after)735        self.assertNumberMigrations(changes, 'testapp', 0)736        # A changed partial reference.737        args_changed = [ModelState("testapp", "Author", [738            ("id", models.AutoField(primary_key=True)),739            ("file", models.FileField(max_length=200, upload_to=content_file_name('other-file'))),740        ])]741        changes = self.get_changes(before, args_changed)742        self.assertNumberMigrations(changes, 'testapp', 1)743        self.assertOperationTypes(changes, 'testapp', 0, ['AlterField'])744        # Can't use assertOperationFieldAttributes because we need the745        # deconstructed version, i.e., the exploded func/args/keywords rather746        # than the partial: we don't care if it's not the same instance of the747        # partial, only if it's the same source function, args, and keywords.748        value = changes['testapp'][0].operations[0].field.upload_to749        self.assertEqual(750            (_content_file_name, ('other-file',), {}),751            (value.func, value.args, value.keywords)752        )753        kwargs_changed = [ModelState("testapp", "Author", [754            ("id", models.AutoField(primary_key=True)),755            ("file", models.FileField(max_length=200, upload_to=content_file_name('file', spam='eggs'))),756        ])]757        changes = self.get_changes(before, kwargs_changed)758        self.assertNumberMigrations(changes, 'testapp', 1)759        self.assertOperationTypes(changes, 'testapp', 0, ['AlterField'])760        value = changes['testapp'][0].operations[0].field.upload_to761        self.assertEqual(762            (_content_file_name, ('file',), {'spam': 'eggs'}),763            (value.func, value.args, value.keywords)764        )765    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration',766                side_effect=AssertionError("Should not have prompted for not null addition"))767    def test_alter_field_to_not_null_with_default(self, mocked_ask_method):768        """769        #23609 - Tests autodetection of nullable to non-nullable alterations.770        """771        changes = self.get_changes([self.author_name_null], [self.author_name_default])772        # Right number/type of migrations?773        self.assertNumberMigrations(changes, 'testapp', 1)774        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])775        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=True)776        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, default='Ada Lovelace')777    @mock.patch(778        'django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration',779        return_value=models.NOT_PROVIDED,780    )781    def test_alter_field_to_not_null_without_default(self, mocked_ask_method):782        """783        #23609 - Tests autodetection of nullable to non-nullable alterations.784        """785        changes = self.get_changes([self.author_name_null], [self.author_name])786        self.assertEqual(mocked_ask_method.call_count, 1)787        # Right number/type of migrations?788        self.assertNumberMigrations(changes, 'testapp', 1)789        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])790        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=True)791        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, default=models.NOT_PROVIDED)792    @mock.patch(793        'django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration',794        return_value='Some Name',795    )796    def test_alter_field_to_not_null_oneoff_default(self, mocked_ask_method):797        """798        #23609 - Tests autodetection of nullable to non-nullable alterations.799        """800        changes = self.get_changes([self.author_name_null], [self.author_name])801        self.assertEqual(mocked_ask_method.call_count, 1)802        # Right number/type of migrations?803        self.assertNumberMigrations(changes, 'testapp', 1)804        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])805        self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=False)806        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, default="Some Name")807    def test_rename_field(self):808        """Tests autodetection of renamed fields."""809        changes = self.get_changes(810            [self.author_name], [self.author_name_renamed], MigrationQuestioner({"ask_rename": True})811        )812        # Right number/type of migrations?813        self.assertNumberMigrations(changes, 'testapp', 1)814        self.assertOperationTypes(changes, 'testapp', 0, ["RenameField"])815        self.assertOperationAttributes(changes, 'testapp', 0, 0, old_name="name", new_name="names")816    def test_rename_field_foreign_key_to_field(self):817        before = [818            ModelState('app', 'Foo', [819                ('id', models.AutoField(primary_key=True)),820                ('field', models.IntegerField(unique=True)),821            ]),822            ModelState('app', 'Bar', [823                ('id', models.AutoField(primary_key=True)),824                ('foo', models.ForeignKey('app.Foo', models.CASCADE, to_field='field')),825            ]),826        ]827        after = [828            ModelState('app', 'Foo', [829                ('id', models.AutoField(primary_key=True)),830                ('renamed_field', models.IntegerField(unique=True)),831            ]),832            ModelState('app', 'Bar', [833                ('id', models.AutoField(primary_key=True)),834                ('foo', models.ForeignKey('app.Foo', models.CASCADE, to_field='renamed_field')),835            ]),836        ]837        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))838        # Right number/type of migrations?839        self.assertNumberMigrations(changes, 'app', 1)840        self.assertOperationTypes(changes, 'app', 0, ['RenameField'])841        self.assertOperationAttributes(changes, 'app', 0, 0, old_name='field', new_name='renamed_field')842    def test_rename_foreign_object_fields(self):843        fields = ('first', 'second')844        renamed_fields = ('first_renamed', 'second_renamed')845        before = [846            ModelState('app', 'Foo', [847                ('id', models.AutoField(primary_key=True)),848                ('first', models.IntegerField()),849                ('second', models.IntegerField()),850            ], options={'unique_together': {fields}}),851            ModelState('app', 'Bar', [852                ('id', models.AutoField(primary_key=True)),853                ('first', models.IntegerField()),854                ('second', models.IntegerField()),855                ('foo', models.ForeignObject(856                    'app.Foo', models.CASCADE, from_fields=fields, to_fields=fields,857                )),858            ]),859        ]860        # Case 1: to_fields renames.861        after = [862            ModelState('app', 'Foo', [863                ('id', models.AutoField(primary_key=True)),864                ('first_renamed', models.IntegerField()),865                ('second_renamed', models.IntegerField()),866            ], options={'unique_together': {renamed_fields}}),867            ModelState('app', 'Bar', [868                ('id', models.AutoField(primary_key=True)),869                ('first', models.IntegerField()),870                ('second', models.IntegerField()),871                ('foo', models.ForeignObject(872                    'app.Foo', models.CASCADE, from_fields=fields, to_fields=renamed_fields,873                )),874            ]),875        ]876        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))877        self.assertNumberMigrations(changes, 'app', 1)878        self.assertOperationTypes(changes, 'app', 0, ['RenameField', 'RenameField', 'AlterUniqueTogether'])879        self.assertOperationAttributes(880            changes, 'app', 0, 0, model_name='foo', old_name='first', new_name='first_renamed',881        )882        self.assertOperationAttributes(883            changes, 'app', 0, 1, model_name='foo', old_name='second', new_name='second_renamed',884        )885        # Case 2: from_fields renames.886        after = [887            ModelState('app', 'Foo', [888                ('id', models.AutoField(primary_key=True)),889                ('first', models.IntegerField()),890                ('second', models.IntegerField()),891            ], options={'unique_together': {fields}}),892            ModelState('app', 'Bar', [893                ('id', models.AutoField(primary_key=True)),894                ('first_renamed', models.IntegerField()),895                ('second_renamed', models.IntegerField()),896                ('foo', models.ForeignObject(897                    'app.Foo', models.CASCADE, from_fields=renamed_fields, to_fields=fields,898                )),899            ]),900        ]901        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))902        self.assertNumberMigrations(changes, 'app', 1)903        self.assertOperationTypes(changes, 'app', 0, ['RenameField', 'RenameField'])904        self.assertOperationAttributes(905            changes, 'app', 0, 0, model_name='bar', old_name='first', new_name='first_renamed',906        )907        self.assertOperationAttributes(908            changes, 'app', 0, 1, model_name='bar', old_name='second', new_name='second_renamed',909        )910    def test_rename_referenced_primary_key(self):911        before = [912            ModelState('app', 'Foo', [913                ('id', models.CharField(primary_key=True, serialize=False)),914            ]),915            ModelState('app', 'Bar', [916                ('id', models.AutoField(primary_key=True)),917                ('foo', models.ForeignKey('app.Foo', models.CASCADE)),918            ]),919        ]920        after = [921            ModelState('app', 'Foo', [922                ('renamed_id', models.CharField(primary_key=True, serialize=False))923            ]),924            ModelState('app', 'Bar', [925                ('id', models.AutoField(primary_key=True)),926                ('foo', models.ForeignKey('app.Foo', models.CASCADE)),927            ]),928        ]929        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))930        self.assertNumberMigrations(changes, 'app', 1)931        self.assertOperationTypes(changes, 'app', 0, ['RenameField'])932        self.assertOperationAttributes(changes, 'app', 0, 0, old_name='id', new_name='renamed_id')933    def test_rename_field_preserved_db_column(self):934        """935        RenameField is used if a field is renamed and db_column equal to the936        old field's column is added.937        """938        before = [939            ModelState('app', 'Foo', [940                ('id', models.AutoField(primary_key=True)),941                ('field', models.IntegerField()),942            ]),943        ]944        after = [945            ModelState('app', 'Foo', [946                ('id', models.AutoField(primary_key=True)),947                ('renamed_field', models.IntegerField(db_column='field')),948            ]),949        ]950        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))951        self.assertNumberMigrations(changes, 'app', 1)952        self.assertOperationTypes(changes, 'app', 0, ['RenameField', 'AlterField'])953        self.assertOperationAttributes(954            changes, 'app', 0, 0, model_name='foo', old_name='field', new_name='renamed_field',955        )956        self.assertOperationAttributes(changes, 'app', 0, 1, model_name='foo', name='renamed_field')957        self.assertEqual(changes['app'][0].operations[-1].field.deconstruct(), (958            'renamed_field', 'django.db.models.IntegerField', [], {'db_column': 'field'},959        ))960    def test_rename_related_field_preserved_db_column(self):961        before = [962            ModelState('app', 'Foo', [963                ('id', models.AutoField(primary_key=True)),964            ]),965            ModelState('app', 'Bar', [966                ('id', models.AutoField(primary_key=True)),967                ('foo', models.ForeignKey('app.Foo', models.CASCADE)),968            ]),969        ]970        after = [971            ModelState('app', 'Foo', [972                ('id', models.AutoField(primary_key=True)),973            ]),974            ModelState('app', 'Bar', [975                ('id', models.AutoField(primary_key=True)),976                ('renamed_foo', models.ForeignKey('app.Foo', models.CASCADE, db_column='foo_id')),977            ]),978        ]979        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename': True}))980        self.assertNumberMigrations(changes, 'app', 1)981        self.assertOperationTypes(changes, 'app', 0, ['RenameField', 'AlterField'])982        self.assertOperationAttributes(983            changes, 'app', 0, 0, model_name='bar', old_name='foo', new_name='renamed_foo',984        )985        self.assertOperationAttributes(changes, 'app', 0, 1, model_name='bar', name='renamed_foo')986        self.assertEqual(changes['app'][0].operations[-1].field.deconstruct(), (987            'renamed_foo',988            'django.db.models.ForeignKey',989            [],990            {'to': 'app.foo', 'on_delete': models.CASCADE, 'db_column': 'foo_id'},991        ))992    def test_rename_model(self):993        """Tests autodetection of renamed models."""994        changes = self.get_changes(995            [self.author_with_book, self.book],996            [self.author_renamed_with_book, self.book_with_author_renamed],997            MigrationQuestioner({"ask_rename_model": True}),998        )999        # Right number/type of migrations?1000        self.assertNumberMigrations(changes, 'testapp', 1)1001        self.assertOperationTypes(changes, 'testapp', 0, ["RenameModel"])1002        self.assertOperationAttributes(changes, 'testapp', 0, 0, old_name="Author", new_name="Writer")1003        # Now that RenameModel handles related fields too, there should be1004        # no AlterField for the related field.1005        self.assertNumberMigrations(changes, 'otherapp', 0)1006    def test_rename_model_case(self):1007        """1008        Model name is case-insensitive. Changing case doesn't lead to any1009        autodetected operations.1010        """1011        author_renamed = ModelState('testapp', 'author', [1012            ('id', models.AutoField(primary_key=True)),1013        ])1014        changes = self.get_changes(1015            [self.author_empty, self.book],1016            [author_renamed, self.book],1017            questioner=MigrationQuestioner({'ask_rename_model': True}),1018        )1019        self.assertNumberMigrations(changes, 'testapp', 0)1020        self.assertNumberMigrations(changes, 'otherapp', 0)1021    def test_rename_m2m_through_model(self):1022        """1023        Tests autodetection of renamed models that are used in M2M relations as1024        through models.1025        """1026        changes = self.get_changes(1027            [self.author_with_m2m_through, self.publisher, self.contract],1028            [self.author_with_renamed_m2m_through, self.publisher, self.contract_renamed],1029            MigrationQuestioner({'ask_rename_model': True})1030        )1031        # Right number/type of migrations?1032        self.assertNumberMigrations(changes, 'testapp', 1)1033        self.assertOperationTypes(changes, 'testapp', 0, ['RenameModel'])1034        self.assertOperationAttributes(changes, 'testapp', 0, 0, old_name='Contract', new_name='Deal')1035    def test_rename_model_with_renamed_rel_field(self):1036        """1037        Tests autodetection of renamed models while simultaneously renaming one1038        of the fields that relate to the renamed model.1039        """1040        changes = self.get_changes(1041            [self.author_with_book, self.book],1042            [self.author_renamed_with_book, self.book_with_field_and_author_renamed],1043            MigrationQuestioner({"ask_rename": True, "ask_rename_model": True}),1044        )1045        # Right number/type of migrations?1046        self.assertNumberMigrations(changes, 'testapp', 1)1047        self.assertOperationTypes(changes, 'testapp', 0, ["RenameModel"])1048        self.assertOperationAttributes(changes, 'testapp', 0, 0, old_name="Author", new_name="Writer")1049        # Right number/type of migrations for related field rename?1050        # Alter is already taken care of.1051        self.assertNumberMigrations(changes, 'otherapp', 1)1052        self.assertOperationTypes(changes, 'otherapp', 0, ["RenameField"])1053        self.assertOperationAttributes(changes, 'otherapp', 0, 0, old_name="author", new_name="writer")1054    def test_rename_model_with_fks_in_different_position(self):1055        """1056        #24537 - The order of fields in a model does not influence1057        the RenameModel detection.1058        """1059        before = [1060            ModelState("testapp", "EntityA", [1061                ("id", models.AutoField(primary_key=True)),1062            ]),1063            ModelState("testapp", "EntityB", [1064                ("id", models.AutoField(primary_key=True)),1065                ("some_label", models.CharField(max_length=255)),1066                ("entity_a", models.ForeignKey("testapp.EntityA", models.CASCADE)),1067            ]),1068        ]1069        after = [1070            ModelState("testapp", "EntityA", [1071                ("id", models.AutoField(primary_key=True)),1072            ]),1073            ModelState("testapp", "RenamedEntityB", [1074                ("id", models.AutoField(primary_key=True)),1075                ("entity_a", models.ForeignKey("testapp.EntityA", models.CASCADE)),1076                ("some_label", models.CharField(max_length=255)),1077            ]),1078        ]1079        changes = self.get_changes(before, after, MigrationQuestioner({"ask_rename_model": True}))1080        self.assertNumberMigrations(changes, "testapp", 1)1081        self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])1082        self.assertOperationAttributes(changes, "testapp", 0, 0, old_name="EntityB", new_name="RenamedEntityB")1083    def test_rename_model_reverse_relation_dependencies(self):1084        """1085        The migration to rename a model pointed to by a foreign key in another1086        app must run after the other app's migration that adds the foreign key1087        with model's original name. Therefore, the renaming migration has a1088        dependency on that other migration.1089        """1090        before = [1091            ModelState('testapp', 'EntityA', [1092                ('id', models.AutoField(primary_key=True)),1093            ]),1094            ModelState('otherapp', 'EntityB', [1095                ('id', models.AutoField(primary_key=True)),1096                ('entity_a', models.ForeignKey('testapp.EntityA', models.CASCADE)),1097            ]),1098        ]1099        after = [1100            ModelState('testapp', 'RenamedEntityA', [1101                ('id', models.AutoField(primary_key=True)),1102            ]),1103            ModelState('otherapp', 'EntityB', [1104                ('id', models.AutoField(primary_key=True)),1105                ('entity_a', models.ForeignKey('testapp.RenamedEntityA', models.CASCADE)),1106            ]),1107        ]1108        changes = self.get_changes(before, after, MigrationQuestioner({'ask_rename_model': True}))1109        self.assertNumberMigrations(changes, 'testapp', 1)1110        self.assertMigrationDependencies(changes, 'testapp', 0, [('otherapp', '__first__')])1111        self.assertOperationTypes(changes, 'testapp', 0, ['RenameModel'])1112        self.assertOperationAttributes(changes, 'testapp', 0, 0, old_name='EntityA', new_name='RenamedEntityA')1113    def test_fk_dependency(self):1114        """Having a ForeignKey automatically adds a dependency."""1115        # Note that testapp (author) has no dependencies,1116        # otherapp (book) depends on testapp (author),1117        # thirdapp (edition) depends on otherapp (book)1118        changes = self.get_changes([], [self.author_name, self.book, self.edition])1119        # Right number/type of migrations?1120        self.assertNumberMigrations(changes, 'testapp', 1)1121        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])1122        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")1123        self.assertMigrationDependencies(changes, 'testapp', 0, [])1124        # Right number/type of migrations?1125        self.assertNumberMigrations(changes, 'otherapp', 1)1126        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])1127        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name="Book")1128        self.assertMigrationDependencies(changes, 'otherapp', 0, [("testapp", "auto_1")])1129        # Right number/type of migrations?1130        self.assertNumberMigrations(changes, 'thirdapp', 1)1131        self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel"])1132        self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="Edition")1133        self.assertMigrationDependencies(changes, 'thirdapp', 0, [("otherapp", "auto_1")])1134    def test_proxy_fk_dependency(self):1135        """FK dependencies still work on proxy models."""1136        # Note that testapp (author) has no dependencies,1137        # otherapp (book) depends on testapp (authorproxy)1138        changes = self.get_changes([], [self.author_empty, self.author_proxy_third, self.book_proxy_fk])1139        # Right number/type of migrations?1140        self.assertNumberMigrations(changes, 'testapp', 1)1141        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])1142        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")1143        self.assertMigrationDependencies(changes, 'testapp', 0, [])1144        # Right number/type of migrations?1145        self.assertNumberMigrations(changes, 'otherapp', 1)1146        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])1147        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name="Book")1148        self.assertMigrationDependencies(changes, 'otherapp', 0, [("thirdapp", "auto_1")])1149        # Right number/type of migrations?1150        self.assertNumberMigrations(changes, 'thirdapp', 1)1151        self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel"])1152        self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="AuthorProxy")1153        self.assertMigrationDependencies(changes, 'thirdapp', 0, [("testapp", "auto_1")])1154    def test_same_app_no_fk_dependency(self):1155        """1156        A migration with a FK between two models of the same app1157        does not have a dependency to itself.1158        """1159        changes = self.get_changes([], [self.author_with_publisher, self.publisher])1160        # Right number/type of migrations?1161        self.assertNumberMigrations(changes, 'testapp', 1)1162        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel"])1163        self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")1164        self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")1165        self.assertMigrationDependencies(changes, 'testapp', 0, [])1166    def test_circular_fk_dependency(self):1167        """1168        Having a circular ForeignKey dependency automatically1169        resolves the situation into 2 migrations on one side and 1 on the other.1170        """1171        changes = self.get_changes([], [self.author_with_book, self.book, self.publisher_with_book])1172        # Right number/type of migrations?1173        self.assertNumberMigrations(changes, 'testapp', 1)1174        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel"])1175        self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")1176        self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")1177        self.assertMigrationDependencies(changes, 'testapp', 0, [("otherapp", "auto_1")])1178        # Right number/type of migrations?1179        self.assertNumberMigrations(changes, 'otherapp', 2)1180        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])1181        self.assertOperationTypes(changes, 'otherapp', 1, ["AddField"])1182        self.assertMigrationDependencies(changes, 'otherapp', 0, [])1183        self.assertMigrationDependencies(changes, 'otherapp', 1, [("otherapp", "auto_1"), ("testapp", "auto_1")])1184        # both split migrations should be `initial`1185        self.assertTrue(changes['otherapp'][0].initial)1186        self.assertTrue(changes['otherapp'][1].initial)1187    def test_same_app_circular_fk_dependency(self):1188        """1189        A migration with a FK between two models of the same app does1190        not have a dependency to itself.1191        """1192        changes = self.get_changes([], [self.author_with_publisher, self.publisher_with_author])1193        # Right number/type of migrations?1194        self.assertNumberMigrations(changes, 'testapp', 1)1195        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel", "AddField"])1196        self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")1197        self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")1198        self.assertOperationAttributes(changes, "testapp", 0, 2, name="publisher")1199        self.assertMigrationDependencies(changes, 'testapp', 0, [])1200    def test_same_app_circular_fk_dependency_with_unique_together_and_indexes(self):1201        """1202        #22275 - A migration with circular FK dependency does not try1203        to create unique together constraint and indexes before creating all1204        required fields first.1205        """1206        changes = self.get_changes([], [self.knight, self.rabbit])1207        # Right number/type of migrations?1208        self.assertNumberMigrations(changes, 'eggs', 1)1209        self.assertOperationTypes(1210            changes, 'eggs', 0, ["CreateModel", "CreateModel", "AddIndex", "AlterUniqueTogether"]1211        )1212        self.assertNotIn("unique_together", changes['eggs'][0].operations[0].options)1213        self.assertNotIn("unique_together", changes['eggs'][0].operations[1].options)1214        self.assertMigrationDependencies(changes, 'eggs', 0, [])1215    def test_alter_db_table_add(self):1216        """Tests detection for adding db_table in model's options."""1217        changes = self.get_changes([self.author_empty], [self.author_with_db_table_options])1218        # Right number/type of migrations?1219        self.assertNumberMigrations(changes, 'testapp', 1)1220        self.assertOperationTypes(changes, 'testapp', 0, ["AlterModelTable"])1221        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", table="author_one")1222    def test_alter_db_table_change(self):1223        """Tests detection for changing db_table in model's options'."""1224        changes = self.get_changes([self.author_with_db_table_options], [self.author_with_new_db_table_options])1225        # Right number/type of migrations?1226        self.assertNumberMigrations(changes, 'testapp', 1)1227        self.assertOperationTypes(changes, 'testapp', 0, ["AlterModelTable"])1228        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", table="author_two")1229    def test_alter_db_table_remove(self):1230        """Tests detection for removing db_table in model's options."""1231        changes = self.get_changes([self.author_with_db_table_options], [self.author_empty])1232        # Right number/type of migrations?1233        self.assertNumberMigrations(changes, 'testapp', 1)1234        self.assertOperationTypes(changes, 'testapp', 0, ["AlterModelTable"])1235        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", table=None)1236    def test_alter_db_table_no_changes(self):1237        """1238        Alter_db_table doesn't generate a migration if no changes have been made.1239        """1240        changes = self.get_changes([self.author_with_db_table_options], [self.author_with_db_table_options])1241        # Right number of migrations?1242        self.assertEqual(len(changes), 0)1243    def test_keep_db_table_with_model_change(self):1244        """1245        Tests when model changes but db_table stays as-is, autodetector must not1246        create more than one operation.1247        """1248        changes = self.get_changes(1249            [self.author_with_db_table_options],1250            [self.author_renamed_with_db_table_options],1251            MigrationQuestioner({"ask_rename_model": True}),1252        )1253        # Right number/type of migrations?1254        self.assertNumberMigrations(changes, 'testapp', 1)1255        self.assertOperationTypes(changes, 'testapp', 0, ["RenameModel"])1256        self.assertOperationAttributes(changes, "testapp", 0, 0, old_name="Author", new_name="NewAuthor")1257    def test_alter_db_table_with_model_change(self):1258        """1259        Tests when model and db_table changes, autodetector must create two1260        operations.1261        """1262        changes = self.get_changes(1263            [self.author_with_db_table_options],1264            [self.author_renamed_with_new_db_table_options],1265            MigrationQuestioner({"ask_rename_model": True}),1266        )1267        # Right number/type of migrations?1268        self.assertNumberMigrations(changes, 'testapp', 1)1269        self.assertOperationTypes(changes, 'testapp', 0, ["RenameModel", "AlterModelTable"])1270        self.assertOperationAttributes(changes, "testapp", 0, 0, old_name="Author", new_name="NewAuthor")1271        self.assertOperationAttributes(changes, "testapp", 0, 1, name="newauthor", table="author_three")1272    def test_identical_regex_doesnt_alter(self):1273        from_state = ModelState(1274            "testapp", "model", [("id", models.AutoField(primary_key=True, validators=[1275                RegexValidator(1276                    re.compile('^[-a-zA-Z0-9_]+\\Z'),1277                    'Enter a valid âslugâ consisting of letters, numbers, underscores or hyphens.',1278                    'invalid'1279                )1280            ]))]1281        )1282        to_state = ModelState(1283            "testapp", "model", [("id", models.AutoField(primary_key=True, validators=[validate_slug]))]1284        )1285        changes = self.get_changes([from_state], [to_state])1286        # Right number/type of migrations?1287        self.assertNumberMigrations(changes, "testapp", 0)1288    def test_different_regex_does_alter(self):1289        from_state = ModelState(1290            "testapp", "model", [("id", models.AutoField(primary_key=True, validators=[1291                RegexValidator(1292                    re.compile('^[a-z]+\\Z', 32),1293                    'Enter a valid âslugâ consisting of letters, numbers, underscores or hyphens.',1294                    'invalid'1295                )1296            ]))]1297        )1298        to_state = ModelState(1299            "testapp", "model", [("id", models.AutoField(primary_key=True, validators=[validate_slug]))]1300        )1301        changes = self.get_changes([from_state], [to_state])1302        self.assertNumberMigrations(changes, "testapp", 1)1303        self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])1304    def test_empty_foo_together(self):1305        """1306        #23452 - Empty unique/index_together shouldn't generate a migration.1307        """1308        # Explicitly testing for not specified, since this is the case after1309        # a CreateModel operation w/o any definition on the original model1310        model_state_not_specified = ModelState("a", "model", [("id", models.AutoField(primary_key=True))])1311        # Explicitly testing for None, since this was the issue in #23452 after1312        # an AlterFooTogether operation with e.g. () as value1313        model_state_none = ModelState("a", "model", [1314            ("id", models.AutoField(primary_key=True))1315        ], {1316            "index_together": None,1317            "unique_together": None,1318        })1319        # Explicitly testing for the empty set, since we now always have sets.1320        # During removal (('col1', 'col2'),) --> () this becomes set([])1321        model_state_empty = ModelState("a", "model", [1322            ("id", models.AutoField(primary_key=True))1323        ], {1324            "index_together": set(),1325            "unique_together": set(),1326        })1327        def test(from_state, to_state, msg):1328            changes = self.get_changes([from_state], [to_state])1329            if changes:1330                ops = ', '.join(o.__class__.__name__ for o in changes['a'][0].operations)1331                self.fail('Created operation(s) %s from %s' % (ops, msg))1332        tests = (1333            (model_state_not_specified, model_state_not_specified, '"not specified" to "not specified"'),1334            (model_state_not_specified, model_state_none, '"not specified" to "None"'),1335            (model_state_not_specified, model_state_empty, '"not specified" to "empty"'),1336            (model_state_none, model_state_not_specified, '"None" to "not specified"'),1337            (model_state_none, model_state_none, '"None" to "None"'),1338            (model_state_none, model_state_empty, '"None" to "empty"'),1339            (model_state_empty, model_state_not_specified, '"empty" to "not specified"'),1340            (model_state_empty, model_state_none, '"empty" to "None"'),1341            (model_state_empty, model_state_empty, '"empty" to "empty"'),1342        )1343        for t in tests:1344            test(*t)1345    def test_create_model_with_indexes(self):1346        """Test creation of new model with indexes already defined."""1347        author = ModelState('otherapp', 'Author', [1348            ('id', models.AutoField(primary_key=True)),1349            ('name', models.CharField(max_length=200)),1350        ], {'indexes': [models.Index(fields=['name'], name='create_model_with_indexes_idx')]})1351        changes = self.get_changes([], [author])1352        added_index = models.Index(fields=['name'], name='create_model_with_indexes_idx')1353        # Right number of migrations?1354        self.assertEqual(len(changes['otherapp']), 1)1355        # Right number of actions?1356        migration = changes['otherapp'][0]1357        self.assertEqual(len(migration.operations), 2)1358        # Right actions order?1359        self.assertOperationTypes(changes, 'otherapp', 0, ['CreateModel', 'AddIndex'])1360        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name='Author')1361        self.assertOperationAttributes(changes, 'otherapp', 0, 1, model_name='author', index=added_index)1362    def test_add_indexes(self):1363        """Test change detection of new indexes."""1364        changes = self.get_changes([self.author_empty, self.book], [self.author_empty, self.book_indexes])1365        self.assertNumberMigrations(changes, 'otherapp', 1)1366        self.assertOperationTypes(changes, 'otherapp', 0, ['AddIndex'])1367        added_index = models.Index(fields=['author', 'title'], name='book_title_author_idx')1368        self.assertOperationAttributes(changes, 'otherapp', 0, 0, model_name='book', index=added_index)1369    def test_remove_indexes(self):1370        """Test change detection of removed indexes."""1371        changes = self.get_changes([self.author_empty, self.book_indexes], [self.author_empty, self.book])1372        # Right number/type of migrations?1373        self.assertNumberMigrations(changes, 'otherapp', 1)1374        self.assertOperationTypes(changes, 'otherapp', 0, ['RemoveIndex'])1375        self.assertOperationAttributes(changes, 'otherapp', 0, 0, model_name='book', name='book_title_author_idx')1376    def test_order_fields_indexes(self):1377        """Test change detection of reordering of fields in indexes."""1378        changes = self.get_changes(1379            [self.author_empty, self.book_indexes], [self.author_empty, self.book_unordered_indexes]1380        )1381        self.assertNumberMigrations(changes, 'otherapp', 1)1382        self.assertOperationTypes(changes, 'otherapp', 0, ['RemoveIndex', 'AddIndex'])1383        self.assertOperationAttributes(changes, 'otherapp', 0, 0, model_name='book', name='book_title_author_idx')1384        added_index = models.Index(fields=['title', 'author'], name='book_author_title_idx')1385        self.assertOperationAttributes(changes, 'otherapp', 0, 1, model_name='book', index=added_index)1386    def test_create_model_with_check_constraint(self):1387        """Test creation of new model with constraints already defined."""1388        author = ModelState('otherapp', 'Author', [1389            ('id', models.AutoField(primary_key=True)),1390            ('name', models.CharField(max_length=200)),1391        ], {'constraints': [models.CheckConstraint(check=models.Q(name__contains='Bob'), name='name_contains_bob')]})1392        changes = self.get_changes([], [author])1393        added_constraint = models.CheckConstraint(check=models.Q(name__contains='Bob'), name='name_contains_bob')1394        # Right number of migrations?1395        self.assertEqual(len(changes['otherapp']), 1)1396        # Right number of actions?1397        migration = changes['otherapp'][0]1398        self.assertEqual(len(migration.operations), 2)1399        # Right actions order?1400        self.assertOperationTypes(changes, 'otherapp', 0, ['CreateModel', 'AddConstraint'])1401        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name='Author')1402        self.assertOperationAttributes(changes, 'otherapp', 0, 1, model_name='author', constraint=added_constraint)1403    def test_add_constraints(self):1404        """Test change detection of new constraints."""1405        changes = self.get_changes([self.author_name], [self.author_name_check_constraint])1406        self.assertNumberMigrations(changes, 'testapp', 1)1407        self.assertOperationTypes(changes, 'testapp', 0, ['AddConstraint'])1408        added_constraint = models.CheckConstraint(check=models.Q(name__contains='Bob'), name='name_contains_bob')1409        self.assertOperationAttributes(changes, 'testapp', 0, 0, model_name='author', constraint=added_constraint)1410    def test_remove_constraints(self):1411        """Test change detection of removed constraints."""1412        changes = self.get_changes([self.author_name_check_constraint], [self.author_name])1413        # Right number/type of migrations?1414        self.assertNumberMigrations(changes, 'testapp', 1)1415        self.assertOperationTypes(changes, 'testapp', 0, ['RemoveConstraint'])1416        self.assertOperationAttributes(changes, 'testapp', 0, 0, model_name='author', name='name_contains_bob')1417    def test_add_foo_together(self):1418        """Tests index/unique_together detection."""1419        changes = self.get_changes([self.author_empty, self.book], [self.author_empty, self.book_foo_together])1420        # Right number/type of migrations?1421        self.assertNumberMigrations(changes, "otherapp", 1)1422        self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether"])1423        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="book", unique_together={("author", "title")})1424        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", index_together={("author", "title")})1425    def test_remove_foo_together(self):1426        """Tests index/unique_together detection."""1427        changes = self.get_changes([self.author_empty, self.book_foo_together], [self.author_empty, self.book])1428        # Right number/type of migrations?1429        self.assertNumberMigrations(changes, "otherapp", 1)1430        self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether"])1431        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="book", unique_together=set())1432        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", index_together=set())1433    def test_foo_together_remove_fk(self):1434        """Tests unique_together and field removal detection & ordering"""1435        changes = self.get_changes(1436            [self.author_empty, self.book_foo_together], [self.author_empty, self.book_with_no_author]1437        )1438        # Right number/type of migrations?1439        self.assertNumberMigrations(changes, "otherapp", 1)1440        self.assertOperationTypes(changes, "otherapp", 0, [1441            "AlterUniqueTogether", "AlterIndexTogether", "RemoveField"1442        ])1443        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="book", unique_together=set())1444        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", index_together=set())1445        self.assertOperationAttributes(changes, "otherapp", 0, 2, model_name="book", name="author")1446    def test_foo_together_no_changes(self):1447        """1448        index/unique_together doesn't generate a migration if no1449        changes have been made.1450        """1451        changes = self.get_changes(1452            [self.author_empty, self.book_foo_together], [self.author_empty, self.book_foo_together]1453        )1454        # Right number of migrations?1455        self.assertEqual(len(changes), 0)1456    def test_foo_together_ordering(self):1457        """1458        index/unique_together also triggers on ordering changes.1459        """1460        changes = self.get_changes(1461            [self.author_empty, self.book_foo_together], [self.author_empty, self.book_foo_together_2]1462        )1463        # Right number/type of migrations?1464        self.assertNumberMigrations(changes, "otherapp", 1)1465        self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether"])1466        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="book", unique_together={("title", "author")})1467        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", index_together={("title", "author")})1468    def test_add_field_and_foo_together(self):1469        """1470        Added fields will be created before using them in index/unique_together.1471        """1472        changes = self.get_changes([self.author_empty, self.book], [self.author_empty, self.book_foo_together_3])1473        # Right number/type of migrations?1474        self.assertNumberMigrations(changes, "otherapp", 1)1475        self.assertOperationTypes(changes, "otherapp", 0, ["AddField", "AlterUniqueTogether", "AlterIndexTogether"])1476        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", unique_together={("title", "newfield")})1477        self.assertOperationAttributes(changes, "otherapp", 0, 2, name="book", index_together={("title", "newfield")})1478    def test_create_model_and_unique_together(self):1479        author = ModelState("otherapp", "Author", [1480            ("id", models.AutoField(primary_key=True)),1481            ("name", models.CharField(max_length=200)),1482        ])1483        book_with_author = ModelState("otherapp", "Book", [1484            ("id", models.AutoField(primary_key=True)),1485            ("author", models.ForeignKey("otherapp.Author", models.CASCADE)),1486            ("title", models.CharField(max_length=200)),1487        ], {1488            "index_together": {("title", "author")},1489            "unique_together": {("title", "author")},1490        })1491        changes = self.get_changes([self.book_with_no_author], [author, book_with_author])1492        # Right number of migrations?1493        self.assertEqual(len(changes['otherapp']), 1)1494        # Right number of actions?1495        migration = changes['otherapp'][0]1496        self.assertEqual(len(migration.operations), 4)1497        # Right actions order?1498        self.assertOperationTypes(1499            changes, 'otherapp', 0,1500            ['CreateModel', 'AddField', 'AlterUniqueTogether', 'AlterIndexTogether']1501        )1502    def test_remove_field_and_foo_together(self):1503        """1504        Removed fields will be removed after updating index/unique_together.1505        """1506        changes = self.get_changes(1507            [self.author_empty, self.book_foo_together_3], [self.author_empty, self.book_foo_together]1508        )1509        # Right number/type of migrations?1510        self.assertNumberMigrations(changes, "otherapp", 1)1511        self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether", "RemoveField"])1512        self.assertOperationAttributes(changes, "otherapp", 0, 0, name="book", unique_together={("author", "title")})1513        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", index_together={("author", "title")})1514        self.assertOperationAttributes(changes, "otherapp", 0, 2, model_name="book", name="newfield")1515    def test_rename_field_and_foo_together(self):1516        """1517        Removed fields will be removed after updating index/unique_together.1518        """1519        changes = self.get_changes(1520            [self.author_empty, self.book_foo_together_3],1521            [self.author_empty, self.book_foo_together_4],1522            MigrationQuestioner({"ask_rename": True}),1523        )1524        # Right number/type of migrations?1525        self.assertNumberMigrations(changes, "otherapp", 1)1526        self.assertOperationTypes(changes, "otherapp", 0, ["RenameField", "AlterUniqueTogether", "AlterIndexTogether"])1527        self.assertOperationAttributes(changes, "otherapp", 0, 1, name="book", unique_together={1528            ("title", "newfield2")1529        })1530        self.assertOperationAttributes(changes, "otherapp", 0, 2, name="book", index_together={("title", "newfield2")})1531    def test_proxy(self):1532        """The autodetector correctly deals with proxy models."""1533        # First, we test adding a proxy model1534        changes = self.get_changes([self.author_empty], [self.author_empty, self.author_proxy])1535        # Right number/type of migrations?1536        self.assertNumberMigrations(changes, "testapp", 1)1537        self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])1538        self.assertOperationAttributes(1539            changes, "testapp", 0, 0, name="AuthorProxy", options={"proxy": True, "indexes": [], "constraints": []}1540        )1541        # Now, we test turning a proxy model into a non-proxy model1542        # It should delete the proxy then make the real one1543        changes = self.get_changes(1544            [self.author_empty, self.author_proxy], [self.author_empty, self.author_proxy_notproxy]1545        )1546        # Right number/type of migrations?1547        self.assertNumberMigrations(changes, "testapp", 1)1548        self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel", "CreateModel"])1549        self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy")1550        self.assertOperationAttributes(changes, "testapp", 0, 1, name="AuthorProxy", options={})1551    def test_proxy_custom_pk(self):1552        """1553        #23415 - The autodetector must correctly deal with custom FK on proxy1554        models.1555        """1556        # First, we test the default pk field name1557        changes = self.get_changes([], [self.author_empty, self.author_proxy_third, self.book_proxy_fk])1558        # The field name the FK on the book model points to1559        self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'id')1560        # Now, we test the custom pk field name1561        changes = self.get_changes([], [self.author_custom_pk, self.author_proxy_third, self.book_proxy_fk])1562        # The field name the FK on the book model points to1563        self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'pk_field')1564    def test_proxy_to_mti_with_fk_to_proxy(self):1565        # First, test the pk table and field name.1566        changes = self.get_changes(1567            [],1568            [self.author_empty, self.author_proxy_third, self.book_proxy_fk],1569        )1570        self.assertEqual(1571            changes['otherapp'][0].operations[0].fields[2][1].remote_field.model._meta.db_table,1572            'testapp_author',1573        )1574        self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'id')1575        # Change AuthorProxy to use MTI.1576        changes = self.get_changes(1577            [self.author_empty, self.author_proxy_third, self.book_proxy_fk],1578            [self.author_empty, self.author_proxy_third_notproxy, self.book_proxy_fk],1579        )1580        # Right number/type of migrations for the AuthorProxy model?1581        self.assertNumberMigrations(changes, 'thirdapp', 1)1582        self.assertOperationTypes(changes, 'thirdapp', 0, ['DeleteModel', 'CreateModel'])1583        # Right number/type of migrations for the Book model with a FK to1584        # AuthorProxy?1585        self.assertNumberMigrations(changes, 'otherapp', 1)1586        self.assertOperationTypes(changes, 'otherapp', 0, ['AlterField'])1587        # otherapp should depend on thirdapp.1588        self.assertMigrationDependencies(changes, 'otherapp', 0, [('thirdapp', 'auto_1')])1589        # Now, test the pk table and field name.1590        self.assertEqual(1591            changes['otherapp'][0].operations[0].field.remote_field.model._meta.db_table,1592            'thirdapp_authorproxy',1593        )1594        self.assertEqual(changes['otherapp'][0].operations[0].field.remote_field.field_name, 'author_ptr')1595    def test_proxy_to_mti_with_fk_to_proxy_proxy(self):1596        # First, test the pk table and field name.1597        changes = self.get_changes(1598            [],1599            [self.author_empty, self.author_proxy, self.author_proxy_proxy, self.book_proxy_proxy_fk],1600        )1601        self.assertEqual(1602            changes['otherapp'][0].operations[0].fields[1][1].remote_field.model._meta.db_table,1603            'testapp_author',1604        )1605        self.assertEqual(changes['otherapp'][0].operations[0].fields[1][1].remote_field.field_name, 'id')1606        # Change AuthorProxy to use MTI. FK still points to AAuthorProxyProxy,1607        # a proxy of AuthorProxy.1608        changes = self.get_changes(1609            [self.author_empty, self.author_proxy, self.author_proxy_proxy, self.book_proxy_proxy_fk],1610            [self.author_empty, self.author_proxy_notproxy, self.author_proxy_proxy, self.book_proxy_proxy_fk],1611        )1612        # Right number/type of migrations for the AuthorProxy model?1613        self.assertNumberMigrations(changes, 'testapp', 1)1614        self.assertOperationTypes(changes, 'testapp', 0, ['DeleteModel', 'CreateModel'])1615        # Right number/type of migrations for the Book model with a FK to1616        # AAuthorProxyProxy?1617        self.assertNumberMigrations(changes, 'otherapp', 1)1618        self.assertOperationTypes(changes, 'otherapp', 0, ['AlterField'])1619        # otherapp should depend on testapp.1620        self.assertMigrationDependencies(changes, 'otherapp', 0, [('testapp', 'auto_1')])1621        # Now, test the pk table and field name.1622        self.assertEqual(1623            changes['otherapp'][0].operations[0].field.remote_field.model._meta.db_table,1624            'testapp_authorproxy',1625        )1626        self.assertEqual(changes['otherapp'][0].operations[0].field.remote_field.field_name, 'author_ptr')1627    def test_unmanaged_create(self):1628        """The autodetector correctly deals with managed models."""1629        # First, we test adding an unmanaged model1630        changes = self.get_changes([self.author_empty], [self.author_empty, self.author_unmanaged])1631        # Right number/type of migrations?1632        self.assertNumberMigrations(changes, 'testapp', 1)1633        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])1634        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="AuthorUnmanaged", options={"managed": False})1635    def test_unmanaged_delete(self):1636        changes = self.get_changes([self.author_empty, self.author_unmanaged], [self.author_empty])1637        self.assertNumberMigrations(changes, 'testapp', 1)1638        self.assertOperationTypes(changes, 'testapp', 0, ['DeleteModel'])1639    def test_unmanaged_to_managed(self):1640        # Now, we test turning an unmanaged model into a managed model1641        changes = self.get_changes(1642            [self.author_empty, self.author_unmanaged], [self.author_empty, self.author_unmanaged_managed]1643        )1644        # Right number/type of migrations?1645        self.assertNumberMigrations(changes, 'testapp', 1)1646        self.assertOperationTypes(changes, 'testapp', 0, ["AlterModelOptions"])1647        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="authorunmanaged", options={})1648    def test_managed_to_unmanaged(self):1649        # Now, we turn managed to unmanaged.1650        changes = self.get_changes(1651            [self.author_empty, self.author_unmanaged_managed], [self.author_empty, self.author_unmanaged]1652        )1653        # Right number/type of migrations?1654        self.assertNumberMigrations(changes, 'testapp', 1)1655        self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])1656        self.assertOperationAttributes(changes, "testapp", 0, 0, name="authorunmanaged", options={"managed": False})1657    def test_unmanaged_custom_pk(self):1658        """1659        #23415 - The autodetector must correctly deal with custom FK on1660        unmanaged models.1661        """1662        # First, we test the default pk field name1663        changes = self.get_changes([], [self.author_unmanaged_default_pk, self.book])1664        # The field name the FK on the book model points to1665        self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'id')1666        # Now, we test the custom pk field name1667        changes = self.get_changes([], [self.author_unmanaged_custom_pk, self.book])1668        # The field name the FK on the book model points to1669        self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'pk_field')1670    @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")1671    def test_swappable(self):1672        with isolate_lru_cache(apps.get_swappable_settings_name):1673            changes = self.get_changes([self.custom_user], [self.custom_user, self.author_with_custom_user])1674        # Right number/type of migrations?1675        self.assertNumberMigrations(changes, 'testapp', 1)1676        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])1677        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")1678        self.assertMigrationDependencies(changes, 'testapp', 0, [("__setting__", "AUTH_USER_MODEL")])1679    def test_swappable_changed(self):1680        with isolate_lru_cache(apps.get_swappable_settings_name):1681            before = self.make_project_state([self.custom_user, self.author_with_user])1682            with override_settings(AUTH_USER_MODEL="thirdapp.CustomUser"):1683                after = self.make_project_state([self.custom_user, self.author_with_custom_user])1684            autodetector = MigrationAutodetector(before, after)1685            changes = autodetector._detect_changes()1686        # Right number/type of migrations?1687        self.assertNumberMigrations(changes, 'testapp', 1)1688        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])1689        self.assertOperationAttributes(changes, 'testapp', 0, 0, model_name="author", name='user')1690        fk_field = changes['testapp'][0].operations[0].field1691        to_model = '%s.%s' % (1692            fk_field.remote_field.model._meta.app_label,1693            fk_field.remote_field.model._meta.object_name,1694        )1695        self.assertEqual(to_model, 'thirdapp.CustomUser')1696    def test_add_field_with_default(self):1697        """#22030 - Adding a field with a default should work."""1698        changes = self.get_changes([self.author_empty], [self.author_name_default])1699        # Right number/type of migrations?1700        self.assertNumberMigrations(changes, 'testapp', 1)1701        self.assertOperationTypes(changes, 'testapp', 0, ["AddField"])1702        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="name")1703    def test_custom_deconstructible(self):1704        """1705        Two instances which deconstruct to the same value aren't considered a1706        change.1707        """1708        changes = self.get_changes([self.author_name_deconstructible_1], [self.author_name_deconstructible_2])1709        # Right number of migrations?1710        self.assertEqual(len(changes), 0)1711    def test_deconstruct_field_kwarg(self):1712        """Field instances are handled correctly by nested deconstruction."""1713        changes = self.get_changes([self.author_name_deconstructible_3], [self.author_name_deconstructible_4])1714        self.assertEqual(changes, {})1715    def test_deconstructible_list(self):1716        """Nested deconstruction descends into lists."""1717        # When lists contain items that deconstruct to identical values, those lists1718        # should be considered equal for the purpose of detecting state changes1719        # (even if the original items are unequal).1720        changes = self.get_changes(1721            [self.author_name_deconstructible_list_1], [self.author_name_deconstructible_list_2]1722        )1723        self.assertEqual(changes, {})1724        # Legitimate differences within the deconstructed lists should be reported1725        # as a change1726        changes = self.get_changes(1727            [self.author_name_deconstructible_list_1], [self.author_name_deconstructible_list_3]1728        )1729        self.assertEqual(len(changes), 1)1730    def test_deconstructible_tuple(self):1731        """Nested deconstruction descends into tuples."""1732        # When tuples contain items that deconstruct to identical values, those tuples1733        # should be considered equal for the purpose of detecting state changes1734        # (even if the original items are unequal).1735        changes = self.get_changes(1736            [self.author_name_deconstructible_tuple_1], [self.author_name_deconstructible_tuple_2]1737        )1738        self.assertEqual(changes, {})1739        # Legitimate differences within the deconstructed tuples should be reported1740        # as a change1741        changes = self.get_changes(1742            [self.author_name_deconstructible_tuple_1], [self.author_name_deconstructible_tuple_3]1743        )1744        self.assertEqual(len(changes), 1)1745    def test_deconstructible_dict(self):1746        """Nested deconstruction descends into dict values."""1747        # When dicts contain items whose values deconstruct to identical values,1748        # those dicts should be considered equal for the purpose of detecting1749        # state changes (even if the original values are unequal).1750        changes = self.get_changes(1751            [self.author_name_deconstructible_dict_1], [self.author_name_deconstructible_dict_2]1752        )1753        self.assertEqual(changes, {})1754        # Legitimate differences within the deconstructed dicts should be reported1755        # as a change1756        changes = self.get_changes(1757            [self.author_name_deconstructible_dict_1], [self.author_name_deconstructible_dict_3]1758        )1759        self.assertEqual(len(changes), 1)1760    def test_nested_deconstructible_objects(self):1761        """1762        Nested deconstruction is applied recursively to the args/kwargs of1763        deconstructed objects.1764        """1765        # If the items within a deconstructed object's args/kwargs have the same1766        # deconstructed values - whether or not the items themselves are different1767        # instances - then the object as a whole is regarded as unchanged.1768        changes = self.get_changes(1769            [self.author_name_nested_deconstructible_1], [self.author_name_nested_deconstructible_2]1770        )1771        self.assertEqual(changes, {})1772        # Differences that exist solely within the args list of a deconstructed object1773        # should be reported as changes1774        changes = self.get_changes(1775            [self.author_name_nested_deconstructible_1], [self.author_name_nested_deconstructible_changed_arg]1776        )1777        self.assertEqual(len(changes), 1)1778        # Additional args should also be reported as a change1779        changes = self.get_changes(1780            [self.author_name_nested_deconstructible_1], [self.author_name_nested_deconstructible_extra_arg]1781        )1782        self.assertEqual(len(changes), 1)1783        # Differences that exist solely within the kwargs dict of a deconstructed object1784        # should be reported as changes1785        changes = self.get_changes(1786            [self.author_name_nested_deconstructible_1], [self.author_name_nested_deconstructible_changed_kwarg]1787        )1788        self.assertEqual(len(changes), 1)1789        # Additional kwargs should also be reported as a change1790        changes = self.get_changes(1791            [self.author_name_nested_deconstructible_1], [self.author_name_nested_deconstructible_extra_kwarg]1792        )1793        self.assertEqual(len(changes), 1)1794    def test_deconstruct_type(self):1795        """1796        #22951 -- Uninstantiated classes with deconstruct are correctly returned1797        by deep_deconstruct during serialization.1798        """1799        author = ModelState(1800            "testapp",1801            "Author",1802            [1803                ("id", models.AutoField(primary_key=True)),1804                ("name", models.CharField(1805                    max_length=200,1806                    # IntegerField intentionally not instantiated.1807                    default=models.IntegerField,1808                ))1809            ],1810        )1811        changes = self.get_changes([], [author])1812        # Right number/type of migrations?1813        self.assertNumberMigrations(changes, 'testapp', 1)1814        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])1815    def test_replace_string_with_foreignkey(self):1816        """1817        #22300 - Adding an FK in the same "spot" as a deleted CharField should1818        work.1819        """1820        changes = self.get_changes([self.author_with_publisher_string], [self.author_with_publisher, self.publisher])1821        # Right number/type of migrations?1822        self.assertNumberMigrations(changes, 'testapp', 1)1823        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "RemoveField", "AddField"])1824        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Publisher")1825        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="publisher_name")1826        self.assertOperationAttributes(changes, 'testapp', 0, 2, name="publisher")1827    def test_foreign_key_removed_before_target_model(self):1828        """1829        Removing an FK and the model it targets in the same change must remove1830        the FK field before the model to maintain consistency.1831        """1832        changes = self.get_changes(1833            [self.author_with_publisher, self.publisher], [self.author_name]1834        )  # removes both the model and FK1835        # Right number/type of migrations?1836        self.assertNumberMigrations(changes, 'testapp', 1)1837        self.assertOperationTypes(changes, 'testapp', 0, ["RemoveField", "DeleteModel"])1838        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="publisher")1839        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="Publisher")1840    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',1841                side_effect=AssertionError("Should not have prompted for not null addition"))1842    def test_add_many_to_many(self, mocked_ask_method):1843        """#22435 - Adding a ManyToManyField should not prompt for a default."""1844        changes = self.get_changes([self.author_empty, self.publisher], [self.author_with_m2m, self.publisher])1845        # Right number/type of migrations?1846        self.assertNumberMigrations(changes, 'testapp', 1)1847        self.assertOperationTypes(changes, 'testapp', 0, ["AddField"])1848        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="publishers")1849    def test_alter_many_to_many(self):1850        changes = self.get_changes(1851            [self.author_with_m2m, self.publisher], [self.author_with_m2m_blank, self.publisher]1852        )1853        # Right number/type of migrations?1854        self.assertNumberMigrations(changes, 'testapp', 1)1855        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])1856        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="publishers")1857    def test_create_with_through_model(self):1858        """1859        Adding a m2m with a through model and the models that use it should be1860        ordered correctly.1861        """1862        changes = self.get_changes([], [self.author_with_m2m_through, self.publisher, self.contract])1863        # Right number/type of migrations?1864        self.assertNumberMigrations(changes, "testapp", 1)1865        self.assertOperationTypes(changes, "testapp", 0, [1866            'CreateModel', 'CreateModel', 'CreateModel', 'AddField',1867        ])1868        self.assertOperationAttributes(changes, 'testapp', 0, 0, name='Author')1869        self.assertOperationAttributes(changes, 'testapp', 0, 1, name='Publisher')1870        self.assertOperationAttributes(changes, 'testapp', 0, 2, name='Contract')1871        self.assertOperationAttributes(changes, 'testapp', 0, 3, model_name='author', name='publishers')1872    def test_many_to_many_removed_before_through_model(self):1873        """1874        Removing a ManyToManyField and the "through" model in the same change1875        must remove the field before the model to maintain consistency.1876        """1877        changes = self.get_changes(1878            [self.book_with_multiple_authors_through_attribution, self.author_name, self.attribution],1879            [self.book_with_no_author, self.author_name],1880        )1881        # Remove both the through model and ManyToMany1882        # Right number/type of migrations?1883        self.assertNumberMigrations(changes, "otherapp", 1)1884        self.assertOperationTypes(changes, 'otherapp', 0, ['RemoveField', 'DeleteModel'])1885        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name='authors', model_name='book')1886        self.assertOperationAttributes(changes, 'otherapp', 0, 1, name='Attribution')1887    def test_many_to_many_removed_before_through_model_2(self):1888        """1889        Removing a model that contains a ManyToManyField and the "through" model1890        in the same change must remove the field before the model to maintain1891        consistency.1892        """1893        changes = self.get_changes(1894            [self.book_with_multiple_authors_through_attribution, self.author_name, self.attribution],1895            [self.author_name],1896        )1897        # Remove both the through model and ManyToMany1898        # Right number/type of migrations?1899        self.assertNumberMigrations(changes, "otherapp", 1)1900        self.assertOperationTypes(changes, 'otherapp', 0, ['RemoveField', 'DeleteModel', 'DeleteModel'])1901        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name='authors', model_name='book')1902        self.assertOperationAttributes(changes, 'otherapp', 0, 1, name='Attribution')1903        self.assertOperationAttributes(changes, 'otherapp', 0, 2, name='Book')1904    def test_m2m_w_through_multistep_remove(self):1905        """1906        A model with a m2m field that specifies a "through" model cannot be1907        removed in the same migration as that through model as the schema will1908        pass through an inconsistent state. The autodetector should produce two1909        migrations to avoid this issue.1910        """1911        changes = self.get_changes([self.author_with_m2m_through, self.publisher, self.contract], [self.publisher])1912        # Right number/type of migrations?1913        self.assertNumberMigrations(changes, "testapp", 1)1914        self.assertOperationTypes(changes, "testapp", 0, [1915            "RemoveField", "RemoveField", "DeleteModel", "DeleteModel"1916        ])1917        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", model_name='contract')1918        self.assertOperationAttributes(changes, "testapp", 0, 1, name="publisher", model_name='contract')1919        self.assertOperationAttributes(changes, "testapp", 0, 2, name="Author")1920        self.assertOperationAttributes(changes, "testapp", 0, 3, name="Contract")1921    def test_concrete_field_changed_to_many_to_many(self):1922        """1923        #23938 - Changing a concrete field into a ManyToManyField1924        first removes the concrete field and then adds the m2m field.1925        """1926        changes = self.get_changes([self.author_with_former_m2m], [self.author_with_m2m, self.publisher])1927        # Right number/type of migrations?1928        self.assertNumberMigrations(changes, "testapp", 1)1929        self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "RemoveField", "AddField"])1930        self.assertOperationAttributes(changes, 'testapp', 0, 0, name='Publisher')1931        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="publishers", model_name='author')1932        self.assertOperationAttributes(changes, 'testapp', 0, 2, name="publishers", model_name='author')1933    def test_many_to_many_changed_to_concrete_field(self):1934        """1935        #23938 - Changing a ManyToManyField into a concrete field1936        first removes the m2m field and then adds the concrete field.1937        """1938        changes = self.get_changes([self.author_with_m2m, self.publisher], [self.author_with_former_m2m])1939        # Right number/type of migrations?1940        self.assertNumberMigrations(changes, "testapp", 1)1941        self.assertOperationTypes(changes, "testapp", 0, ["RemoveField", "AddField", "DeleteModel"])1942        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="publishers", model_name='author')1943        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="publishers", model_name='author')1944        self.assertOperationAttributes(changes, 'testapp', 0, 2, name='Publisher')1945        self.assertOperationFieldAttributes(changes, 'testapp', 0, 1, max_length=100)1946    def test_non_circular_foreignkey_dependency_removal(self):1947        """1948        If two models with a ForeignKey from one to the other are removed at the1949        same time, the autodetector should remove them in the correct order.1950        """1951        changes = self.get_changes([self.author_with_publisher, self.publisher_with_author], [])1952        # Right number/type of migrations?1953        self.assertNumberMigrations(changes, "testapp", 1)1954        self.assertOperationTypes(changes, "testapp", 0, ["RemoveField", "DeleteModel", "DeleteModel"])1955        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", model_name='publisher')1956        self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")1957        self.assertOperationAttributes(changes, "testapp", 0, 2, name="Publisher")1958    def test_alter_model_options(self):1959        """Changing a model's options should make a change."""1960        changes = self.get_changes([self.author_empty], [self.author_with_options])1961        # Right number/type of migrations?1962        self.assertNumberMigrations(changes, "testapp", 1)1963        self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])1964        self.assertOperationAttributes(changes, "testapp", 0, 0, options={1965            "permissions": [('can_hire', 'Can hire')],1966            "verbose_name": "Authi",1967        })1968        # Changing them back to empty should also make a change1969        changes = self.get_changes([self.author_with_options], [self.author_empty])1970        # Right number/type of migrations?1971        self.assertNumberMigrations(changes, "testapp", 1)1972        self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])1973        self.assertOperationAttributes(changes, "testapp", 0, 0, name="author", options={})1974    def test_alter_model_options_proxy(self):1975        """Changing a proxy model's options should also make a change."""1976        changes = self.get_changes(1977            [self.author_proxy, self.author_empty], [self.author_proxy_options, self.author_empty]1978        )1979        # Right number/type of migrations?1980        self.assertNumberMigrations(changes, "testapp", 1)1981        self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])1982        self.assertOperationAttributes(changes, "testapp", 0, 0, name="authorproxy", options={1983            "verbose_name": "Super Author"1984        })1985    def test_set_alter_order_with_respect_to(self):1986        """Setting order_with_respect_to adds a field."""1987        changes = self.get_changes([self.book, self.author_with_book], [self.book, self.author_with_book_order_wrt])1988        # Right number/type of migrations?1989        self.assertNumberMigrations(changes, 'testapp', 1)1990        self.assertOperationTypes(changes, 'testapp', 0, ["AlterOrderWithRespectTo"])1991        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="author", order_with_respect_to="book")1992    def test_add_alter_order_with_respect_to(self):1993        """1994        Setting order_with_respect_to when adding the FK too does1995        things in the right order.1996        """1997        changes = self.get_changes([self.author_name], [self.book, self.author_with_book_order_wrt])1998        # Right number/type of migrations?1999        self.assertNumberMigrations(changes, 'testapp', 1)2000        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AlterOrderWithRespectTo"])2001        self.assertOperationAttributes(changes, 'testapp', 0, 0, model_name="author", name="book")2002        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book")2003    def test_remove_alter_order_with_respect_to(self):2004        """2005        Removing order_with_respect_to when removing the FK too does2006        things in the right order.2007        """2008        changes = self.get_changes([self.book, self.author_with_book_order_wrt], [self.author_name])2009        # Right number/type of migrations?2010        self.assertNumberMigrations(changes, 'testapp', 1)2011        self.assertOperationTypes(changes, 'testapp', 0, ["AlterOrderWithRespectTo", "RemoveField"])2012        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="author", order_with_respect_to=None)2013        self.assertOperationAttributes(changes, 'testapp', 0, 1, model_name="author", name="book")2014    def test_add_model_order_with_respect_to(self):2015        """2016        Setting order_with_respect_to when adding the whole model2017        does things in the right order.2018        """2019        changes = self.get_changes([], [self.book, self.author_with_book_order_wrt])2020        # Right number/type of migrations?2021        self.assertNumberMigrations(changes, 'testapp', 1)2022        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])2023        self.assertOperationAttributes(2024            changes, 'testapp', 0, 0, name="Author", options={'order_with_respect_to': 'book'}2025        )2026        self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields])2027    def test_add_model_order_with_respect_to_index_foo_together(self):2028        changes = self.get_changes([], [2029            self.book,2030            ModelState('testapp', 'Author', [2031                ('id', models.AutoField(primary_key=True)),2032                ('name', models.CharField(max_length=200)),2033                ('book', models.ForeignKey('otherapp.Book', models.CASCADE)),2034            ], options={2035                'order_with_respect_to': 'book',2036                'index_together': {('name', '_order')},2037                'unique_together': {('id', '_order')},2038            }),2039        ])2040        self.assertNumberMigrations(changes, 'testapp', 1)2041        self.assertOperationTypes(changes, 'testapp', 0, ['CreateModel'])2042        self.assertOperationAttributes(2043            changes,2044            'testapp',2045            0,2046            0,2047            name='Author',2048            options={2049                'order_with_respect_to': 'book',2050                'index_together': {('name', '_order')},2051                'unique_together': {('id', '_order')},2052            },2053        )2054    def test_add_model_order_with_respect_to_index_constraint(self):2055        tests = [2056            (2057                'AddIndex',2058                {'indexes': [2059                    models.Index(fields=['_order'], name='book_order_idx'),2060                ]},2061            ),2062            (2063                'AddConstraint',2064                {'constraints': [2065                    models.CheckConstraint(2066                        check=models.Q(_order__gt=1),2067                        name='book_order_gt_1',2068                    ),2069                ]},2070            ),2071        ]2072        for operation, extra_option in tests:2073            with self.subTest(operation=operation):2074                after = ModelState('testapp', 'Author', [2075                    ('id', models.AutoField(primary_key=True)),2076                    ('name', models.CharField(max_length=200)),2077                    ('book', models.ForeignKey('otherapp.Book', models.CASCADE)),2078                ], options={2079                    'order_with_respect_to': 'book',2080                    **extra_option,2081                })2082                changes = self.get_changes([], [self.book, after])2083                self.assertNumberMigrations(changes, 'testapp', 1)2084                self.assertOperationTypes(changes, 'testapp', 0, [2085                    'CreateModel', operation,2086                ])2087                self.assertOperationAttributes(2088                    changes,2089                    'testapp',2090                    0,2091                    0,2092                    name='Author',2093                    options={'order_with_respect_to': 'book'},2094                )2095    def test_set_alter_order_with_respect_to_index_constraint_foo_together(self):2096        tests = [2097            (2098                'AddIndex',2099                {'indexes': [2100                    models.Index(fields=['_order'], name='book_order_idx'),2101                ]},2102            ),2103            (2104                'AddConstraint',2105                {'constraints': [2106                    models.CheckConstraint(2107                        check=models.Q(_order__gt=1),2108                        name='book_order_gt_1',2109                    ),2110                ]},2111            ),2112            ('AlterIndexTogether', {'index_together': {('name', '_order')}}),2113            ('AlterUniqueTogether', {'unique_together': {('id', '_order')}}),2114        ]2115        for operation, extra_option in tests:2116            with self.subTest(operation=operation):2117                after = ModelState('testapp', 'Author', [2118                    ('id', models.AutoField(primary_key=True)),2119                    ('name', models.CharField(max_length=200)),2120                    ('book', models.ForeignKey('otherapp.Book', models.CASCADE)),2121                ], options={2122                    'order_with_respect_to': 'book',2123                    **extra_option,2124                })2125                changes = self.get_changes(2126                    [self.book, self.author_with_book],2127                    [self.book, after],2128                )2129                self.assertNumberMigrations(changes, 'testapp', 1)2130                self.assertOperationTypes(changes, 'testapp', 0, [2131                    'AlterOrderWithRespectTo', operation,2132                ])2133    def test_alter_model_managers(self):2134        """2135        Changing the model managers adds a new operation.2136        """2137        changes = self.get_changes([self.other_pony], [self.other_pony_food])2138        # Right number/type of migrations?2139        self.assertNumberMigrations(changes, 'otherapp', 1)2140        self.assertOperationTypes(changes, 'otherapp', 0, ["AlterModelManagers"])2141        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name="pony")2142        self.assertEqual([name for name, mgr in changes['otherapp'][0].operations[0].managers],2143                         ['food_qs', 'food_mgr', 'food_mgr_kwargs'])2144        self.assertEqual(changes['otherapp'][0].operations[0].managers[1][1].args, ('a', 'b', 1, 2))2145        self.assertEqual(changes['otherapp'][0].operations[0].managers[2][1].args, ('x', 'y', 3, 4))2146    def test_swappable_first_inheritance(self):2147        """Swappable models get their CreateModel first."""2148        changes = self.get_changes([], [self.custom_user, self.aardvark])2149        # Right number/type of migrations?2150        self.assertNumberMigrations(changes, 'thirdapp', 1)2151        self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"])2152        self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser")2153        self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark")2154    def test_default_related_name_option(self):2155        model_state = ModelState('app', 'model', [2156            ('id', models.AutoField(primary_key=True)),2157        ], options={'default_related_name': 'related_name'})2158        changes = self.get_changes([], [model_state])2159        self.assertNumberMigrations(changes, 'app', 1)2160        self.assertOperationTypes(changes, 'app', 0, ['CreateModel'])2161        self.assertOperationAttributes(2162            changes, 'app', 0, 0, name='model',2163            options={'default_related_name': 'related_name'},2164        )2165        altered_model_state = ModelState('app', 'Model', [2166            ('id', models.AutoField(primary_key=True)),2167        ])2168        changes = self.get_changes([model_state], [altered_model_state])2169        self.assertNumberMigrations(changes, 'app', 1)2170        self.assertOperationTypes(changes, 'app', 0, ['AlterModelOptions'])2171        self.assertOperationAttributes(changes, 'app', 0, 0, name='model', options={})2172    @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")2173    def test_swappable_first_setting(self):2174        """Swappable models get their CreateModel first."""2175        with isolate_lru_cache(apps.get_swappable_settings_name):2176            changes = self.get_changes([], [self.custom_user_no_inherit, self.aardvark])2177        # Right number/type of migrations?2178        self.assertNumberMigrations(changes, 'thirdapp', 1)2179        self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"])2180        self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser")2181        self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark")2182    def test_bases_first(self):2183        """Bases of other models come first."""2184        changes = self.get_changes([], [self.aardvark_based_on_author, self.author_name])2185        # Right number/type of migrations?2186        self.assertNumberMigrations(changes, 'testapp', 1)2187        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel"])2188        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")2189        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="Aardvark")2190    def test_multiple_bases(self):2191        """#23956 - Inheriting models doesn't move *_ptr fields into AddField operations."""2192        A = ModelState("app", "A", [("a_id", models.AutoField(primary_key=True))])2193        B = ModelState("app", "B", [("b_id", models.AutoField(primary_key=True))])2194        C = ModelState("app", "C", [], bases=("app.A", "app.B"))2195        D = ModelState("app", "D", [], bases=("app.A", "app.B"))2196        E = ModelState("app", "E", [], bases=("app.A", "app.B"))2197        changes = self.get_changes([], [A, B, C, D, E])2198        # Right number/type of migrations?2199        self.assertNumberMigrations(changes, "app", 1)2200        self.assertOperationTypes(changes, "app", 0, [2201            "CreateModel", "CreateModel", "CreateModel", "CreateModel", "CreateModel"2202        ])2203        self.assertOperationAttributes(changes, "app", 0, 0, name="A")2204        self.assertOperationAttributes(changes, "app", 0, 1, name="B")2205        self.assertOperationAttributes(changes, "app", 0, 2, name="C")2206        self.assertOperationAttributes(changes, "app", 0, 3, name="D")2207        self.assertOperationAttributes(changes, "app", 0, 4, name="E")2208    def test_proxy_bases_first(self):2209        """Bases of proxies come first."""2210        changes = self.get_changes([], [self.author_empty, self.author_proxy, self.author_proxy_proxy])2211        # Right number/type of migrations?2212        self.assertNumberMigrations(changes, 'testapp', 1)2213        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel", "CreateModel"])2214        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")2215        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="AuthorProxy")2216        self.assertOperationAttributes(changes, 'testapp', 0, 2, name="AAuthorProxyProxy")2217    def test_pk_fk_included(self):2218        """2219        A relation used as the primary key is kept as part of CreateModel.2220        """2221        changes = self.get_changes([], [self.aardvark_pk_fk_author, self.author_name])2222        # Right number/type of migrations?2223        self.assertNumberMigrations(changes, 'testapp', 1)2224        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "CreateModel"])2225        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Author")2226        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="Aardvark")2227    def test_first_dependency(self):2228        """2229        A dependency to an app with no migrations uses __first__.2230        """2231        # Load graph2232        loader = MigrationLoader(connection)2233        before = self.make_project_state([])2234        after = self.make_project_state([self.book_migrations_fk])2235        after.real_apps = ["migrations"]2236        autodetector = MigrationAutodetector(before, after)2237        changes = autodetector._detect_changes(graph=loader.graph)2238        # Right number/type of migrations?2239        self.assertNumberMigrations(changes, 'otherapp', 1)2240        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])2241        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name="Book")2242        self.assertMigrationDependencies(changes, 'otherapp', 0, [("migrations", "__first__")])2243    @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})2244    def test_last_dependency(self):2245        """2246        A dependency to an app with existing migrations uses the2247        last migration of that app.2248        """2249        # Load graph2250        loader = MigrationLoader(connection)2251        before = self.make_project_state([])2252        after = self.make_project_state([self.book_migrations_fk])2253        after.real_apps = ["migrations"]2254        autodetector = MigrationAutodetector(before, after)2255        changes = autodetector._detect_changes(graph=loader.graph)2256        # Right number/type of migrations?2257        self.assertNumberMigrations(changes, 'otherapp', 1)2258        self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])2259        self.assertOperationAttributes(changes, 'otherapp', 0, 0, name="Book")2260        self.assertMigrationDependencies(changes, 'otherapp', 0, [("migrations", "0002_second")])2261    def test_alter_fk_before_model_deletion(self):2262        """2263        ForeignKeys are altered _before_ the model they used to2264        refer to are deleted.2265        """2266        changes = self.get_changes(2267            [self.author_name, self.publisher_with_author],2268            [self.aardvark_testapp, self.publisher_with_aardvark_author]2269        )2270        # Right number/type of migrations?2271        self.assertNumberMigrations(changes, 'testapp', 1)2272        self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "AlterField", "DeleteModel"])2273        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="Aardvark")2274        self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author")2275        self.assertOperationAttributes(changes, 'testapp', 0, 2, name="Author")2276    def test_fk_dependency_other_app(self):2277        """2278        #23100 - ForeignKeys correctly depend on other apps' models.2279        """2280        changes = self.get_changes([self.author_name, self.book], [self.author_with_book, self.book])2281        # Right number/type of migrations?2282        self.assertNumberMigrations(changes, 'testapp', 1)2283        self.assertOperationTypes(changes, 'testapp', 0, ["AddField"])2284        self.assertOperationAttributes(changes, 'testapp', 0, 0, name="book")2285        self.assertMigrationDependencies(changes, 'testapp', 0, [("otherapp", "__first__")])2286    def test_alter_field_to_fk_dependency_other_app(self):2287        changes = self.get_changes(2288            [self.author_empty, self.book_with_no_author_fk],2289            [self.author_empty, self.book],2290        )2291        self.assertNumberMigrations(changes, 'otherapp', 1)2292        self.assertOperationTypes(changes, 'otherapp', 0, ['AlterField'])2293        self.assertMigrationDependencies(changes, 'otherapp', 0, [('testapp', '__first__')])2294    def test_circular_dependency_mixed_addcreate(self):2295        """2296        #23315 - The dependency resolver knows to put all CreateModel2297        before AddField and not become unsolvable.2298        """2299        address = ModelState("a", "Address", [2300            ("id", models.AutoField(primary_key=True)),2301            ("country", models.ForeignKey("b.DeliveryCountry", models.CASCADE)),2302        ])2303        person = ModelState("a", "Person", [2304            ("id", models.AutoField(primary_key=True)),2305        ])2306        apackage = ModelState("b", "APackage", [2307            ("id", models.AutoField(primary_key=True)),2308            ("person", models.ForeignKey("a.Person", models.CASCADE)),2309        ])2310        country = ModelState("b", "DeliveryCountry", [2311            ("id", models.AutoField(primary_key=True)),2312        ])2313        changes = self.get_changes([], [address, person, apackage, country])2314        # Right number/type of migrations?2315        self.assertNumberMigrations(changes, 'a', 2)2316        self.assertNumberMigrations(changes, 'b', 1)2317        self.assertOperationTypes(changes, 'a', 0, ["CreateModel", "CreateModel"])2318        self.assertOperationTypes(changes, 'a', 1, ["AddField"])2319        self.assertOperationTypes(changes, 'b', 0, ["CreateModel", "CreateModel"])2320    @override_settings(AUTH_USER_MODEL="a.Tenant")2321    def test_circular_dependency_swappable(self):2322        """2323        #23322 - The dependency resolver knows to explicitly resolve2324        swappable models.2325        """2326        with isolate_lru_cache(apps.get_swappable_settings_name):2327            tenant = ModelState("a", "Tenant", [2328                ("id", models.AutoField(primary_key=True)),2329                ("primary_address", models.ForeignKey("b.Address", models.CASCADE))],2330                bases=(AbstractBaseUser,)2331            )2332            address = ModelState("b", "Address", [2333                ("id", models.AutoField(primary_key=True)),2334                ("tenant", models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE)),2335            ])2336            changes = self.get_changes([], [address, tenant])2337        # Right number/type of migrations?2338        self.assertNumberMigrations(changes, 'a', 2)2339        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])2340        self.assertOperationTypes(changes, 'a', 1, ["AddField"])2341        self.assertMigrationDependencies(changes, 'a', 0, [])2342        self.assertMigrationDependencies(changes, 'a', 1, [('a', 'auto_1'), ('b', 'auto_1')])2343        # Right number/type of migrations?2344        self.assertNumberMigrations(changes, 'b', 1)2345        self.assertOperationTypes(changes, 'b', 0, ["CreateModel"])2346        self.assertMigrationDependencies(changes, 'b', 0, [('__setting__', 'AUTH_USER_MODEL')])2347    @override_settings(AUTH_USER_MODEL="b.Tenant")2348    def test_circular_dependency_swappable2(self):2349        """2350        #23322 - The dependency resolver knows to explicitly resolve2351        swappable models but with the swappable not being the first migrated2352        model.2353        """2354        with isolate_lru_cache(apps.get_swappable_settings_name):2355            address = ModelState("a", "Address", [2356                ("id", models.AutoField(primary_key=True)),2357                ("tenant", models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE)),2358            ])2359            tenant = ModelState("b", "Tenant", [2360                ("id", models.AutoField(primary_key=True)),2361                ("primary_address", models.ForeignKey("a.Address", models.CASCADE))],2362                bases=(AbstractBaseUser,)2363            )2364            changes = self.get_changes([], [address, tenant])2365        # Right number/type of migrations?2366        self.assertNumberMigrations(changes, 'a', 2)2367        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])2368        self.assertOperationTypes(changes, 'a', 1, ["AddField"])2369        self.assertMigrationDependencies(changes, 'a', 0, [])2370        self.assertMigrationDependencies(changes, 'a', 1, [('__setting__', 'AUTH_USER_MODEL'), ('a', 'auto_1')])2371        # Right number/type of migrations?2372        self.assertNumberMigrations(changes, 'b', 1)2373        self.assertOperationTypes(changes, 'b', 0, ["CreateModel"])2374        self.assertMigrationDependencies(changes, 'b', 0, [('a', 'auto_1')])2375    @override_settings(AUTH_USER_MODEL="a.Person")2376    def test_circular_dependency_swappable_self(self):2377        """2378        #23322 - The dependency resolver knows to explicitly resolve2379        swappable models.2380        """2381        with isolate_lru_cache(apps.get_swappable_settings_name):2382            person = ModelState("a", "Person", [2383                ("id", models.AutoField(primary_key=True)),2384                ("parent1", models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='children'))2385            ])2386            changes = self.get_changes([], [person])2387        # Right number/type of migrations?2388        self.assertNumberMigrations(changes, 'a', 1)2389        self.assertOperationTypes(changes, 'a', 0, ["CreateModel"])2390        self.assertMigrationDependencies(changes, 'a', 0, [])2391    @override_settings(AUTH_USER_MODEL='a.User')2392    def test_swappable_circular_multi_mti(self):2393        with isolate_lru_cache(apps.get_swappable_settings_name):2394            parent = ModelState('a', 'Parent', [2395                ('user', models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE))2396            ])2397            child = ModelState('a', 'Child', [], bases=('a.Parent',))2398            user = ModelState('a', 'User', [], bases=(AbstractBaseUser, 'a.Child'))2399            changes = self.get_changes([], [parent, child, user])2400        self.assertNumberMigrations(changes, 'a', 1)2401        self.assertOperationTypes(changes, 'a', 0, ['CreateModel', 'CreateModel', 'CreateModel', 'AddField'])2402    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',2403                side_effect=AssertionError("Should not have prompted for not null addition"))2404    def test_add_blank_textfield_and_charfield(self, mocked_ask_method):2405        """2406        #23405 - Adding a NOT NULL and blank `CharField` or `TextField`2407        without default should not prompt for a default.2408        """2409        changes = self.get_changes([self.author_empty], [self.author_with_biography_blank])2410        # Right number/type of migrations?2411        self.assertNumberMigrations(changes, 'testapp', 1)2412        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField"])2413        self.assertOperationAttributes(changes, 'testapp', 0, 0)2414    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition')2415    def test_add_non_blank_textfield_and_charfield(self, mocked_ask_method):2416        """2417        #23405 - Adding a NOT NULL and non-blank `CharField` or `TextField`2418        without default should prompt for a default.2419        """2420        changes = self.get_changes([self.author_empty], [self.author_with_biography_non_blank])2421        self.assertEqual(mocked_ask_method.call_count, 2)2422        # Right number/type of migrations?2423        self.assertNumberMigrations(changes, 'testapp', 1)2424        self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField"])2425        self.assertOperationAttributes(changes, 'testapp', 0, 0)2426    def test_mti_inheritance_model_removal(self):2427        Animal = ModelState('app', 'Animal', [2428            ("id", models.AutoField(primary_key=True)),2429        ])2430        Dog = ModelState('app', 'Dog', [], bases=('app.Animal',))2431        changes = self.get_changes([Animal, Dog], [Animal])2432        self.assertNumberMigrations(changes, 'app', 1)2433        self.assertOperationTypes(changes, 'app', 0, ['DeleteModel'])2434        self.assertOperationAttributes(changes, 'app', 0, 0, name='Dog')2435    def test_add_model_with_field_removed_from_base_model(self):2436        """2437        Removing a base field takes place before adding a new inherited model2438        that has a field with the same name.2439        """2440        before = [2441            ModelState('app', 'readable', [2442                ('id', models.AutoField(primary_key=True)),2443                ('title', models.CharField(max_length=200)),2444            ]),2445        ]2446        after = [2447            ModelState('app', 'readable', [2448                ('id', models.AutoField(primary_key=True)),2449            ]),2450            ModelState('app', 'book', [2451                ('title', models.CharField(max_length=200)),2452            ], bases=('app.readable',)),2453        ]2454        changes = self.get_changes(before, after)2455        self.assertNumberMigrations(changes, 'app', 1)2456        self.assertOperationTypes(changes, 'app', 0, ['RemoveField', 'CreateModel'])2457        self.assertOperationAttributes(changes, 'app', 0, 0, name='title', model_name='readable')2458        self.assertOperationAttributes(changes, 'app', 0, 1, name='book')2459class MigrationSuggestNameTests(SimpleTestCase):2460    def test_single_operation(self):2461        class Migration(migrations.Migration):2462            operations = [migrations.CreateModel('Person', fields=[])]2463        migration = Migration('0001_initial', 'test_app')2464        self.assertEqual(migration.suggest_name(), 'person')2465        class Migration(migrations.Migration):2466            operations = [migrations.DeleteModel('Person')]2467        migration = Migration('0002_initial', 'test_app')2468        self.assertEqual(migration.suggest_name(), 'delete_person')...script.js
Source:script.js  
1<<<<<<< HEAD2document.addEventListener('DOMContentLoaded', function(){3	let precent = document.getElementsByClassName('precent'),4	    dolar = document.getElementsByClassName('dolar'),5	    switch_in = document.getElementsByClassName('switch'),6	    toggle = document.getElementsByClassName('toggle-bg'),7        input_price = document.getElementsByClassName('input_price'),8        hour_change = document.getElementsByClassName('hour_change'),9        day_change = document.getElementsByClassName('day_change'),10        week_change = document.getElementsByClassName('week_change'),11        month_change = document.getElementsByClassName('month_change'),12        select = document.getElementById('option_id');13        14  	var promise = $.ajax({15      url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'16    }).then(function (result) {  17       18         for (var i = 0; i < input_price.length; i++) {19           input_price[i].value = '$' + result.ask;20        };21        for (var i = 0; i < hour_change.length; i++) {22            hour_change[i].value = result.changes.percent.hour + '%';23        }24         for (var i = 0; i < day_change.length; i++) {25           day_change[i].value = result.changes.percent.day + '%';26        }27         for (var i = 0; i < week_change.length; i++) { 28           week_change[i].value = result.changes.percent.week + '%';29        }30        for (var i = 0; i < month_change.length; i++) {31           month_change[i].value = result.changes.percent.month + '%';32        };33          $('#option_id').change(function(){34          val = select.value;35         36          if (val == "USD"){37              var promise = $.ajax({38                    url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'39                  }).then(function (result) { 40              precent[0].addEventListener('change',  function(){41                          switch_in[0].classList.remove('power');42                          switch_in[0].classList.add('off');43                          toggle[0].style.background = "#3aa5da";44                          hour_change[0].value = result.changes.percent.hour + '%';45                          day_change[0].value = result.changes.percent.day + '%';46                          week_change[0].value = result.changes.percent.week + '%';47                          month_change[0].value = result.changes.percent.month + '%';48                      });49                       dolar[0].addEventListener('change',  function(){50                          switch_in[0].classList.remove('off');51                          switch_in[0].classList.add('power');52                          toggle[0].style.background = "#b0b1b2";53                          hour_change[0].value = '$' + result.changes.price.hour;54                          day_change[0].value = '$' + result.changes.price.day;55                          week_change[0].value = '$' + result.changes.price.week;56                          month_change[0].value = '$' + result.changes.price.month;57                      });58                        precent[1].addEventListener('change',  function(){59                          switch_in[1].classList.add('off');60                          switch_in[1].classList.remove('power');61                          toggle[1].style.background = "#3aa5da";62                          hour_change[1].value = result.changes.percent.hour + '%';63                          day_change[1].value = result.changes.percent.day + '%';64                          week_change[1].value = result.changes.percent.week + '%';65                          month_change[1].value = result.changes.percent.month + '%';66                      });67                        dolar[1].addEventListener('change',  function(){68                          switch_in[1].classList.add('power');69                          switch_in[1].classList.remove('off');70                          toggle[1].style.background = "#b0b1b2";71                          hour_change[1].value = '$' + result.changes.price.hour;72                          day_change[1].value = '$' + result.changes.price.day;73                          week_change[1].value = '$' + result.changes.price.week;74                          month_change[1].value = '$' + result.changes.price.month;75                      });76                        precent[2].addEventListener('change',  function(){77                          switch_in[2].classList.add('off');78                          switch_in[2].classList.remove('power');79                          toggle[2].style.background = "#3aa5da";80                          hour_change[2].value = result.changes.percent.hour + '%';81                          day_change[2].value = result.changes.percent.day + '%';82                          week_change[2].value = result.changes.percent.week + '%';83                          month_change[2].value = result.changes.percent.month + '%';84                      });85                        dolar[2].addEventListener('change',  function(){86                          switch_in[2].classList.add('power');87                          switch_in[2].classList.remove('off');88                          toggle[2].style.background = "#b0b1b2";89                          hour_change[2].value = '$' + result.changes.price.hour;90                          day_change[2].value = '$' + result.changes.price.day;91                          week_change[2].value = '$' + result.changes.price.week;92                          month_change[2].value = '$' + result.changes.price.month;93                      });94               95                   96                  });97                98          };99          if(val == "RUB"){100              var promise = $.ajax({101                    url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'102                  }).then(function (result) { 103              precent[0].addEventListener('change',  function(){104                          switch_in[0].classList.remove('power');105                          switch_in[0].classList.add('off');106                          toggle[0].style.background = "#3aa5da";107                          hour_change[0].value = result.changes.percent.hour + '%';108                          day_change[0].value = result.changes.percent.day + '%';109                          week_change[0].value = result.changes.percent.week + '%';110                          month_change[0].value = result.changes.percent.month + '%';111                      });112                       dolar[0].addEventListener('change',  function(){113                          switch_in[0].classList.remove('off');114                          switch_in[0].classList.add('power');115                          toggle[0].style.background = "#b0b1b2";116                          hour_change[0].value = 'â½' + 67*result.changes.price.hour;117                          day_change[0].value = 'â½' + 67*result.changes.price.day;118                          week_change[0].value = 'â½' + 67*result.changes.price.week;119                          month_change[0].value = 'â½' + 67*result.changes.price.month;120                      });121                        precent[1].addEventListener('change',  function(){122                          switch_in[1].classList.add('off');123                          switch_in[1].classList.remove('power');124                          toggle[1].style.background = "#3aa5da";125                          hour_change[1].value = result.changes.percent.hour + '%';126                          day_change[1].value = result.changes.percent.day + '%';127                          week_change[1].value = result.changes.percent.week + '%';128                          month_change[1].value = result.changes.percent.month + '%';129                      });130                        dolar[1].addEventListener('change',  function(){131                          switch_in[1].classList.add('power');132                          switch_in[1].classList.remove('off');133                          toggle[1].style.background = "#b0b1b2";134                          hour_change[1].value = 'â½' + result.changes.price.hour;135                          day_change[1].value = 'â½' + result.changes.price.day;136                          week_change[1].value = 'â½' + result.changes.price.week;137                          month_change[1].value = 'â½' + result.changes.price.month;138                      });139                        precent[2].addEventListener('change',  function(){140                          switch_in[2].classList.add('off');141                          switch_in[2].classList.remove('power');142                          toggle[2].style.background = "#3aa5da";143                          hour_change[2].value = result.changes.percent.hour + '%';144                          day_change[2].value = result.changes.percent.day + '%';145                          week_change[2].value = result.changes.percent.week + '%';146                          month_change[2].value = result.changes.percent.month + '%';147                      });148                        dolar[2].addEventListener('change',  function(){149                          switch_in[2].classList.add('power');150                          switch_in[2].classList.remove('off');151                          toggle[2].style.background = "#b0b1b2";152                          hour_change[2].value = 'â½' + result.changes.price.hour;153                          day_change[2].value = 'â½' + result.changes.price.day;154                          week_change[2].value = 'â½' + result.changes.price.week;155                          month_change[2].value = 'â½' + result.changes.price.month;156                      });157               158                   159                  });160          };161          if(val == "GBR"){162                 var promise = $.ajax({163                       url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'164                     }).then(function (result) { 165                 precent[0].addEventListener('change',  function(){166                             switch_in[0].classList.remove('power');167                             switch_in[0].classList.add('off');168                             toggle[0].style.background = "#3aa5da";169                             hour_change[0].value = result.changes.percent.hour + '%';170                             day_change[0].value = result.changes.percent.day + '%';171                             week_change[0].value = result.changes.percent.week + '%';172                             month_change[0].value = result.changes.percent.month + '%';173                         });174                          dolar[0].addEventListener('change',  function(){175                             switch_in[0].classList.remove('off');176                             switch_in[0].classList.add('power');177                             toggle[0].style.background = "#b0b1b2";178                             hour_change[0].value = '£' + 1.30*67*result.changes.price.hour;179                             day_change[0].value = '£' + 1.30*67*result.changes.price.day;180                             week_change[0].value = '£' + 1.30*67*result.changes.price.week;181                             month_change[0].value = '£' + 1.30*67*result.changes.price.month;182                         });183                           precent[1].addEventListener('change',  function(){184                             switch_in[1].classList.add('off');185                             switch_in[1].classList.remove('power');186                             toggle[1].style.background = "#3aa5da";187                             hour_change[1].value = result.changes.percent.hour + '%';188                             day_change[1].value = result.changes.percent.day + '%';189                             week_change[1].value = result.changes.percent.week + '%';190                             month_change[1].value = result.changes.percent.month + '%';191                         });192                           dolar[1].addEventListener('change',  function(){193                             switch_in[1].classList.add('power');194                             switch_in[1].classList.remove('off');195                             toggle[1].style.background = "#b0b1b2";196                             hour_change[1].value = '£' + 1.30*result.changes.price.hour;197                             day_change[1].value = '£' + 1.30*result.changes.price.day;198                             week_change[1].value = '£' + 1.30*result.changes.price.week;199                             month_change[1].value = '£' + 1.30*result.changes.price.month;200                         });201                           precent[2].addEventListener('change',  function(){202                             switch_in[2].classList.add('off');203                             switch_in[2].classList.remove('power');204                             toggle[2].style.background = "#3aa5da";205                             hour_change[2].value = result.changes.percent.hour + '%';206                             day_change[2].value = result.changes.percent.day + '%';207                             week_change[2].value = result.changes.percent.week + '%';208                             month_change[2].value = result.changes.percent.month + '%';209                         });210                           dolar[2].addEventListener('change',  function(){211                             switch_in[2].classList.add('power');212                             switch_in[2].classList.remove('off');213                             toggle[2].style.background = "#b0b1b2";214                             hour_change[2].value = '£' + 1.30*result.changes.price.hour;215                             day_change[2].value = '£' + 1.30*result.changes.price.day;216                             week_change[2].value = '£' + 1.30*result.changes.price.week;217                             month_change[2].value = '£' + 1.30*result.changes.price.month;218                         });219                  220                      221                     });222             };223         224        });225        precent[0].addEventListener('change',  function(){226            switch_in[0].classList.remove('power');227            switch_in[0].classList.add('off');228            toggle[0].style.background = "#3aa5da";229            hour_change[0].value = result.changes.percent.hour + '%';230            day_change[0].value = result.changes.percent.day + '%';231            week_change[0].value = result.changes.percent.week + '%';232            month_change[0].value = result.changes.percent.month + '%';233        });234         dolar[0].addEventListener('change',  function(){235            switch_in[0].classList.remove('off');236            switch_in[0].classList.add('power');237            toggle[0].style.background = "#b0b1b2";238            hour_change[0].value = '$' + result.changes.price.hour;239            day_change[0].value = '$' + result.changes.price.day;240            week_change[0].value = '$' + result.changes.price.week;241            month_change[0].value = '$' + result.changes.price.month;242        });243          precent[1].addEventListener('change',  function(){244            switch_in[1].classList.add('off');245            switch_in[1].classList.remove('power');246            toggle[1].style.background = "#3aa5da";247            hour_change[1].value = result.changes.percent.hour + '%';248            day_change[1].value = result.changes.percent.day + '%';249            week_change[1].value = result.changes.percent.week + '%';250            month_change[1].value = result.changes.percent.month + '%';251        });252          dolar[1].addEventListener('change',  function(){253            switch_in[1].classList.add('power');254            switch_in[1].classList.remove('off');255            toggle[1].style.background = "#b0b1b2";256            hour_change[1].value = '$' + result.changes.price.hour;257            day_change[1].value = '$' + result.changes.price.day;258            week_change[1].value = '$' + result.changes.price.week;259            month_change[1].value = '$' + result.changes.price.month;260        });261          precent[2].addEventListener('change',  function(){262            switch_in[2].classList.add('off');263            switch_in[2].classList.remove('power');264            toggle[2].style.background = "#3aa5da";265            hour_change[2].value = result.changes.percent.hour + '%';266            day_change[2].value = result.changes.percent.day + '%';267            week_change[2].value = result.changes.percent.week + '%';268            month_change[2].value = result.changes.percent.month + '%';269        });270          dolar[2].addEventListener('change',  function(){271            switch_in[2].classList.add('power');272            switch_in[2].classList.remove('off');273            toggle[2].style.background = "#b0b1b2";274            hour_change[2].value = '$' + result.changes.price.hour;275            day_change[2].value = '$' + result.changes.price.day;276            week_change[2].value = '$' + result.changes.price.week;277            month_change[2].value = '$' + result.changes.price.month;278        });279     280    }).catch(function (err) {281      console.log('err', err)282    })283	284});285=======286document.addEventListener('DOMContentLoaded', function(){287	let precent = document.getElementsByClassName('precent'),288	    dolar = document.getElementsByClassName('dolar'),289	    switch_in = document.getElementsByClassName('switch'),290	    toggle = document.getElementsByClassName('toggle-bg'),291        input_price = document.getElementsByClassName('input_price'),292        hour_change = document.getElementsByClassName('hour_change'),293        day_change = document.getElementsByClassName('day_change'),294        week_change = document.getElementsByClassName('week_change'),295        month_change = document.getElementsByClassName('month_change'),296        select = document.getElementById('option_id');297        298  	var promise = $.ajax({299      url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'300    }).then(function (result) {  301       302         for (var i = 0; i < input_price.length; i++) {303           input_price[i].value = '$' + result.ask;304        };305        for (var i = 0; i < hour_change.length; i++) {306            hour_change[i].value = result.changes.percent.hour + '%';307        }308         for (var i = 0; i < day_change.length; i++) {309           day_change[i].value = result.changes.percent.day + '%';310        }311         for (var i = 0; i < week_change.length; i++) { 312           week_change[i].value = result.changes.percent.week + '%';313        }314        for (var i = 0; i < month_change.length; i++) {315           month_change[i].value = result.changes.percent.month + '%';316        };317          $('#option_id').change(function(){318          val = select.value;319         320          if (val == "USD"){321              var promise = $.ajax({322                    url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'323                  }).then(function (result) { 324              precent[0].addEventListener('change',  function(){325                          switch_in[0].classList.remove('power');326                          switch_in[0].classList.add('off');327                          toggle[0].style.background = "#3aa5da";328                          hour_change[0].value = result.changes.percent.hour + '%';329                          day_change[0].value = result.changes.percent.day + '%';330                          week_change[0].value = result.changes.percent.week + '%';331                          month_change[0].value = result.changes.percent.month + '%';332                      });333                       dolar[0].addEventListener('change',  function(){334                          switch_in[0].classList.remove('off');335                          switch_in[0].classList.add('power');336                          toggle[0].style.background = "#b0b1b2";337                          hour_change[0].value = '$' + result.changes.price.hour;338                          day_change[0].value = '$' + result.changes.price.day;339                          week_change[0].value = '$' + result.changes.price.week;340                          month_change[0].value = '$' + result.changes.price.month;341                      });342                        precent[1].addEventListener('change',  function(){343                          switch_in[1].classList.add('off');344                          switch_in[1].classList.remove('power');345                          toggle[1].style.background = "#3aa5da";346                          hour_change[1].value = result.changes.percent.hour + '%';347                          day_change[1].value = result.changes.percent.day + '%';348                          week_change[1].value = result.changes.percent.week + '%';349                          month_change[1].value = result.changes.percent.month + '%';350                      });351                        dolar[1].addEventListener('change',  function(){352                          switch_in[1].classList.add('power');353                          switch_in[1].classList.remove('off');354                          toggle[1].style.background = "#b0b1b2";355                          hour_change[1].value = '$' + result.changes.price.hour;356                          day_change[1].value = '$' + result.changes.price.day;357                          week_change[1].value = '$' + result.changes.price.week;358                          month_change[1].value = '$' + result.changes.price.month;359                      });360                        precent[2].addEventListener('change',  function(){361                          switch_in[2].classList.add('off');362                          switch_in[2].classList.remove('power');363                          toggle[2].style.background = "#3aa5da";364                          hour_change[2].value = result.changes.percent.hour + '%';365                          day_change[2].value = result.changes.percent.day + '%';366                          week_change[2].value = result.changes.percent.week + '%';367                          month_change[2].value = result.changes.percent.month + '%';368                      });369                        dolar[2].addEventListener('change',  function(){370                          switch_in[2].classList.add('power');371                          switch_in[2].classList.remove('off');372                          toggle[2].style.background = "#b0b1b2";373                          hour_change[2].value = '$' + result.changes.price.hour;374                          day_change[2].value = '$' + result.changes.price.day;375                          week_change[2].value = '$' + result.changes.price.week;376                          month_change[2].value = '$' + result.changes.price.month;377                      });378               379                   380                  });381                382          };383          if(val == "RUB"){384              var promise = $.ajax({385                    url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'386                  }).then(function (result) { 387              precent[0].addEventListener('change',  function(){388                          switch_in[0].classList.remove('power');389                          switch_in[0].classList.add('off');390                          toggle[0].style.background = "#3aa5da";391                          hour_change[0].value = result.changes.percent.hour + '%';392                          day_change[0].value = result.changes.percent.day + '%';393                          week_change[0].value = result.changes.percent.week + '%';394                          month_change[0].value = result.changes.percent.month + '%';395                      });396                       dolar[0].addEventListener('change',  function(){397                          switch_in[0].classList.remove('off');398                          switch_in[0].classList.add('power');399                          toggle[0].style.background = "#b0b1b2";400                          hour_change[0].value = 'â½' + 67*result.changes.price.hour;401                          day_change[0].value = 'â½' + 67*result.changes.price.day;402                          week_change[0].value = 'â½' + 67*result.changes.price.week;403                          month_change[0].value = 'â½' + 67*result.changes.price.month;404                      });405                        precent[1].addEventListener('change',  function(){406                          switch_in[1].classList.add('off');407                          switch_in[1].classList.remove('power');408                          toggle[1].style.background = "#3aa5da";409                          hour_change[1].value = result.changes.percent.hour + '%';410                          day_change[1].value = result.changes.percent.day + '%';411                          week_change[1].value = result.changes.percent.week + '%';412                          month_change[1].value = result.changes.percent.month + '%';413                      });414                        dolar[1].addEventListener('change',  function(){415                          switch_in[1].classList.add('power');416                          switch_in[1].classList.remove('off');417                          toggle[1].style.background = "#b0b1b2";418                          hour_change[1].value = 'â½' + result.changes.price.hour;419                          day_change[1].value = 'â½' + result.changes.price.day;420                          week_change[1].value = 'â½' + result.changes.price.week;421                          month_change[1].value = 'â½' + result.changes.price.month;422                      });423                        precent[2].addEventListener('change',  function(){424                          switch_in[2].classList.add('off');425                          switch_in[2].classList.remove('power');426                          toggle[2].style.background = "#3aa5da";427                          hour_change[2].value = result.changes.percent.hour + '%';428                          day_change[2].value = result.changes.percent.day + '%';429                          week_change[2].value = result.changes.percent.week + '%';430                          month_change[2].value = result.changes.percent.month + '%';431                      });432                        dolar[2].addEventListener('change',  function(){433                          switch_in[2].classList.add('power');434                          switch_in[2].classList.remove('off');435                          toggle[2].style.background = "#b0b1b2";436                          hour_change[2].value = 'â½' + result.changes.price.hour;437                          day_change[2].value = 'â½' + result.changes.price.day;438                          week_change[2].value = 'â½' + result.changes.price.week;439                          month_change[2].value = 'â½' + result.changes.price.month;440                      });441               442                   443                  });444          };445          if(val == "GBR"){446                 var promise = $.ajax({447                       url: 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD'448                     }).then(function (result) { 449                 precent[0].addEventListener('change',  function(){450                             switch_in[0].classList.remove('power');451                             switch_in[0].classList.add('off');452                             toggle[0].style.background = "#3aa5da";453                             hour_change[0].value = result.changes.percent.hour + '%';454                             day_change[0].value = result.changes.percent.day + '%';455                             week_change[0].value = result.changes.percent.week + '%';456                             month_change[0].value = result.changes.percent.month + '%';457                         });458                          dolar[0].addEventListener('change',  function(){459                             switch_in[0].classList.remove('off');460                             switch_in[0].classList.add('power');461                             toggle[0].style.background = "#b0b1b2";462                             hour_change[0].value = '£' + 1.30*67*result.changes.price.hour;463                             day_change[0].value = '£' + 1.30*67*result.changes.price.day;464                             week_change[0].value = '£' + 1.30*67*result.changes.price.week;465                             month_change[0].value = '£' + 1.30*67*result.changes.price.month;466                         });467                           precent[1].addEventListener('change',  function(){468                             switch_in[1].classList.add('off');469                             switch_in[1].classList.remove('power');470                             toggle[1].style.background = "#3aa5da";471                             hour_change[1].value = result.changes.percent.hour + '%';472                             day_change[1].value = result.changes.percent.day + '%';473                             week_change[1].value = result.changes.percent.week + '%';474                             month_change[1].value = result.changes.percent.month + '%';475                         });476                           dolar[1].addEventListener('change',  function(){477                             switch_in[1].classList.add('power');478                             switch_in[1].classList.remove('off');479                             toggle[1].style.background = "#b0b1b2";480                             hour_change[1].value = '£' + 1.30*result.changes.price.hour;481                             day_change[1].value = '£' + 1.30*result.changes.price.day;482                             week_change[1].value = '£' + 1.30*result.changes.price.week;483                             month_change[1].value = '£' + 1.30*result.changes.price.month;484                         });485                           precent[2].addEventListener('change',  function(){486                             switch_in[2].classList.add('off');487                             switch_in[2].classList.remove('power');488                             toggle[2].style.background = "#3aa5da";489                             hour_change[2].value = result.changes.percent.hour + '%';490                             day_change[2].value = result.changes.percent.day + '%';491                             week_change[2].value = result.changes.percent.week + '%';492                             month_change[2].value = result.changes.percent.month + '%';493                         });494                           dolar[2].addEventListener('change',  function(){495                             switch_in[2].classList.add('power');496                             switch_in[2].classList.remove('off');497                             toggle[2].style.background = "#b0b1b2";498                             hour_change[2].value = '£' + 1.30*result.changes.price.hour;499                             day_change[2].value = '£' + 1.30*result.changes.price.day;500                             week_change[2].value = '£' + 1.30*result.changes.price.week;501                             month_change[2].value = '£' + 1.30*result.changes.price.month;502                         });503                  504                      505                     });506             };507         508        });509        precent[0].addEventListener('change',  function(){510            switch_in[0].classList.remove('power');511            switch_in[0].classList.add('off');512            toggle[0].style.background = "#3aa5da";513            hour_change[0].value = result.changes.percent.hour + '%';514            day_change[0].value = result.changes.percent.day + '%';515            week_change[0].value = result.changes.percent.week + '%';516            month_change[0].value = result.changes.percent.month + '%';517        });518         dolar[0].addEventListener('change',  function(){519            switch_in[0].classList.remove('off');520            switch_in[0].classList.add('power');521            toggle[0].style.background = "#b0b1b2";522            hour_change[0].value = '$' + result.changes.price.hour;523            day_change[0].value = '$' + result.changes.price.day;524            week_change[0].value = '$' + result.changes.price.week;525            month_change[0].value = '$' + result.changes.price.month;526        });527          precent[1].addEventListener('change',  function(){528            switch_in[1].classList.add('off');529            switch_in[1].classList.remove('power');530            toggle[1].style.background = "#3aa5da";531            hour_change[1].value = result.changes.percent.hour + '%';532            day_change[1].value = result.changes.percent.day + '%';533            week_change[1].value = result.changes.percent.week + '%';534            month_change[1].value = result.changes.percent.month + '%';535        });536          dolar[1].addEventListener('change',  function(){537            switch_in[1].classList.add('power');538            switch_in[1].classList.remove('off');539            toggle[1].style.background = "#b0b1b2";540            hour_change[1].value = '$' + result.changes.price.hour;541            day_change[1].value = '$' + result.changes.price.day;542            week_change[1].value = '$' + result.changes.price.week;543            month_change[1].value = '$' + result.changes.price.month;544        });545          precent[2].addEventListener('change',  function(){546            switch_in[2].classList.add('off');547            switch_in[2].classList.remove('power');548            toggle[2].style.background = "#3aa5da";549            hour_change[2].value = result.changes.percent.hour + '%';550            day_change[2].value = result.changes.percent.day + '%';551            week_change[2].value = result.changes.percent.week + '%';552            month_change[2].value = result.changes.percent.month + '%';553        });554          dolar[2].addEventListener('change',  function(){555            switch_in[2].classList.add('power');556            switch_in[2].classList.remove('off');557            toggle[2].style.background = "#b0b1b2";558            hour_change[2].value = '$' + result.changes.price.hour;559            day_change[2].value = '$' + result.changes.price.day;560            week_change[2].value = '$' + result.changes.price.week;561            month_change[2].value = '$' + result.changes.price.month;562        });563     564    }).catch(function (err) {565      console.log('err', err)566    })567	568});...test_collection.py
Source:test_collection.py  
...4import voluptuous as vol5from homeassistant.helpers import collection, entity, entity_component, storage6from tests.common import flush_store7_LOGGER = logging.getLogger(__name__)8def track_changes(coll: collection.ObservableCollection):9    """Create helper to track changes in a collection."""10    changes = []11    async def listener(*args):12        changes.append(args)13    coll.async_add_listener(listener)14    return changes15class MockEntity(entity.Entity):16    """Entity that is config based."""17    def __init__(self, config):18        """Initialize entity."""19        self._config = config20    @property21    def unique_id(self):22        """Return unique ID of entity."""23        return self._config["id"]24    @property25    def name(self):26        """Return name of entity."""27        return self._config["name"]28    @property29    def state(self):30        """Return state of entity."""31        return self._config["state"]32    async def async_update_config(self, config):33        """Update entity config."""34        self._config = config35        self.async_write_ha_state()36class MockStorageCollection(collection.StorageCollection):37    """Mock storage collection."""38    async def _process_create_data(self, data: dict) -> dict:39        """Validate the config is valid."""40        if "name" not in data:41            raise ValueError("invalid")42        return data43    def _get_suggested_id(self, info: dict) -> str:44        """Suggest an ID based on the config."""45        return info["name"]46    async def _update_data(self, data: dict, update_data: dict) -> dict:47        """Return a new updated data object."""48        return {**data, **update_data}49def test_id_manager():50    """Test the ID manager."""51    id_manager = collection.IDManager()52    assert not id_manager.has_id("some_id")53    data = {}54    id_manager.add_collection(data)55    assert not id_manager.has_id("some_id")56    data["some_id"] = 157    assert id_manager.has_id("some_id")58    assert id_manager.generate_id("some_id") == "some_id_2"59    assert id_manager.generate_id("bla") == "bla"60async def test_observable_collection():61    """Test observerable collection."""62    coll = collection.ObservableCollection(_LOGGER)63    assert coll.async_items() == []64    coll.data["bla"] = 165    assert coll.async_items() == [1]66    changes = track_changes(coll)67    await coll.notify_changes(68        [collection.CollectionChangeSet("mock_type", "mock_id", {"mock": "item"})]69    )70    assert len(changes) == 171    assert changes[0] == ("mock_type", "mock_id", {"mock": "item"})72async def test_yaml_collection():73    """Test a YAML collection."""74    id_manager = collection.IDManager()75    coll = collection.YamlCollection(_LOGGER, id_manager)76    changes = track_changes(coll)77    await coll.async_load(78        [{"id": "mock-1", "name": "Mock 1"}, {"id": "mock-2", "name": "Mock 2"}]79    )80    assert id_manager.has_id("mock-1")81    assert id_manager.has_id("mock-2")82    assert len(changes) == 283    assert changes[0] == (84        collection.CHANGE_ADDED,85        "mock-1",86        {"id": "mock-1", "name": "Mock 1"},87    )88    assert changes[1] == (89        collection.CHANGE_ADDED,90        "mock-2",91        {"id": "mock-2", "name": "Mock 2"},92    )93    # Test loading new data. Mock 1 is updated, 2 removed, 3 added.94    await coll.async_load(95        [{"id": "mock-1", "name": "Mock 1-updated"}, {"id": "mock-3", "name": "Mock 3"}]96    )97    assert len(changes) == 598    assert changes[2] == (99        collection.CHANGE_UPDATED,100        "mock-1",101        {"id": "mock-1", "name": "Mock 1-updated"},102    )103    assert changes[3] == (104        collection.CHANGE_ADDED,105        "mock-3",106        {"id": "mock-3", "name": "Mock 3"},107    )108    assert changes[4] == (109        collection.CHANGE_REMOVED,110        "mock-2",111        {"id": "mock-2", "name": "Mock 2"},112    )113async def test_yaml_collection_skipping_duplicate_ids():114    """Test YAML collection skipping duplicate IDs."""115    id_manager = collection.IDManager()116    id_manager.add_collection({"existing": True})117    coll = collection.YamlCollection(_LOGGER, id_manager)118    changes = track_changes(coll)119    await coll.async_load(120        [{"id": "mock-1", "name": "Mock 1"}, {"id": "existing", "name": "Mock 2"}]121    )122    assert len(changes) == 1123    assert changes[0] == (124        collection.CHANGE_ADDED,125        "mock-1",126        {"id": "mock-1", "name": "Mock 1"},127    )128async def test_storage_collection(hass):129    """Test storage collection."""130    store = storage.Store(hass, 1, "test-data")131    await store.async_save(132        {133            "items": [134                {"id": "mock-1", "name": "Mock 1", "data": 1},135                {"id": "mock-2", "name": "Mock 2", "data": 2},136            ]137        }138    )139    id_manager = collection.IDManager()140    coll = MockStorageCollection(store, _LOGGER, id_manager)141    changes = track_changes(coll)142    await coll.async_load()143    assert id_manager.has_id("mock-1")144    assert id_manager.has_id("mock-2")145    assert len(changes) == 2146    assert changes[0] == (147        collection.CHANGE_ADDED,148        "mock-1",149        {"id": "mock-1", "name": "Mock 1", "data": 1},150    )151    assert changes[1] == (152        collection.CHANGE_ADDED,153        "mock-2",154        {"id": "mock-2", "name": "Mock 2", "data": 2},155    )156    item = await coll.async_create_item({"name": "Mock 3"})157    assert item["id"] == "mock_3"158    assert len(changes) == 3159    assert changes[2] == (160        collection.CHANGE_ADDED,161        "mock_3",162        {"id": "mock_3", "name": "Mock 3"},163    )164    updated_item = await coll.async_update_item("mock-2", {"name": "Mock 2 updated"})165    assert id_manager.has_id("mock-2")166    assert updated_item == {"id": "mock-2", "name": "Mock 2 updated", "data": 2}167    assert len(changes) == 4168    assert changes[3] == (collection.CHANGE_UPDATED, "mock-2", updated_item)169    with pytest.raises(ValueError):170        await coll.async_update_item("mock-2", {"id": "mock-2-updated"})171    assert id_manager.has_id("mock-2")172    assert not id_manager.has_id("mock-2-updated")173    assert len(changes) == 4174    await flush_store(store)175    assert await storage.Store(hass, 1, "test-data").async_load() == {176        "items": [177            {"id": "mock-1", "name": "Mock 1", "data": 1},178            {"id": "mock-2", "name": "Mock 2 updated", "data": 2},179            {"id": "mock_3", "name": "Mock 3"},180        ]181    }182async def test_attach_entity_component_collection(hass):183    """Test attaching collection to entity component."""184    ent_comp = entity_component.EntityComponent(_LOGGER, "test", hass)185    coll = collection.ObservableCollection(_LOGGER)186    collection.sync_entity_lifecycle(hass, "test", "test", ent_comp, coll, MockEntity)187    await coll.notify_changes(188        [189            collection.CollectionChangeSet(190                collection.CHANGE_ADDED,191                "mock_id",192                {"id": "mock_id", "state": "initial", "name": "Mock 1"},193            )194        ],195    )196    assert hass.states.get("test.mock_1").name == "Mock 1"197    assert hass.states.get("test.mock_1").state == "initial"198    await coll.notify_changes(199        [200            collection.CollectionChangeSet(201                collection.CHANGE_UPDATED,202                "mock_id",203                {"id": "mock_id", "state": "second", "name": "Mock 1 updated"},204            )205        ],206    )207    assert hass.states.get("test.mock_1").name == "Mock 1 updated"208    assert hass.states.get("test.mock_1").state == "second"209    await coll.notify_changes(210        [collection.CollectionChangeSet(collection.CHANGE_REMOVED, "mock_id", None)],211    )212    assert hass.states.get("test.mock_1") is None213async def test_storage_collection_websocket(hass, hass_ws_client):214    """Test exposing a storage collection via websockets."""215    store = storage.Store(hass, 1, "test-data")216    coll = MockStorageCollection(store, _LOGGER)217    changes = track_changes(coll)218    collection.StorageCollectionWebsocket(219        coll,220        "test_item/collection",221        "test_item",222        {vol.Required("name"): str, vol.Required("immutable_string"): str},223        {vol.Optional("name"): str},224    ).async_setup(hass)225    client = await hass_ws_client(hass)226    # Create invalid227    await client.send_json(228        {229            "id": 1,230            "type": "test_item/collection/create",231            "name": 1,...Callout.js
Source:Callout.js  
1/**2 * @class Ext.chart.label.Callout3 * @extends Ext.draw.modifier.Modifier4 *5 * This is a modifier to place labels and callouts by additional attributes.6 */7Ext.define('Ext.chart.label.Callout', {8    extend: 'Ext.draw.modifier.Modifier',9    prepareAttributes: function (attr) {10        if (!attr.hasOwnProperty('calloutOriginal')) {11            attr.calloutOriginal = Ext.Object.chain(attr);12            // No __proto__, nor getPrototypeOf in IE8,13            // so manually saving a reference to 'attr' after chaining.14            attr.calloutOriginal.prototype = attr;15        }16        if (this._previous) {17            this._previous.prepareAttributes(attr.calloutOriginal);18        }19    },20    setAttrs: function (attr, changes) {21        var callout = attr.callout,22            origin = attr.calloutOriginal,23            bbox = attr.bbox.plain,24            width = (bbox.width || 0) + attr.labelOverflowPadding,25            height = (bbox.height || 0) + attr.labelOverflowPadding,26            dx, dy;27        if ('callout' in changes) {28            callout = changes.callout;29        }30        if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {31            var rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads,32                x = 'x' in changes ? (origin.x = changes.x) : origin.x,33                y = 'y' in changes ? (origin.y = changes.y) : origin.y,34                calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX,35                calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY,36                calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical,37                temp;38            // Normalize Rotations39            rotationRads %= Math.PI * 2;40            if (Math.cos(rotationRads) < 0) {41                rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);42            }43            if (rotationRads > Math.PI) {44                rotationRads -= Math.PI * 2;45            }46            if (calloutVertical) {47                rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;48                temp = width;49                width = height;50                height = temp;51            } else {52                rotationRads = rotationRads * (1 - callout);53            }54            changes.rotationRads = rotationRads;55            // Placing a label in the middle of a pie slice (x/y)56            // if callout doesn't exists (callout=0),57            // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).58            changes.x = x * (1 - callout) + calloutPlaceX * callout;59            changes.y = y * (1 - callout) + calloutPlaceY * callout;60            dx = calloutPlaceX - x;61            dy = calloutPlaceY - y;62            // Finding where the callout line intersects the bbox of the label63            // if it were to go to the center of the label,64            // and make that intersection point the end of the callout line.65            // Effectively, the end of the callout line traces label's bbox when chart is rotated.66            if (Math.abs(dy * width) > Math.abs(dx * height)) {67                // on top/bottom68                if (dy > 0) {69                    changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;70                    changes.calloutEndY = changes.y - (height / 2) * callout;71                } else {72                    changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;73                    changes.calloutEndY = changes.y + (height / 2) * callout;74                }75            } else {76                // on left/right77                if (dx > 0) {78                    changes.calloutEndX = changes.x - width / 2;79                    changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;80                } else {81                    changes.calloutEndX = changes.x + width / 2;82                    changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;83                }84            }85            // Since the length of the callout line is adjusted depending on the label's position86            // and dimensions, we hide the callout line if the length becomes negative.87            if (changes.calloutStartX && changes.calloutStartY) {88                changes.calloutHasLine =89                    (dx > 0 && changes.calloutStartX < changes.calloutEndX) ||90                    (dx <= 0 && changes.calloutStartX > changes.calloutEndX) ||91                    (dy > 0 && changes.calloutStartY < changes.calloutEndY) ||92                    (dy <= 0 && changes.calloutStartY > changes.calloutEndY);93            } else {94                changes.calloutHasLine = true;95            }96        }97        return changes;98    },99    pushDown: function (attr, changes) {100        changes = Ext.draw.modifier.Modifier.prototype.pushDown.call(this, attr.calloutOriginal, changes);101        return this.setAttrs(attr, changes);102    },103    popUp: function (attr, changes) {104        attr = attr.prototype;105        changes = this.setAttrs(attr, changes);106        if (this._next) {107            return this._next.popUp(attr, changes);108        } else {109            return Ext.apply(attr, changes);110        }111    }...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
