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:
parent
c39925fcd0
commit
147cc96a51
214
ietf/utils/management/commands/makeresources.py
Normal file
214
ietf/utils/management/commands/makeresources.py
Normal 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__)
|
Loading…
Reference in a new issue