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

Fix Postgres issue not dropping null constraint #154

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

List of the most important changes for each release.

## 0.6.10
- Fixes Django migration issue introduced in 0.6.7 allowing nullable fields with PostgreSQL backends

## 0.6.9
- Fixes un-ordered selection of buffers during sync which can allow duplicates to be synced with PostgreSQL backends
- Moves updating of database counters to occur in the same DB transaction as updates to the Store
Expand Down
2 changes: 1 addition & 1 deletion morango/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from __future__ import unicode_literals

default_app_config = "morango.apps.MorangoConfig"
__version__ = "0.6.9"
__version__ = "0.6.10"
33 changes: 33 additions & 0 deletions morango/migrations/0020_postgres_fix_nullable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2022-01-13 18:07
from __future__ import unicode_literals

from django.db import migrations


def apply(apps, schema_editor):
# sqlite does not allow ALTER COLUMN, but also isn't affected by this issue
if "postgresql" in schema_editor.connection.vendor:
schema_editor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage DROP NOT NULL")
schema_editor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage_status DROP NOT NULL")


def revert(apps, schema_editor):
# sqlite does not allow ALTER COLUMN, but also isn't affected by this issue
if "postgresql" in schema_editor.connection.vendor:
schema_editor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage SET NOT NULL")
schema_editor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage_status SET NOT NULL")


class Migration(migrations.Migration):
"""
Applies nullable change made to 0018_auto_20210714_2216.py after it was released
"""

dependencies = [
("morango", "0019_auto_20220113_1807"),
]

operations = [
migrations.RunPython(apply, reverse_code=revert),
]
38 changes: 38 additions & 0 deletions tests/testapp/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

import factory
import mock
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.test import TestCase
from django.test.testcases import LiveServerTestCase
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
Expand Down Expand Up @@ -442,3 +445,38 @@ def stage_status(self):
def update_state(self, stage=None, stage_status=None):
self._stage = stage or self._stage
self._stage_status = stage_status or self._stage_status


class TestMigrations(TestCase):
# Modified from https://www.caktusgroup.com/blog/2016/02/02/writing-unit-tests-django-migrations/

migrate_from = None
migrate_to = None
app = None

def setUp(self):
assert (
self.migrate_from and self.migrate_to
), "TestCase '{}' must define migrate_from and migrate_to properties".format(
type(self).__name__
)

migrate_from = [(self.app, self.migrate_from)]
migrate_to = [(self.app, self.migrate_to)]
executor = MigrationExecutor(connection)
old_apps = executor.loader.project_state(migrate_from).apps

# Reverse to the original migration
executor.migrate(migrate_from)

self.setUpBeforeMigration(old_apps)

# Run the migration to test
executor = MigrationExecutor(connection)
executor.loader.build_graph() # reload.
executor.migrate(migrate_to)

self.apps = executor.loader.project_state(migrate_to).apps

def setUpBeforeMigration(self, apps):
pass
54 changes: 54 additions & 0 deletions tests/testapp/tests/test_migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import uuid

import pytest

from django.conf import settings
from django.db import connection
from django.db.utils import IntegrityError
from django.utils import timezone

from .helpers import TestMigrations


@pytest.mark.skipif(not settings.MORANGO_TEST_POSTGRESQL, reason="Only postgres")
class MorangoNullableMigrationTest(TestMigrations):
"""
Test migration that applies nullable status to `transfer_stage` and `transfer_stage_status`
"""

app = "morango"
migrate_from = "0018_auto_20210714_2216"
migrate_to = "0020_postgres_fix_nullable"

def setUpBeforeMigration(self, apps):
# simulate as if 0018_auto_20210714_2216 hadn't applied Nullablity to the columns,
# a change which we added after the migration might have run on other
SyncSession = apps.get_model("morango", "SyncSession")

with connection.cursor() as cursor:
cursor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage SET NOT NULL")
cursor.execute("ALTER TABLE morango_transfersession ALTER COLUMN transfer_stage_status SET NOT NULL")

self.sync_session = SyncSession.objects.create(
id=uuid.uuid4().hex,
profile="facilitydata",
last_activity_timestamp=timezone.now(),
)

def test_nullable(self):
TransferSession = self.apps.get_model("morango", "TransferSession")

try:
transfer_session = TransferSession.objects.create(
id=uuid.uuid4().hex,
sync_session_id=self.sync_session.id,
push=True,
last_activity_timestamp=timezone.now(),
transfer_stage=None,
transfer_stage_status=None,
)
except IntegrityError:
self.fail("Couldn't create TransferSession with nullable fields")

self.assertIsNone(transfer_session.transfer_stage)
self.assertIsNone(transfer_session.transfer_stage_status)