Skip to content

Commit

Permalink
Dramatically refactored how migrations are performed to work with Dja…
Browse files Browse the repository at this point in the history
…ngo 1.7
  • Loading branch information
coordt committed Jun 9, 2015
1 parent 28ef4d5 commit acff7f0
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 23 deletions.
37 changes: 37 additions & 0 deletions categories/apps.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions categories/fields.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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)


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)
Expand Down
54 changes: 54 additions & 0 deletions categories/migration.py
Original file line number Diff line number Diff line change
@@ -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])
117 changes: 96 additions & 21 deletions categories/registration.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down

0 comments on commit acff7f0

Please sign in to comment.