From 8097e5483210fa8c9d6142f1d48e1caed6d7b7bb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 4 Apr 2015 18:07:46 +0200 Subject: [PATCH] Fixed #23879 -- Allowed model migration skip based on feature/vendor Thanks Carl Meyer for the report and review, and Tim Graham for the review. --- django/core/management/commands/migrate.py | 2 +- django/db/backends/base/creation.py | 2 +- django/db/migrations/operations/base.py | 2 +- django/db/models/options.py | 22 ++++++++++++++++++- docs/ref/models/options.txt | 25 ++++++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 1c836ff4a7..2217d2f277 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -268,7 +268,7 @@ class Command(BaseCommand): deferred_sql = [] for app_name, model_list in manifest.items(): for model in model_list: - if model._meta.proxy or not model._meta.managed: + if not model._meta.can_migrate(connection): continue if self.verbosity >= 3: self.stdout.write( diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index a5e69ceedb..9bb9657b47 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -106,7 +106,7 @@ class BaseDatabaseCreation(object): # Make a function to iteratively return every object def get_objects(): for model in serializers.sort_dependencies(app_list): - if (not model._meta.proxy and model._meta.managed and + if (model._meta.can_migrate(self.connection) and router.allow_migrate_model(self.connection.alias, model)): queryset = model._default_manager.using(self.connection.alias).order_by(model._meta.pk.name) for obj in queryset.iterator(): diff --git a/django/db/migrations/operations/base.py b/django/db/migrations/operations/base.py index b750a1e4be..44a0643fdd 100644 --- a/django/db/migrations/operations/base.py +++ b/django/db/migrations/operations/base.py @@ -106,7 +106,7 @@ class Operation(object): This is a thin wrapper around router.allow_migrate_model() that preemptively rejects any proxy, swapped out, or unmanaged model. """ - if model._meta.proxy or model._meta.swapped or not model._meta.managed: + if not model._meta.can_migrate(connection_alias): return False return router.allow_migrate_model(connection_alias, model) diff --git a/django/db/models/options.py b/django/db/models/options.py index 91b6790b84..a2cdca82ee 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -8,6 +8,7 @@ from itertools import chain from django.apps import apps from django.conf import settings from django.core.exceptions import FieldDoesNotExist +from django.db import connections from django.db.models.fields import AutoField from django.db.models.fields.proxy import OrderWrt from django.db.models.fields.related import ManyToManyField @@ -36,7 +37,8 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering', 'order_with_respect_to', 'app_label', 'db_tablespace', 'abstract', 'managed', 'proxy', 'swappable', 'auto_created', 'index_together', 'apps', 'default_permissions', - 'select_on_save', 'default_related_name') + 'select_on_save', 'default_related_name', + 'required_db_features', 'required_db_vendor') class raise_deprecation(object): @@ -111,6 +113,8 @@ class Options(object): self.get_latest_by = None self.order_with_respect_to = None self.db_tablespace = settings.DEFAULT_TABLESPACE + self.required_db_features = [] + self.required_db_vendor = None self.meta = meta self.pk = None self.has_auto_field = False @@ -337,6 +341,22 @@ class Options(object): def __str__(self): return "%s.%s" % (smart_text(self.app_label), smart_text(self.model_name)) + def can_migrate(self, connection): + """ + Return True if the model can/should be migrated on the `connection`. + `connection` can be either a real connection or a connection alias. + """ + if self.proxy or self.swapped or not self.managed: + return False + if isinstance(connection, six.string_types): + connection = connections[connection] + if self.required_db_vendor: + return self.required_db_vendor == connection.vendor + if self.required_db_features: + return all(getattr(connection.features, feat, False) + for feat in self.required_db_features) + return True + @property def verbose_name_raw(self): """ diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 2f4f5aff71..f9a17721cf 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -283,6 +283,31 @@ Django quotes column and table names behind the scenes. If ``proxy = True``, a model which subclasses another model will be treated as a :ref:`proxy model `. +``required_db_features`` +------------------------ + +.. attribute:: Options.required_db_features + + .. versionadded:: 1.9 + + List of database features that the current connection should have so that + the model is considered during the migration phase. For example, if you set + this list to ``['gis_enabled']``, the model will only be synchronized on + GIS-enabled databases. It's also useful to skip some models when testing + with several database backends. + +``required_db_vendor`` +---------------------- + +.. attribute:: Options.required_db_vendor + + .. versionadded:: 1.9 + + Name of a supported database vendor that this model is specific to. Current + built-in vendor names are: ``sqlite``, ``postgresql``, ``mysql``, + ``oracle``. If this attribute is not empty and the current connection vendor + doesn't match it, the model will not be synchronized. + ``select_on_save`` ------------------