Added a management command to generate (and update) resource files for tastypie, in order to have to manually maintain the resource files needed by tastypie when models are added (model changes will have to be handled manually, or by removing the old class from the resources and auto-generate it again).

- Legacy-Id: 8743
This commit is contained in:
Henrik Levkowetz 2014-12-14 20:32:12 +00:00
parent c39925fcd0
commit 147cc96a51

View file

@ -0,0 +1,214 @@
from __future__ import print_function
import os
import datetime
import collections
import debug # pyflakes:ignore
from django.core.management.base import AppCommand
from django.db import models
from django.utils.importlib import import_module
from django.template import Template, Context
from tastypie.resources import ModelResource
resource_head_template = """# Autogenerated by the mkresources management command {{date}}
from tastypie.resources import ModelResource
from tastypie.fields import ToOneField, ToManyField # pyflakes:ignore
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
from ietf import api
from {{app}}.models import * # pyflakes:ignore
"""
resource_class_template = """{% autoescape off %}
{% for model in models %}{% for import in model.imports %}{% if import.module != app_label %}
from ietf.{{ import.module }}.resources import {% for name in import.names %}{% if not forloop.first %}, {%endif%}{{name}}Resource{% endfor %}{% endif %}{% endfor %}
class {{model.name}}Resource(ModelResource):{% if model.foreign_keys %}{% for fk in model.foreign_keys %}
{{fk.name|ljust:"16"}} = ToOneField({{fk.rmodel_name}}, '{{fk.name}}'{% if fk.field.null %}, null=True{% endif %}){% endfor %}{% endif %}{% if model.m2m_keys %}{% for fk in model.m2m_keys %}
{{fk.name|ljust:"16"}} = ToManyField({{fk.rmodel_name}}, '{{fk.name}}', null=True){% endfor %}{% endif %}
class Meta:
queryset = {{model.name}}.objects.all()
#resource_name = '{{model.resource_name}}'
filtering = { {% for name in model.plain_names %}
"{{ name }}": ALL,{%endfor%}{% for name in model.fk_names%}
"{{ name }}": ALL_WITH_RELATIONS,{%endfor%}{% for name in model.m2m_names %}
"{{ name }}": ALL_WITH_RELATIONS,{%endfor%}
}
api.{{app_label}}.register({{model.name}}Resource())
{% endfor %}{% endautoescape %}
"""
def render(template, dictionary):
template = Template(template, None, None)
context = Context(dictionary)
return template.render(context)
class Command(AppCommand):
def handle_app(self, app, **options):
if app.__package__:
print("\nInspecting %s .." % app.__package__)
resource_file_path = os.path.join(os.path.dirname(app.__file__), "resources.py")
app_models = models.get_models(app)
app_resources = {}
if os.path.exists(resource_file_path):
resources = import_module("%s.resources" % app.__package__)
for n,v in resources.__dict__.items():
if issubclass(type(v), type(ModelResource)):
app_resources[n] = v
do_update_resources = False
for m in app_models:
model_name = m.__name__
rclass_name = model_name + "Resource"
resource_name = m.__name__.lower()
if not rclass_name in app_resources:
do_update_resources = True
if do_update_resources:
print("Updating resources.py for %s" % app.__package__)
with open(resource_file_path, "a") as rfile:
info = dict(
app=app.__package__,
app_label=app.__package__.split('.')[-1],
date=datetime.datetime.now()
)
new_models = {}
for model in app_models:
model_name = model.__name__
rclass_name = model_name + "Resource"
resource_name = model.__name__.lower()
if not rclass_name in app_resources:
imports = collections.defaultdict(lambda: collections.defaultdict(list))
print("Adding resource class for %s" % model_name)
foreign_keys = []
plain_names = []
fk_names = []
m2m_names = []
#debug.pprint('dir(model)')
for field in model._meta.fields:
if isinstance(field, (models.ForeignKey, models.OneToOneField)):
#debug.show('field.name')
#debug.pprint('dir(field.rel.to)')
#exit()
rel_app=field.rel.to._meta.app_label
rel_model_name=field.rel.to.__name__
if rel_model_name == model_name:
# foreign key to self class -- quote
# the rmodel_name
rmodel_name="'%s.resources.%sResource'" % (app.__package__, rel_model_name)
else:
rmodel_name=rel_model_name+"Resource"
foreign_keys.append(dict(
field=field,
name=field.name,
app=rel_app,
module=rel_app.split('.')[-1],
model=field.rel.to,
model_name=rel_model_name,
rmodel_name=rmodel_name,
resource_name=field.rel.to.__name__.lower(),
))
imports[rel_app]["module"] = rel_app
imports[rel_app]["names"].append(rel_model_name)
fk_names.append(field.name)
else:
plain_names.append(field.name)
m2m_keys = []
for field in model._meta.many_to_many:
#debug.show('field.name')
#debug.pprint('dir(field.rel.to)')
#exit()
rel_app=field.rel.to._meta.app_label
rel_model_name=field.rel.to.__name__
if rel_model_name == model_name:
# foreign key to self class -- quote
# the rmodel_name
rmodel_name="'%s.resources.%sResource'" % (app.__package__, rel_model_name)
else:
rmodel_name=rel_model_name+"Resource"
m2m_keys.append(dict(
field=field,
name=field.name,
app=rel_app,
module=rel_app.split('.')[-1],
model=field.rel.to,
model_name=rel_model_name,
rmodel_name=rmodel_name,
resource_name=field.rel.to.__name__.lower(),
))
imports[rel_app]["module"] = rel_app
imports[rel_app]["names"].append(rel_model_name)
m2m_names.append(field.name)
# some special import cases
if "auth" in imports:
imports["auth"]["module"] = 'utils'
if "contenttypes" in imports:
imports["contenttypes"]["module"] = 'utils'
for k in imports:
imports[k]["names"] = set(imports[k]["names"])
new_models[model_name] = dict(
app=app.__package__.split('.')[-1],
model=model,
fields=model._meta.fields,
m2m_fields=model._meta.many_to_many,
name=model_name,
imports=[ v for k,v in imports.items() ],
foreign_keys=foreign_keys,
m2m_keys=m2m_keys,
resource_name=resource_name,
plain_names=plain_names,
fk_names=fk_names,
m2m_names=m2m_names,
)
# Sort resources according to internal FK reference depth
new_model_list = []
# Write out classes with FKs to classes in the same module
# lower or equal to 'internal_fk_count_limit. Start out
# by writing only leaf classes, then increase the limit if
# needed:
internal_fk_count_limit = 0
while len(new_models) > 0:
list_len = len(new_models)
#debug.show('len(new_models)')
keys = new_models.keys()
for model_name in keys:
internal_fk_count = 0
for fk in new_models[model_name]["foreign_keys"]+new_models[model_name]["m2m_keys"]:
#debug.say("if statement comparison on:")
#debug.show('fk["model_name"]')
#debug.show('model_name')
#debug.say('if fk["model_name"] in new_models and not fk["model_name"] == model_name:')
if fk["model_name"] in new_models and not fk["model_name"] == model_name:
#print("Not a leaf model: %s: found fk to %s" % (model_name, fk["model"]))
internal_fk_count += 1
if internal_fk_count <= internal_fk_count_limit:
#print("Ordered: "+model_name)
new_model_list.append(new_models[model_name])
del new_models[model_name]
if list_len == len(new_models):
#debug.show('list_len, len(new_models)')
print("Circular FK dependencies -- cannot order resource classes")
if internal_fk_count_limit < list_len:
print("Attempting a partial ordering ...")
internal_fk_count_limit += 1
else:
print("Failed also with partial ordering, writing resource classes without ordering")
new_model_list = [ v for k,v in new_models.items() ]
break
if rfile.tell() == 0:
print("Writing resource file head")
rfile.write(render(resource_head_template, info))
info.update(dict(models=new_model_list))
rfile.write(render(resource_class_template, info))
else:
print(" nothing to do for %s" % app.__package__)