Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django "flush" management command fails because of "wagtailsearch_editorspick" table #1824

Closed
atbaker opened this issue Oct 13, 2015 · 29 comments
Assignees
Milestone

Comments

@atbaker
Copy link
Contributor

atbaker commented Oct 13, 2015

I'm gearing up to use Wagtail for a new project and ran into a small issue flushing a Postgres database.

With a stock Wagtail installation (no contrib apps), running python manage.py flush fails because of the wagtailsearch_editorspick table:

CommandError: Database sample_project couldn't be flushed. Possible reasons:
  * The database isn't running or isn't configured correctly.
  * At least one of the expected database tables doesn't exist.
  * The SQL was invalid.
Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.
The full error: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailcore_page".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.

After doing some digging, it looks like the key is that this migration leaves the wagtailsearch_editorspick table in the database for the contrib app to pick up:

https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailsearch/migrations/0003_remove_editors_pick.py

Relevant comment:

        # If wagtailsearchpromotions isn't installed, this table will remain
        # in the database unmanaged until it is. This could potentially happen
        # at any point in the future so it's important to keep this behaviour
        # even if we decide to squash these migrations.

I'm new to Wagtail (and really liking it so far!), so I'm not sure what the fix is here. If someone can point me in the right direction, though, I'm happy to put together a PR.

Thanks!

Additional background:

Installed apps:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Wagtail
    'wagtail.wagtailcore',
    'wagtail.wagtailadmin',
    'wagtail.wagtaildocs',
    'wagtail.wagtailsnippets',
    'wagtail.wagtailusers',
    'wagtail.wagtailimages',
    'wagtail.wagtailembeds',
    'wagtail.wagtailsearch',
    'wagtail.wagtailsites',
    'wagtail.wagtailredirects',
    'wagtail.wagtailforms',

    'compressor',
    'taggit',
    'modelcluster',

    # Other
    'rest_framework',
)

Requirements:

# Django (1.8 is a long-term support release)
Django==1.8.5

# Third-party libraries
djangorestframework==3.2.4
Pygments==2.0.2
psycopg2==2.6.1
pysaml2==3.0.0
requests==2.7.0
wagtail==1.1

# Testing and debugging
django-debug-toolbar==1.3.2
coverage==4.0
model-mommy==1.2.5
@gasman
Copy link
Collaborator

gasman commented Oct 13, 2015

It sounds like you've already pieced together the background to this, but just to fill in any gaps... the EditorsPick model was taken out of the wagtailsearch app in Wagtail 1.1, and moved to the optional wagtail.contrib.wagtailsearchpromotions app (where it was renamed to SearchPromotion). The current sequence of migrations was the best way we could find of keeping the database in a predictable state under all circumstances, while not dropping any data that previously existed in the EditorsPick table.

However, it relied on the assumption that having an unmanaged table sitting around in the database wouldn't cause any problems - and evidently that isn't the case, mainly due to it having a foreign key to Page.

(You're the second person to report this problem, incidentally - on the first occasion, it was someone who had upgraded from a pre-1.1 version. If this was something that only affected upgrades, then we could handle it in a reasonably satisfactory way by adding some detailed troubleshooting documentation in our upgrade notes. If it's a problem on fresh installs too, then that's rather more annoying...)

This is clearly something that's going to take a lot of careful planning to solve. Perhaps the way to tackle it is to divide it into two steps:

  1. What is the preferred database state for:
  • someone who has never used the editor's pick / search promotions feature
  • someone who currently has wagtail.contrib.wagtailsearchpromotions installed
  • someone who used editor's picks prior to 1.1, but does not currently have wagtail.contrib.wagtailsearchpromotions installed
  1. How do we get from here to there? (Where "here" = all the possible database states that could exist right now, including pre-1.1 installations, post-1.1 installations with an unmanaged EditorsPick table, and post-1.1 installations running the contrib module)

I don't currently have the answers to the above, and I'm not even sure that this is a fully solved problem within Django. (To give a sort of scaled-down version of the kind of problem we're dealing with: what's the official Django-recommended procedure for removing an app from INSTALLED_APPS and ensuring that all of its tables get garbage-collected?)

@gasman gasman added this to the 1.3 milestone Oct 26, 2015
@gasman gasman modified the milestones: 1.4, 1.3 Dec 15, 2015
@sumittada
Copy link

I am facing the same bug, the relevant versions from pip-freeze are:
beautifulsoup4==4.4.1
Django==1.9.1
django-appconf==1.0.1
django-compressor==1.6
django-modelcluster==1.1
django-taggit==0.17.6
django-treebeard==3.0
djangorestframework==3.3.2
gunicorn==19.4.5
html5lib==0.9999999
Pillow==3.1.0
psycopg2==2.6.1
pytz==2015.7
six==1.10.0
Unidecode==0.4.18
wagtail==1.3.1
wheel==0.24.0
Willow==0.2.2

@peterlauri
Copy link

We faced the same problem, but in our case it was just that our test suite started to fail when integrating wagtail into our existing django project. Here is a workaround for someone that want to use wagtail, but not include wagtailsearchpromotions. Please note that this might/will cause problems if you later on want to use wagtailsearchpromotions, as the wagtailsearch_editorspick no longer exists, and the wagtailsearchpromotions initial migration will/might fail. Note that I haven't tried that path, but I suspect it might fail.

This migration needs to be added to your project. Please note the danger of this migration as well, in case you already have data in wagtailsearch_editorspick as it will remove related rows.

class Migration(migrations.Migration):
    dependencies = [
        ('publicpages', '0001_initial'),  # change this to fit your need
        ('wagtailsearch', '0003_remove_editors_pick'),
    ]

    operations = [
        migrations.RunSQL('DROP TABLE IF EXISTS wagtailsearch_editorspick CASCADE;'),
    ]

@aaugustin
Copy link
Contributor

I'm hitting the same issue when running tests with pytest and pytest-django.

@aaugustin
Copy link
Contributor

To answer the questions above:

  • I believe this happens as soon as you integrate wagtail into an existing project having tests. It must be quite common.
  • Django doesn't provide an easy way to move a table across apps. AFAIK the safe option is to create a new table in the new app, copy the data with a data migration, then drop the old table in the old app.

@aaugustin
Copy link
Contributor

To minimize the damage at this point, a solution might be to remove the FK constraint on the wagtailsearch_editorspick table in an additional migration in the wagtailsearch app, turning the field into a simple integer field, and to restore that constraint in the wagtailsearchpromotions migrations.

@atbaker
Copy link
Contributor Author

atbaker commented May 6, 2016

FWIW, this issue isn't a blocker for me anymore. We ended up not needing to run manage.py flush regularly.

But I endorse @aaugustin's proposed solution - it seems like a good way out of this pickle.

@gasman gasman modified the milestones: real-soon-now, 1.5 May 10, 2016
@pratyushsharma
Copy link

Any updates on the implementation, I am having trouble running tests with fixtures?

@glassresistor
Copy link
Contributor

I'm having this same issue whenever I try and run tests here is my output. This is making it so I can't run any tests that work with Page models.

I really need a fix for this or we might have to stop using wagtail

======================================================================
ERROR: test_init_without_project (biz_content.tests.test_forms.CheckListFormTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/mikela/code/syracuse_biz_portal/biz_content/tests/test_forms.py", line 17, in test_init_without_project
    self.assertQuerysetEqual(form_qs, qs)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/test/testcases.py", line 966, in assertQuerysetEqual
    values = list(values)
TypeError: 'DeferringRelatedManager' object is not iterable

======================================================================
ERROR: test_init_without_project (biz_content.tests.test_forms.CheckListFormTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
psycopg2.NotSupportedError: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailsearch_query".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/core/management/commands/flush.py", line 67, in handle
    cursor.execute(sql)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/utils.py", line 95, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
django.db.utils.NotSupportedError: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailsearch_query".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/test/testcases.py", line 217, in __call__
    self._post_teardown()
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/test/testcases.py", line 924, in _post_teardown
    self._fixture_teardown()
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/test/testcases.py", line 960, in _fixture_teardown
    inhibit_post_migrate=inhibit_post_migrate)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/core/management/__init__.py", line 119, in call_command
    return command.execute(*args, **defaults)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/core/management/base.py", line 399, in execute
    output = self.handle(*args, **options)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/core/management/commands/flush.py", line 77, in handle
    six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2])
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/core/management/commands/flush.py", line 67, in handle
    cursor.execute(sql)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/utils.py", line 95, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/home/mikela/.conda/envs/syracuse/lib/python3.5/site-packages/django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
django.core.management.base.CommandError: Database test_syracuse_biz_portal couldn't be flushed. Possible reasons:
  * The database isn't running or isn't configured correctly.
  * At least one of the expected database tables doesn't exist.
  * The SQL was invalid.
Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.
The full error: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailsearch_query".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.

@glassresistor
Copy link
Contributor

I am noticing the errors go away if i use TestCase instead of TransactionTestCase but that still a bad sign.

@peterb154
Copy link

I ran into the same issue where the table wagtailsearch_editorspick did not live in my database but was defined as a model. So when I ran manage.py flush, I hit the same error

CommandError: Database cssdjangotest couldn't be flushed. Possible reasons:

  • The database isn't running or isn't configured correctly.
  • At least one of the expected database tables doesn't exist.
  • The SQL was invalid.
    Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.

Looking at the output of ./manage.py sqlflush you can see the SQL statement that django tries to run.(note that I have wagtail integrated with another application called css_portal).

BEGIN;
TRUNCATE "css_cms_tier1page", "wagtailimages_rendition", "css_portal_invoice", "django_admin_log", "django_content_type", "css_cms_standardpage", "wagtailcore_page", "django_session", "css_cms_homepage", "wagtailimages_image", "wagtaildocs_document", "wagtailsearch_query", "wagtailcore_site", "css_portal_profile", "wagtailembeds_embed", "wagtailsearch_querydailyhits", "auth_group", "wagtailcore_groupcollectionpermission", "taggit_tag", "auth_permission", "wagtailcore_pageviewrestriction_groups", "wagtailcore_collectionviewrestriction", "django_ses_sesstat", "css_cms_tier2page", "css_portal_customerdomain", "wagtailcore_collection", "taggit_taggeditem", "wagtailcore_collectionviewrestriction_groups", "css_portal_invoiceauditfile", "wagtailusers_userprofile", "wagtailforms_formsubmission", "auth_user_groups", "wagtailcore_pagerevision", "wagtailcore_grouppagepermission", "wagtailcore_pageviewrestriction", "css_portal_customer", "css_portal_cloudaccount", "auth_user", "auth_group_permissions", "css_portal_dynamodbri", "auth_user_user_permissions", "wagtailredirects_redirect";
....

When I execute that command in the dbshell using ./manage.py dbshell I can see the problem (and the suggested fix).

psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04), server 10.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

cssdjangotest=> TRUNCATE "css_cms_tier1page", "wagtailimages_rendition", "css_portal_invoice", "django_admin_log", "django_content_type", "css_cms_standardpage", "wagtailcore_page", "django_session", "css_cms_homepage", "wagtailimages_image", "wagtaildocs_document", "wagtailsearch_query", "wagtailcore_site", "css_portal_profile", "wagtailembeds_embed", "wagtailsearch_querydailyhits", "auth_group", "wagtailcore_groupcollectionpermission", "taggit_tag", "auth_permission", "wagtailcore_pageviewrestriction_groups", "wagtailcore_collectionviewrestriction", "django_ses_sesstat", "css_cms_tier2page", "css_portal_customerdomain", "wagtailcore_collection", "taggit_taggeditem", "wagtailcore_collectionviewrestriction_groups", "css_portal_invoiceauditfile", "wagtailusers_userprofile", "wagtailforms_formsubmission", "auth_user_groups", "wagtailcore_pagerevision", "wagtailcore_grouppagepermission", "wagtailcore_pageviewrestriction", "css_portal_customer", "css_portal_cloudaccount", "auth_user", "auth_group_permissions", "css_portal_dynamodbri", "auth_user_user_permissions", "wagtailredirects_redirect";
ERROR: cannot truncate a table referenced in a foreign key constraint
DETAIL: Table "wagtailsearch_editorspick" references "wagtailcore_page".
HINT: Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.

When I run the same command with "CASCADE;" at the end, it works.

TRUNCATE "css_cms_tier1page", ..... "css_portal_dynamodbri", "auth_user_user_permissions", "wagtailredirects_redirect" CASCADE;

The django flush.py management command on line 34 (of django 2.1 anyway) has built in support to allow the cascade option.

34 allow_cascade = options.get('allow_cascade', False)

But it is a "stealth option" and set to False by default. You cannot currently specify something like --allow_cascade=True on the ./manage.py flush command line.

So, using the django instructions for over-riding a management module I copied flush.py to myapp/management/commands/flush.py and inserted the following in the add_arguments() function (at line 26)

parser.add_argument(
    '--allow_cascade', action='store', dest='allow_cascade', default=False,
        help='Adds "CASCADE" option to TRUNCATE command if supported by db backend. Default=False.',
)

And now, I can simply run ./manage.py flush --allow_cascade=True and the flush works perfectly.

Hope this helps someone!

@saka2jp
Copy link

saka2jp commented Dec 11, 2018

I faced the same problem. When will it be fixed?
In some cases, I have to give up using wagtail.

Requirements:

Django==2.1.4
wagtail==2.3

@hypercodex
Copy link

this is clunky, what can we do to fix this?

@SebCorbin
Copy link
Contributor

SebCorbin commented Dec 30, 2018

As most of you, I had this problem while integrating wagtail in a existing project. After reading this thread I found out that only my Selenium tests were failing and that was due to cascade not being specified. I simply overrode the _fixture_teardwon() to force allow_cascade when the flush command was called.

        # Allow TRUNCATE ... CASCADE and don't emit the post_migrate signal
        # when flushing only a subset of the apps
        for db_name in self._databases_names(include_mirrors=False):
            # Flush the database
            inhibit_post_migrate = (
                self.available_apps is not None or
                (   # Inhibit the post_migrate signal when using serialized
                    # rollback to avoid trying to recreate the serialized data.
                    self.serialized_rollback and
                    hasattr(connections[db_name], '_test_serialized_contents')
                )
            )
            call_command('flush', verbosity=0, interactive=False,
                         database=db_name, reset_sequences=False,
                         allow_cascade=True,
                         inhibit_post_migrate=inhibit_post_migrate)

@gasman gasman modified the milestones: real-soon-now, 2.5 Jan 9, 2019
@gasman
Copy link
Collaborator

gasman commented Jan 9, 2019

Apologies for the delay in dealing with this - I'll aim to get this looked at for version 2.5 (but in the meantime anyone is welcome to pick this up). I think the solution will be something along the lines that @aaugustin describes - either dropping and re-adding the foreign key constraint, or having the wagtailsearch app 'reclaim' the current wagtailsearch_editorspick table and having wagtailsearchpromotions create and populate a new table instead.

Whatever we go for, we'll need to check the solution carefully to make sure that all before/after states of the database are accounted for - retrospectively changing the migration setup is a tricky business, and there's a risk of making things worse if we're not careful.

@barseghyanartur
Copy link

barseghyanartur commented Feb 26, 2019

@SebCorbin:

As most of you, I had this problem while integrating wagtail in a existing project. After reading this thread I found out that only my Selenium tests were failing and that was due to cascade not being specified. I simply overrode the _fixture_teardwon() to force allow_cascade when the flush command was called.

        # Allow TRUNCATE ... CASCADE and don't emit the post_migrate signal
        # when flushing only a subset of the apps
        for db_name in self._databases_names(include_mirrors=False):
            # Flush the database
            inhibit_post_migrate = (
                self.available_apps is not None or
                (   # Inhibit the post_migrate signal when using serialized
                    # rollback to avoid trying to recreate the serialized data.
                    self.serialized_rollback and
                    hasattr(connections[db_name], '_test_serialized_contents')
                )
            )
            call_command('flush', verbosity=0, interactive=False,
                         database=db_name, reset_sequences=False,
                         allow_cascade=True,
                         inhibit_post_migrate=inhibit_post_migrate)

That worked for me. Thanks!

@gasman gasman added this to the 2.13 milestone Jan 15, 2021
jpmckinney added a commit to open-contracting-archive/covid-19-procurement-explorer-admin that referenced this issue Apr 1, 2021
@gasman gasman modified the milestones: 2.13, 2.14 Apr 19, 2021
@gasman gasman modified the milestones: 2.14, 2.15 Jul 13, 2021
@gasman gasman modified the milestones: 2.15, 2.16 Oct 14, 2021
@gasman gasman modified the milestones: 2.16, 2.17 Jan 12, 2022
@electroniceagle
Copy link

I don't know if this is useful information or not, but I only hit this when using LiveServerTestcase. TestCase and the WagtailTestCase don't trigger it for me. Assuming it is related to TransactionTestCase rollback optimizations.

@gasman gasman modified the milestones: 3.0, 4.0 Apr 14, 2022
@chosak
Copy link
Member

chosak commented Apr 20, 2022

Can confirm this still exists in Wagtail 2.16.2, and Wagtail 3.0rc1.

Note as mentioned above (#1824 (comment)) this seems to prevent use of Django LiveServerTestCase when running with a PostgreSQL database, due to the way that it calls sqlflush.

Adding "wagtail.contrib.search_promotions" to settings.INSTALLED_APPS is a workaround but it would be nice not to have to do this in cases where users don't want to enable that app in the admin.

Minimal test case to reproduce with 3.0rc1:

  1. pip install wagtail==3.0rc1 psycopg2
  2. wagtail start myproject
  3. cd myproject
  4. Create a Postgres database (e.g. with createdb) and modify myproject/settings/base.py to point to it, for example:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.postgresql_psycopg2",
            "NAME": "testwagtail",
        }
    }
  5. ./manage.py migrate
  6. Create a test file under home/test.py with this content:
    from django.test import LiveServerTestCase
    
    class MyTests(LiveServerTestCase):
        def test_something(self):
            pass
  7. ./manage.py test will fail with django.db.utils.NotSupportedError: cannot truncate a table referenced in a foreign key constraint.
Click to expand full stack trace
% ./manage.py test
Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.E
======================================================================
ERROR: test_something (home.tests.MyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/utils.py", line 87, in _execute
    return self.cursor.execute(sql)
psycopg2.errors.FeatureNotSupported: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailsearch_query".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/core/management/commands/flush.py", line 73, in handle
    connection.ops.execute_sql_flush(sql_list)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/base/operations.py", line 442, in execute_sql_flush
    cursor.execute(sql)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/db/backends/utils.py", line 87, in _execute
    return self.cursor.execute(sql)
django.db.utils.NotSupportedError: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "wagtailsearch_editorspick" references "wagtailsearch_query".
HINT:  Truncate table "wagtailsearch_editorspick" at the same time, or use TRUNCATE ... CASCADE.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/test/testcases.py", line 299, in _setup_and_call
    self._post_teardown()
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/test/testcases.py", line 1199, in _post_teardown
    self._fixture_teardown()
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/test/testcases.py", line 1233, in _fixture_teardown
    call_command(
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/core/management/__init__.py", line 198, in call_command
    return command.execute(*args, **defaults)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
  File "/Users/chosaka/.virtualenvs/testwagtail/lib/python3.8/site-packages/django/core/management/commands/flush.py", line 75, in handle
    raise CommandError(
django.core.management.base.CommandError: Database test_testwagtail couldn't be flushed. Possible reasons:
  * The database isn't running or isn't configured correctly.
  * At least one of the expected database tables doesn't exist.
  * The SQL was invalid.
Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.

----------------------------------------------------------------------
Ran 1 test in 0.514s

FAILED (errors=1)
Destroying test database for alias 'default'...

@katzmo
Copy link

katzmo commented Aug 3, 2022

Also stumbled across this in Wagtail 3.0.1. Why is it so hard to fix? What happened when looking into this for version 2.5 in 2019?

@gasman gasman modified the milestones: 4.0, 4.1 Aug 11, 2022
@GabrielBogo
Copy link

Also stuck here. I need to use pytest-django's @pytest.mark.django_db(transaction=True) for certain tests, which needs to flush the test database after each test run. Then it fails.

Has anybody figured out a work around this bug to make it compatible with pytest-django?

@gasman
Copy link
Collaborator

gasman commented Oct 7, 2022

@GabrielBogo As @chosak mentioned, adding "wagtail.contrib.search_promotions" to INSTALLED_APPS in your test environment should serve as a workaround.

Working towards a proper solution... The migrations that brought about the current situation are:

  • wagtailsearch.0001_initial: creates the wagtailsearch_editorspick table
  • wagtailsearch.0003_remove_editors_pick: removes the EditorsPick model from the wagtailsearch application state but leaves the wagtailsearch_editorspick table in the database as an unmanaged table
  • wagtailsearchpromotions.0001_initial: renames the wagtailsearch_editorspick table to wagtailsearchpromotions_searchpromotion and modifies the wagtailsearchpromotions application state to 'adopt' that model

All of these migrations have been around since Wagtail 1.1, and at this point we can reasonably assume that nobody is going to upgrade directly from Wagtail 1.1 to 4.x without deploying any intermediate version (or, at least, if they're foolhardy enough to do that, the onus is on them to devise their own migration plan), so we can dismiss the possibility of a project being in some historical state where migrations are only partially applied, or where a wagtailsearch_editorspick table contains meaningful data.

This means that any prospective bugfix will need to handle all of these initial states:

  1. No migrations applied - i.e. a newly-created project immediately before running ./manage.py migrate
  2. wagtailsearch migrations applied but not wagtailsearchpromotions - i.e. a fully migrated project that doesn't have wagtail.contrib.search_promotions in INSTALLED_APPS. These have an unmanaged wagtailsearch_editorspick table and are affected by the bug described here.
  3. Both wagtailsearch and wagtailsearchpromotions migrations applied - i.e. a fully migrated project with wagtail.contrib.search_promotions in INSTALLED_APPS. These have a wagtailsearchpromotions_searchpromotion table managed by Django, and no wagtailsearch_editorspick table. They are not affected by this bug (since the flush command knows about all tables that exist in the database).

From these, we want to arrive at a state where wagtailsearch_editorspick does not exist, and wagtailsearchpromotions_searchpromotion exists if and only if wagtail.contrib.search_promotions is in INSTALLED_APPS. Additionally:

  1. In the post-bugfix state, adding wagtail.contrib.search_promotions to INSTALLED_APPS and running migrate must still successfully create the wagtailsearchpromotions_searchpromotion table.

My first pass at a solution would be:

  • Change wagtailsearchpromotions.0001_initial to create searchpromotion as a new table, rather than renaming editorspick
  • Change wagtailsearch.0003_remove_editors_pick to do nothing
  • Add a new wagtailsearch.0007 migration to drop the editorspick table and model

However, this fails on scenario 3, because then the wagtailsearch.0007 migration will end up trying to delete a table that doesn't exist. I expect there's a way to specify "drop table if it exists" to work around that, but it feels like I'm missing a more elegant solution - if we got into this mess without any conditional logic, surely we can get out of it again?

@gasman
Copy link
Collaborator

gasman commented Oct 7, 2022

but it feels like I'm missing a more elegant solution - if we got into this mess without any conditional logic, surely we can get out of it again?

On further consideration, probably not. Essentially I'm trying to engineer a sort of symmetrical process to undo our mistakes in the reverse of the order they happened in - and since wagtailsearchpromotions.0001 is an optional final step that runs after the wagtailsearch migrations to remove wagtailsearch_editorspick, the fix would logically have to begin with an optional initial step within wagtailsearchpromotions that creates wagtailsearch_editorspick. But there's no way to insert an optional step like that - if we put a run_before rule in, that'll break the ability to install wagtailsearchpromotions after the wagtailsearch migrations have been applied.

So, I'm pretty sure the "drop table if exists" route is the way to go... looks like https://stackoverflow.com/questions/40546762/django-migrations-how-to-check-if-table-exists-in-migrations gives us a recipe for doing that.

gasman added a commit to gasman/wagtail that referenced this issue Oct 10, 2022
…ound

Fixes wagtail#1824, as per the outline in wagtail#1824 (comment). Revise the existing migrations so that rather than keeping the wagtailsearch_editorspick table in place and unmanaged for the wagtail.contrib.search_promotions app to potentially rename and adopt, search_promotions creates its own table. This then leaves wagtailsearch free to delete wagtailsearch_editorspick properly in a new migration, to be run on both new and upgraded projects (although this needs to be done inside an existence check, in case the old version of the search_promotions app migrations have already run and renamed the table).
@gasman gasman closed this as completed in f37768e Oct 17, 2022
@GabrielBogo
Copy link

@GabrielBogo As @chosak mentioned, adding "wagtail.contrib.search_promotions" to INSTALLED_APPS in your test environment should serve as a workaround.

Thanks, @gasman - that worked

@garyd203
Copy link

Thanks @gasman

Yekasumah pushed a commit to Yekasumah/wagtail- that referenced this issue Nov 1, 2022
…ound

Fixes wagtail#1824, as per the outline in wagtail#1824 (comment). Revise the existing migrations so that rather than keeping the wagtailsearch_editorspick table in place and unmanaged for the wagtail.contrib.search_promotions app to potentially rename and adopt, search_promotions creates its own table. This then leaves wagtailsearch free to delete wagtailsearch_editorspick properly in a new migration, to be run on both new and upgraded projects (although this needs to be done inside an existence check, in case the old version of the search_promotions app migrations have already run and renamed the table).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.