Dramatically refactored how migrations are performed to work with Django 1.7

This commit is contained in:
Corey Oordt 2015-05-13 15:08:35 -05:00
parent 28ef4d5565
commit acff7f02a3
4 changed files with 189 additions and 23 deletions

37
categories/apps.py Normal file
View file

@ -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)

View file

@ -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)

54
categories/migration.py Normal file
View file

@ -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])

View file

@ -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):