From acff7f02a344a63cc5b5d32adc70fd4f470787f4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 13 May 2015 15:08:35 -0500 Subject: [PATCH] Dramatically refactored how migrations are performed to work with Django 1.7 --- categories/apps.py | 37 ++++++++++++ categories/fields.py | 4 +- categories/migration.py | 54 +++++++++++++++++ categories/registration.py | 117 ++++++++++++++++++++++++++++++------- 4 files changed, 189 insertions(+), 23 deletions(-) create mode 100644 categories/apps.py create mode 100644 categories/migration.py diff --git a/categories/apps.py b/categories/apps.py new file mode 100644 index 0000000..daab55b --- /dev/null +++ b/categories/apps.py @@ -0,0 +1,37 @@ +from django.apps import AppConfig + + +class CategoriesConfig(AppConfig): + name = 'categories' + verbose_name = "Categories" + + def __init__(self, *args, **kwargs): + super(CategoriesConfig, self).__init__(*args, **kwargs) + from django.db.models.signals import class_prepared + class_prepared.connect(handle_class_prepared) + + def ready(self): + from django.db.models.signals import post_migrate + from .migration import migrate_app + + post_migrate.connect(migrate_app) + + +def handle_class_prepared(sender, **kwargs): + """ + See if this class needs registering of fields + """ + from .settings import M2M_REGISTRY, FK_REGISTRY + from .registration import registry + sender_app = sender._meta.app_label + sender_name = sender._meta.model_name + + for key, val in FK_REGISTRY.items(): + app_name, model_name = key.split('.') + if app_name == sender_app and sender_name == model_name: + registry.register_model(app_name, sender, 'ForeignKey', val) + + for key, val in M2M_REGISTRY.items(): + app_name, model_name = key.split('.') + if app_name == sender_app and sender_name == model_name: + registry.register_model(app_name, sender, 'ManyToManyField', val) diff --git a/categories/fields.py b/categories/fields.py index 9d0db82..4278ef7 100644 --- a/categories/fields.py +++ b/categories/fields.py @@ -1,10 +1,9 @@ from django.db.models import ForeignKey, ManyToManyField -from .models import Category - class CategoryM2MField(ManyToManyField): def __init__(self, **kwargs): + from .models import Category if 'to' in kwargs: kwargs.pop('to') super(CategoryM2MField, self).__init__(to=Category, **kwargs) @@ -12,6 +11,7 @@ class CategoryM2MField(ManyToManyField): class CategoryFKField(ForeignKey): def __init__(self, **kwargs): + from .models import Category if 'to' in kwargs: kwargs.pop('to') super(CategoryFKField, self).__init__(to=Category, **kwargs) diff --git a/categories/migration.py b/categories/migration.py new file mode 100644 index 0000000..290d6de --- /dev/null +++ b/categories/migration.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, connection +from django.apps import apps + + +def table_exists(table_name): + """ + Check if a table exists in the database + """ + pass + + +def field_exists(app_name, model_name, field_name): + """ + Does the FK or M2M table exist in the database already? + """ + model = apps.get_model(app_name, model_name) + table_name = model._meta.db_table + cursor = connection.cursor() + field_info = connection.introspection.get_table_description(cursor, table_name) + field_names = [f.name for f in field_info] + return field_name in field_names + + +def drop_field(app_name, model_name, field_name): + """ + Drop the given field from the app's model + """ + app_config = apps.get_app_config(app_name) + model = app_config.get_model(model_name) + field = model._meta.get_field(field_name) + with connection.schema_editor() as schema_editor: + schema_editor.remove_field(model, field) + + +def migrate_app(sender, app_config, verbosity=False, *args, **kwargs): + """ + Migrate all models of this app registered + """ + from .registration import registry + + app_name = app_config.label + + fields = [fld for fld in registry._field_registry.keys() if fld.startswith(app_name)] + + with connection.schema_editor() as schema_editor: + for fld in fields: + model_name, field_name = fld.split('.')[1:] + if field_exists(app_name, model_name, field_name): + continue + model = app_config.get_model(model_name) + schema_editor.add_field(model, registry._field_registry[fld]) diff --git a/categories/registration.py b/categories/registration.py index 28f39ff..1088817 100644 --- a/categories/registration.py +++ b/categories/registration.py @@ -1,36 +1,111 @@ """ These functions handle the adding of fields to other models """ -from django.db.models import FieldDoesNotExist +from django.db.models import FieldDoesNotExist, ForeignKey, ManyToManyField import fields -from settings import FIELD_REGISTRY, MODEL_REGISTRY +# from settings import self._field_registry, self._model_registry from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ImproperlyConfigured -def register_m2m(model, field_name='categories', extra_params={}): - return _register(model, field_name, extra_params, fields.CategoryM2MField) +FIELD_TYPES = { + 'ForeignKey': ForeignKey, + 'ManyToManyField': ManyToManyField, +} -def register_fk(model, field_name='category', extra_params={}): - return _register(model, field_name, extra_params, fields.CategoryFKField) +class Registry(object): + def __init__(self): + self._field_registry = {} + self._model_registry = {} + def register_model(self, app, model_name, field_type, field_definitions): + """ + Process for Django 1.7 + + app: app name/label + model_name: name of the model + field_definitions: a string, tuple or list of field configurations + field_type: either 'ForeignKey' or 'ManyToManyField' + """ + from django.apps import apps + import collections -def _register(model, field_name, extra_params={}, field=fields.CategoryFKField): - app_label = model._meta.app_label - registry_name = ".".join((app_label, model.__name__, field_name)).lower() + app_config = apps.get_app_config(app) + app_label = app_config.label - if registry_name in FIELD_REGISTRY: - return # raise AlreadyRegistered - opts = model._meta - try: - opts.get_field(field_name) - except FieldDoesNotExist: - if app_label not in MODEL_REGISTRY: - MODEL_REGISTRY[app_label] = [] - if model not in MODEL_REGISTRY[app_label]: - MODEL_REGISTRY[app_label].append(model) - FIELD_REGISTRY[registry_name] = field(**extra_params) - FIELD_REGISTRY[registry_name].contribute_to_class(model, field_name) + if isinstance(field_definitions, basestring): + field_definitions = [field_definitions] + elif not isinstance(field_definitions, collections.Iterable): + raise ImproperlyConfigured(_('Field configuration for %(app)s should ' + 'be a string or iterable') % {'app': app_config.label}) + + if field_type not in ('ForeignKey', 'ManyToManyField'): + raise ImproperlyConfigured(_('`field_type` must be either `"ForeignKey"` or `"ManyToManyField"`.')) + + try: + if not hasattr(model_name, "_meta"): + model = app_config.get_model(model_name) + else: + model = model_name + model_name = model._meta.model_name + opts = model._meta + if app_label not in self._model_registry: + self._model_registry[app_label] = [] + if model not in self._model_registry[app_label]: + self._model_registry[app_label].append(model) + except LookupError: + raise ImproperlyConfigured('Model "%(model)s" doesn\'t exist in app "%(app)s".' % {'model': model_name, 'app': app}) + + if not isinstance(field_definitions, (tuple, list)): + field_definitions = [field_definitions] + + for fld in field_definitions: + extra_params = {'to': 'categories.Category', 'null': True, 'blank': True} + if isinstance(fld, basestring): + field_name = fld + elif isinstance(fld, dict): + field_name = fld.pop('name') + extra_params.update(fld) + else: + raise ImproperlyConfigured(_("%(settings)s doesn't recognize the " + "value of %(app)s.%(model)s") % { + 'settings': 'CATEGORY_SETTINGS', + 'app': app, + 'model': model_name}) + registry_name = ".".join([app_config.label, model_name.lower(), field_name]) + if registry_name in self._field_registry: + continue + + try: + opts.get_field(field_name) + except FieldDoesNotExist: + self._field_registry[registry_name] = FIELD_TYPES[field_type](**extra_params) + self._field_registry[registry_name].contribute_to_class(model, field_name) + + def register_m2m(self, model, field_name='categories', extra_params={}): + return self._register(model, field_name, extra_params, fields.CategoryM2MField) + + def register_fk(self, model, field_name='category', extra_params={}): + return self._register(model, field_name, extra_params, fields.CategoryFKField) + + def _register(self, model, field_name, extra_params={}, field=fields.CategoryFKField): + app_label = model._meta.app_label + registry_name = ".".join((app_label, model.__name__, field_name)).lower() + + if registry_name in self._field_registry: + return # raise AlreadyRegistered + opts = model._meta + try: + opts.get_field(field_name) + except FieldDoesNotExist: + if app_label not in self._model_registry: + self._model_registry[app_label] = [] + if model not in self._model_registry[app_label]: + self._model_registry[app_label].append(model) + self._field_registry[registry_name] = field(**extra_params) + self._field_registry[registry_name].contribute_to_class(model, field_name) + +registry = Registry() def _process_registry(registry, call_func):