Набор тестов Django выдает ошибку в поле, которого нет в последних миграциях

Следующий простой тест,

from django.test import TestCase
    
    class TestSetup(TestCase):   
        def test_setUp(self):
            pdb.set_trace()
            # Code here deleted, it made no difference to the error.

выдает ошибки:

> Destroying old test database for alias 'default'... Traceback (most
> recent call last):   File
> "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 1957, in get_prep_value
>     return float(value) ValueError: could not convert string to float: ''
> 
> The above exception was the direct cause of the following exception:
> 
> Traceback (most recent call last):   File "manage.py", line 22, in
> <module>
>     main()   File "manage.py", line 18, in main
>     execute_from_command_line(sys.argv)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/__init__.py",
> line 442, in execute_from_command_line
>     utility.execute()   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/__init__.py",
> line 436, in execute
>     self.fetch_command(subcommand).run_from_argv(self.argv)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/commands/test.py",
> line 24, in run_from_argv
>     super().run_from_argv(argv)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/base.py",
> line 412, in run_from_argv
>     self.execute(*args, **cmd_options)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/base.py",
> line 458, in execute
>     output = self.handle(*args, **options)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/commands/test.py",
> line 68, in handle
>     failures = test_runner.run_tests(test_labels)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/test/runner.py",
> line 1054, in run_tests
>     old_config = self.setup_databases(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/test/runner.py",
> line 950, in setup_databases
>     return _setup_databases(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/test/utils.py",
> line 221, in setup_databases
>     connection.creation.create_test_db(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/creation.py",
> line 78, in create_test_db
>     call_command(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/__init__.py",
> line 194, in call_command
>     return command.execute(*args, **defaults)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/base.py",
> line 458, in execute
>     output = self.handle(*args, **options)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/base.py",
> line 106, in wrapper
>     res = handle_func(*args, **kwargs)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/core/management/commands/migrate.py",
> line 356, in handle
>     post_migrate_state = executor.migrate(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/migrations/executor.py",
> line 135, in migrate
>     state = self._migrate_all_forwards(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/migrations/executor.py",
> line 167, in _migrate_all_forwards
>     state = self.apply_migration(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/migrations/executor.py",
> line 252, in apply_migration
>     state = migration.apply(state, schema_editor)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/migrations/migration.py",
> line 132, in apply
>     operation.database_forwards(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/migrations/operations/fields.py",
> line 235, in database_forwards
>     schema_editor.alter_field(from_model, from_field, to_field)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 830, in alter_field
>     self._alter_field(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/postgresql/schema.py",
> line 287, in _alter_field
>     super()._alter_field(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 1025, in _alter_field
>     new_default = self.effective_default(new_field)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 429, in effective_default
>     return field.get_db_prep_save(self._effective_default(field), self.connection)   File
> "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 954, in get_db_prep_save
>     return self.get_db_prep_value(value, connection=connection, prepared=False)   File
> "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 947, in get_db_prep_value
>     value = self.get_prep_value(value)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 1959, in get_prep_value
>     raise e.__class__( ValueError: Field 'amount' expected a number but got ''.

Поле «сумма» существует, но в модели, которая не загружается для каких-либо тестов:

models.py
    
class Appointment(models.Model):
    amount = models.FloatField(blank=True, default=0.0)

Но на случай, если эта модель будет создана в процессе создания тестовой базы данных, я перенес «default=0.0» на «default='0.0'» (значение заключено в одинарные кавычки). Та же ошибка.

Я также попытался удалить тестовую базу данных postgresql вручную с помощью psql.

Я начинаю тест с

python manage.py test

а также указав имя_приложения:

python manage.py test giraffe

из каталога проекта.

Соответствующие миграции в последовательном порядке:

# Generated by Django 4.1.3 on 2023-01-10 22:58
from django.db import migrations, models   

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0014_appointment_notes'),
    ]
    operations = [
        migrations.AlterField(
            model_name='appointment',
            name='amount',
            field=models.FloatField(blank=True, default=''),
        ),
    ]

и

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0015_alter_appointment_amount'),
    ]
    operations = [
        migrations.AlterField(
            model_name='appointment',
            name='amount',
            field=models.FloatField(blank=True),
        ),
    ]

и

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0016_alter_appointment_amount'),
    ]
    operations = [
        migrations.AlterField(
            model_name='appointment',
            name='amount',
            field=models.FloatField(blank=True, default=0.0),
        ),
    ]

И некоторые более ранние миграции, относящиеся к модели с полем «сумма», но модели, которая была удалена, но все еще отображается в тестовой базе данных.

...migrations.CreateModel(
            name='UnreimbursedMileage',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('mileage_rate_category', models.CharField(choices=[('FB22a', 'Federal Business Rate, Qtr. 1&2 2022'), ('FB22b', 'Federal Business Rate, Qtr. 3&4, 2022'), ('FB23', 'Federal Business Rate 2023')], default='FB22a', max_length=5)),
                ('num_miles', models.FloatField(blank=True, default=0)),
                ('amount', models.FloatField(blank=True, null=True)),
                ('for_appointment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='giraffe.appointment')),
            ],
            options = {
                'verbose_name_plural': 'Unreimbursed Mileage',
            },
        ),

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0054_rename_to_do_next_time_todonexttime_and_more'),
    ]
    operations = [
        migrations.RemoveField(
            model_name='unreimbursedmileage',
            name='for_appointment',
        ),
        migrations.DeleteModel(
            name='Dummy',
        ),
        migrations.DeleteModel(
            name='UnreimbursedMileage',
        ),
    ]

Пожалуйста, предоставьте минимальный воспроизводимый пример, начните с публикации полной трассировки ошибок. Также поделитесь любыми написанными вами файлами/кодом, которые отображаются в обратной трассировке.

Abdul Aziz Barkat 04.04.2024 05:55

Поделитесь полной трассировкой.

willeM_ Van Onsem 04.04.2024 08:15

@AbdulAzizBarkat, добавлена ​​обратная связь.

Ron 04.04.2024 19:01

Покажите нам соответствующие файлы миграции вашего приложения. И, пожалуйста, отформатируйте эту обратную связь как блок кода, ее трудно прочитать как кавычку.

Abdul Aziz Barkat 04.04.2024 19:07

@AbdulAzizBarkat Существует семь миграций, которые могут быть применимы. Я могу включить их выше, но в самом первом 0001_inital упоминается модель с полем «сумма», которая больше не существует в более поздних миграциях. Но эта модель появляется в тестовой базе данных. Как мне заставить тест использовать только текущую версию базы данных?

Ron 04.04.2024 19:28

Миграции — это стратегия управления версиями вашей базы данных. Django применяет их последовательно к вашей базе данных (вероятно, проблема связана с одной из недавних миграций).

Abdul Aziz Barkat 04.04.2024 19:29

@AbdulAzizBarkat Я включил последние миграции, в которых упоминается поле «сумма». Но разве тот факт, что больше не используемая модель (UnReimbursedExpenses, также с полем «сумма») появляется в тестовой базе данных, не означает, что сбой произошел в более ранней миграции, до того, как эта модель была удалена?

Ron 04.04.2024 19:39

Виной всему миграция. Вы устанавливаете FloatField с default='' на нет, с default на default=0.0.

Chukwujiobi Canon 05.04.2024 06:12

@ChukwujiobiCanon Да, я знаю, но если исходные миграции прошли, что они и сделали, модульный тест должен пройти через них последовательно и использовать самое последнее состояние базы данных, и увидеть мое заявление в вопросе, в который я выполнил недавнюю миграцию. default='0.0', что привело к той же ошибке, поскольку не удалось перейти к более поздним состояниям базы данных. Насколько мне известно, невозможно удалить ранние миграции, оставив нетронутыми более поздние.

Ron 05.04.2024 23:47

Я думаю, что ответ может заключаться в том, чтобы просто выполнить сброс миграций, но сохранив данные. Если только кто-то не предложит другое решение.

Ron 06.04.2024 18:29

Запустите showmigrations и узнаем статус миграции.

Chukwujiobi Canon 06.04.2024 22:01

@ChukwujiobiCanon Их 76, плюс собственные миграции Django, их можно перечислить здесь; но я могу сказать, что рядом с ними у всех есть «[x]».

Ron 07.04.2024 02:24

Беглый просмотр даст мне лучшее представление о том, что происходит.

Chukwujiobi Canon 07.04.2024 04:38

@Рон, эти миграции не могут быть выполнены успешно. Гораздо более вероятно, что вы или кто-то из вашей команды просто подделал миграцию.

Abdul Aziz Barkat 07.04.2024 07:46

@Рон, я не вижу необходимости «сбрасывать миграции», почему бы тебе просто не сделать то, что упоминает Чуквуджиоби Кэнон? Просто отредактируйте неудачную миграцию, чтобы она больше не терпела неудачу. Замените default='' на default=0.0

Abdul Aziz Barkat 07.04.2024 07:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
15
151
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Обратная трассировка показывает, что это происходит при вызове alter_field.

> line 235, in database_forwards
>     schema_editor.alter_field(from_model, from_field, to_field)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 830, in alter_field
>     self._alter_field(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/postgresql/schema.py",
> line 287, in _alter_field
>     super()._alter_field(   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 1025, in _alter_field
>     new_default = self.effective_default(new_field)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/backends/base/schema.py",
> line 429, in effective_default
>     return field.get_db_prep_save(self._effective_default(field), self.connection)   File
> "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 954, in get_db_prep_save
>     return self.get_db_prep_value(value, connection=connection, prepared=False)   File
> "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 947, in get_db_prep_value
>     value = self.get_prep_value(value)   File "/var/www/ptech/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py",
> line 1959, in get_prep_value
>     raise e.__class__( ValueError: Field 'amount' expected a number but got ''.

Это часть инициализации объекта AlterField. Значит ошибка не в вашем models.py. Это происходит, когда вы пытаетесь создать объект migrations.AlterField.

Посмотрите на последнюю строку, чтобы увидеть, что именно пошло не так.

ValueError: Field 'amount' expected a number but got ''

Поэтому, когда вы создаете базу данных SQL, вам необходимо, чтобы тип поля соответствовал типу данных, который вы пытаетесь использовать. Вы указали полю «сумма» значение по умолчанию в базе данных строки при создании объекта AlterField. Но это FloatField, поэтому нам нужен поплавок.

Это проблемный код:

# Generated by Django 4.1.3 on 2023-01-10 22:58
from django.db import migrations, models   

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0014_appointment_notes'),
    ]
    operations = [
        migrations.AlterField(
            model_name='appointment',
            name='amount',
            field=models.FloatField(blank=True, default=''),
        ),
    ]

Полю «сумма» по умолчанию присваивается пустая строка, как и указано в ошибке. Нам нужен поплавок.

# Generated by Django 4.1.3 on 2023-01-10 22:58
from django.db import migrations, models   

class Migration(migrations.Migration):
    dependencies = [
        ('giraffe', '0014_appointment_notes'),
    ]
    operations = [
        migrations.AlterField(
            model_name='appointment',
            name='amount',
            field=models.FloatField(blank=True, default=0.0),
        ),
    ]

Аналогично, все ваши FloatField объекты должны иметь плавающее значение по умолчанию при их создании. Следующая миграция после 0014_appointment_notes, 0015_alter_appointment_amount не имеет значения по умолчанию для FloatField. Измените его, чтобы оно имело значение с плавающей запятой по умолчанию.

Измененный код:

class Migration(migrations.Migration):
dependencies = [
    ('giraffe', '0015_alter_appointment_amount'),
]
operations = [
    migrations.AlterField(
        model_name='appointment',
        name='amount',
        field=models.FloatField(blank=True, default=0.0),
    ),
]

Возможно, вы пытались это изменить, но ничего не произошло.

Как сказал Рон в разделе комментариев, решение состоит в том, чтобы сбросить миграцию. Это может быть сложно для вас, если вы не знакомы с тем, как работают миграции в Django. В каждой папке, где есть приложение Django, есть скрытая папка, то есть в каждой папке есть своя models.py. Это файл кэша, который создается после вызова makemigrations, чтобы ускорить выполнение команды migrate, поскольку обычно сами модели не изменяются. Добавление новой модели обновит этот кеш, а изменение — нет. Обязательно удалите каждую скрытую папку .migrations, а также все файлы и папки внутри с помощью команды Unix.

rm -r .migrations

Запуск команды migrate теперь автоматически запустит команду makemigrations, поскольку папка .migrations не существует и теперь ее необходимо создать для переноса базы данных.

Предыдущие модели, которые вы пытались удалить, больше не будут присутствовать при миграции базы данных, если вы убедитесь, что выполняете миграцию в новую базу данных, в которую еще не записана предыдущая модель.

а) Согласен, что это проблемы, но остается вопрос: как эти миграции успешно выполнялись при первоначальном создании? б) Да, сбрасывать миграцию рискованно, но Виктор Фрейтас объясняет это элегантно.

Ron 07.04.2024 03:17

Нет необходимости удалять все миграции плюс базу данных только для одной неудачной миграции. Неудачные миграции можно легко удалить или исправить. В качестве общего примечания слишком многие люди продолжают рекомендовать просто удалить миграции + БД, что на самом деле является реальной причиной того, что люди застревают со странной историей миграции по сравнению с реальным состоянием базы данных, пожалуйста, избегайте таких рекомендаций.

Abdul Aziz Barkat 07.04.2024 07:49

@Ron А) Как я объяснил, эти миграции были созданы потому, что они находились в файле кеша в папке «.migrations». Вам необходимо удалить эту папку, иначе вы будете продолжать выполнять ту же миграцию. Это не так уж и рискованно, я только что объяснил вам, как сбросить миграцию. Если вам не нужен был ответ и вы это поняли из статьи, то нет смысла назначать награду за вопрос.

Gracen Ownby 07.04.2024 22:44

Более того, я не совсем уверен, что @Abdul понимает, что происходит с Django/SQL. Идея состоит в том, чтобы использовать Django для создания идеальной таблицы SQL и отказаться от использования SQL вручную. Проблема возникает только тогда, когда вы удаляете базу данных и начинаете заново, не удаляя файлы кэша. Если вы удалите файлы кеша в «.migrations», тогда миграциям нечего будет делать, и их придется полностью перегенерировать с помощью кода. Это должны понимать младшие разработчики Django и участники этого форума.

Gracen Ownby 07.04.2024 22:53

По сути, это базовая тема, о которой вы можете прочитать. Вместо того, чтобы спорить о семантике, давайте просто найдем способ сбросить наши миграции и закрыть эту тему. Это сообщение помечается как дубликат существующего сообщения. Пожалуйста, обратитесь к существующей информации, такой как верхний комментарий к этому посту, который повторяет то, что я сказал, но ничего не говорит младшему программисту о папке «миграции». stackoverflow.com/questions/29253399/…

Gracen Ownby 07.04.2024 23:02

Следует отметить, что в рабочей среде вам может потребоваться добавить/удалить разделы таблицы для расширения функциональности и объема. В разработке мы генерируем таблицы с помощью Django. Не рекомендуется начинать с искаженными и отредактированными базами данных. Новый производственный сервер должен иметь идеальную таблицу SQL, которую можно будет добавить позже или изменить только в случае необходимости. В процессе разработки удалите базу данных и убедитесь, что вы создаете идеальную таблицу. Это мое мнение как разработчика, и вы можете делать то, что захотите.

Gracen Ownby 07.04.2024 23:10
Ответ принят как подходящий

Исходный вопрос в том виде, в котором он был опубликован, касается не того, как сбросить миграцию, а НЕ дубликата других вопросов SO о том, как сбросить миграцию. Речь идет о более глубоком понимании любых основных механизмов, которые могут позволить первоначальным миграциям выполняться безупречно, несмотря на ошибки в коде, показанном в вопросе, а затем потерпеть неудачу в модульном тесте. Все более 76 миграций в истории отображаются как успешно выполненные, если они указаны в списке «showmigrations», но теперь (правильно) завершаются неудачей при создании базы данных модульного теста.

Надежда на то, что исправление можно найти без сброса миграции.

Это как если бы данные не применялись к моделям, определенным в исходных миграциях - как если бы за них принималось слово определения полей модели - но тогда проблемы все равно могут возникнуть при фактическом создании экземпляра в тесте. Однако я не могу себе представить, чтобы в Django существовала такая дыра. В любом случае сценарий, описанный в вопросе, является хорошим аргументом в пользу TDD.

Попутно мы надеялись, что нюансы, которые могут возникнуть, учитывая последовательный, взаимозависимый характер миграций, станут понятнее для новых пользователей Django, которые прочитают это в будущем, т. е. это немного сложнее, чем «просто исправить». эта одна миграция».

Но суть в том, что какие бы ни были основные механизмы или действия, которые привели к проблеме, в настоящее время необходимо выполнить сброс миграций и создать новую первоначальную миграцию. Рекомендуемый метод для этого можно найти в разделе Как сбросить миграцию.

Сценарий 1 в этой статье точно описывает то, что вам здесь говорят, и представляет собой рекомендуемый способ сброса ваших миграций путем удаления папки миграций. В Django нет дыры, разработчики Django не допустили ошибки. Миграции будут продолжаться до тех пор, пока вы не удалите папку кэша миграций и не создадите заново папку миграций. Вот как работает Джанго. Если вы ищете более глубокое понимание, я рекомендую вам немного почитать, прежде чем вы начнете публиковать награды на Stack Overflow и отвечать себе за баллы.

Gracen Ownby 08.04.2024 23:16

Возможно, вам стоит немного почитать, прежде чем задавать вопросы о переполнении стека. Мы здесь для того, чтобы помогать решать проблемы, а не для того, чтобы спорить о семантике или придираться к нашим идеям после того, как мы поможем вам понять, в чем заключалась первоначальная проблема, и вы решите перейти к следующей проблеме. У вас есть 27 вопросов и 7 ответов. В какой-то момент вам следует начать участвовать в общедоступном форуме, особенно если вы настолько хорошо разбираетесь в этой «дыре» в Django, что никто другой даже не догадался.

Gracen Ownby 08.04.2024 23:32

Другие вопросы по теме