mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
7.1 KiB
Python
193 lines
7.1 KiB
Python
5 years ago
|
"""
|
||
|
Handles freezing of models into FakeORMs.
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import sys
|
||
|
|
||
|
from django.db import models
|
||
|
from django.db.models.base import ModelBase, Model
|
||
|
from django.contrib.contenttypes.generic import GenericRelation
|
||
|
|
||
|
from south.utils import get_attribute, auto_through
|
||
|
from south import modelsinspector
|
||
|
from south.utils.py3 import string_types
|
||
|
|
||
|
def freeze_apps(apps):
|
||
|
"""
|
||
|
Takes a list of app labels, and returns a string of their frozen form.
|
||
|
"""
|
||
|
if isinstance(apps, string_types):
|
||
|
apps = [apps]
|
||
|
frozen_models = set()
|
||
|
# For each app, add in all its models
|
||
|
for app in apps:
|
||
|
for model in models.get_models(models.get_app(app)):
|
||
|
# Only add if it's not abstract or proxy
|
||
|
if not model._meta.abstract and not getattr(model._meta, "proxy", False):
|
||
|
frozen_models.add(model)
|
||
|
# Now, add all the dependencies
|
||
|
for model in list(frozen_models):
|
||
|
frozen_models.update(model_dependencies(model))
|
||
|
# Serialise!
|
||
|
model_defs = {}
|
||
|
model_classes = {}
|
||
|
for model in frozen_models:
|
||
|
model_defs[model_key(model)] = prep_for_freeze(model)
|
||
|
model_classes[model_key(model)] = model
|
||
|
# Check for any custom fields that failed to freeze.
|
||
|
missing_fields = False
|
||
|
for key, fields in model_defs.items():
|
||
|
for field_name, value in fields.items():
|
||
|
if value is None:
|
||
|
missing_fields = True
|
||
|
model_class = model_classes[key]
|
||
|
field_class = model_class._meta.get_field_by_name(field_name)[0]
|
||
|
print(" ! Cannot freeze field '%s.%s'" % (key, field_name))
|
||
|
print(" ! (this field has class %s.%s)" % (field_class.__class__.__module__, field_class.__class__.__name__))
|
||
|
if missing_fields:
|
||
|
print("")
|
||
|
print(" ! South cannot introspect some fields; this is probably because they are custom")
|
||
|
print(" ! fields. If they worked in 0.6 or below, this is because we have removed the")
|
||
|
print(" ! models parser (it often broke things).")
|
||
|
print(" ! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork")
|
||
|
sys.exit(1)
|
||
|
|
||
|
return model_defs
|
||
|
|
||
|
def freeze_apps_to_string(apps):
|
||
|
return pprint_frozen_models(freeze_apps(apps))
|
||
|
|
||
|
###
|
||
|
|
||
|
def model_key(model):
|
||
|
"For a given model, return 'appname.modelname'."
|
||
|
return "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
|
||
|
|
||
|
def prep_for_freeze(model):
|
||
|
"""
|
||
|
Takes a model and returns the ready-to-serialise dict (all you need
|
||
|
to do is just pretty-print it).
|
||
|
"""
|
||
|
fields = modelsinspector.get_model_fields(model, m2m=True)
|
||
|
# Remove useless attributes (like 'choices')
|
||
|
for name, field in fields.items():
|
||
|
fields[name] = remove_useless_attributes(field)
|
||
|
# See if there's a Meta
|
||
|
fields['Meta'] = remove_useless_meta(modelsinspector.get_model_meta(model))
|
||
|
# Add in our own special items to track the object name and managed
|
||
|
fields['Meta']['object_name'] = model._meta.object_name # Special: not eval'able.
|
||
|
if not getattr(model._meta, "managed", True):
|
||
|
fields['Meta']['managed'] = repr(model._meta.managed)
|
||
|
return fields
|
||
|
|
||
|
### Dependency resolvers
|
||
|
|
||
|
def model_dependencies(model, checked_models=None):
|
||
|
"""
|
||
|
Returns a set of models this one depends on to be defined; things like
|
||
|
OneToOneFields as ID, ForeignKeys everywhere, etc.
|
||
|
"""
|
||
|
depends = set()
|
||
|
checked_models = checked_models or set()
|
||
|
# Get deps for each field
|
||
|
for field in model._meta.fields + model._meta.many_to_many:
|
||
|
depends.update(field_dependencies(field, checked_models))
|
||
|
# Add in any non-abstract bases
|
||
|
for base in model.__bases__:
|
||
|
if issubclass(base, models.Model) and hasattr(base, '_meta') and not base._meta.abstract:
|
||
|
depends.add(base)
|
||
|
# Now recurse
|
||
|
new_to_check = depends - checked_models
|
||
|
while new_to_check:
|
||
|
checked_model = new_to_check.pop()
|
||
|
if checked_model == model or checked_model in checked_models:
|
||
|
continue
|
||
|
checked_models.add(checked_model)
|
||
|
deps = model_dependencies(checked_model, checked_models)
|
||
|
# Loop through dependencies...
|
||
|
for dep in deps:
|
||
|
# If the new dep is not already checked, add to the queue
|
||
|
if (dep not in depends) and (dep not in new_to_check) and (dep not in checked_models):
|
||
|
new_to_check.add(dep)
|
||
|
depends.add(dep)
|
||
|
return depends
|
||
|
|
||
|
def field_dependencies(field, checked_models=None):
|
||
|
checked_models = checked_models or set()
|
||
|
depends = set()
|
||
|
arg_defs, kwarg_defs = modelsinspector.matching_details(field)
|
||
|
for attrname, options in arg_defs + list(kwarg_defs.values()):
|
||
|
if options.get("ignore_if_auto_through", False) and auto_through(field):
|
||
|
continue
|
||
|
if options.get("is_value", False):
|
||
|
value = attrname
|
||
|
elif attrname == 'rel.through' and hasattr(getattr(field, 'rel', None), 'through_model'):
|
||
|
# Hack for django 1.1 and below, where the through model is stored
|
||
|
# in rel.through_model while rel.through stores only the model name.
|
||
|
value = field.rel.through_model
|
||
|
else:
|
||
|
try:
|
||
|
value = get_attribute(field, attrname)
|
||
|
except AttributeError:
|
||
|
if options.get("ignore_missing", False):
|
||
|
continue
|
||
|
raise
|
||
|
if isinstance(value, Model):
|
||
|
value = value.__class__
|
||
|
if not isinstance(value, ModelBase):
|
||
|
continue
|
||
|
if getattr(value._meta, "proxy", False):
|
||
|
value = value._meta.proxy_for_model
|
||
|
if value in checked_models:
|
||
|
continue
|
||
|
checked_models.add(value)
|
||
|
depends.add(value)
|
||
|
depends.update(model_dependencies(value, checked_models))
|
||
|
|
||
|
return depends
|
||
|
|
||
|
### Prettyprinters
|
||
|
|
||
|
def pprint_frozen_models(models):
|
||
|
return "{\n %s\n }" % ",\n ".join([
|
||
|
"%r: %s" % (name, pprint_fields(fields))
|
||
|
for name, fields in sorted(models.items())
|
||
|
])
|
||
|
|
||
|
def pprint_fields(fields):
|
||
|
return "{\n %s\n }" % ",\n ".join([
|
||
|
"%r: %r" % (name, defn)
|
||
|
for name, defn in sorted(fields.items())
|
||
|
])
|
||
|
|
||
|
### Output sanitisers
|
||
|
|
||
|
USELESS_KEYWORDS = ["choices", "help_text", "verbose_name"]
|
||
|
USELESS_DB_KEYWORDS = ["related_name", "default", "blank"] # Important for ORM, not for DB.
|
||
|
INDEX_KEYWORDS = ["db_index"]
|
||
|
|
||
|
def remove_useless_attributes(field, db=False, indexes=False):
|
||
|
"Removes useless (for database) attributes from the field's defn."
|
||
|
# Work out what to remove, and remove it.
|
||
|
keywords = USELESS_KEYWORDS[:]
|
||
|
if db:
|
||
|
keywords += USELESS_DB_KEYWORDS[:]
|
||
|
if indexes:
|
||
|
keywords += INDEX_KEYWORDS[:]
|
||
|
if field:
|
||
|
for name in keywords:
|
||
|
if name in field[2]:
|
||
|
del field[2][name]
|
||
|
return field
|
||
|
|
||
|
USELESS_META = ["verbose_name", "verbose_name_plural"]
|
||
|
def remove_useless_meta(meta):
|
||
|
"Removes useless (for database) attributes from the table's meta."
|
||
|
if meta:
|
||
|
for name in USELESS_META:
|
||
|
if name in meta:
|
||
|
del meta[name]
|
||
|
return meta
|