229 lines
12 KiB
Python
229 lines
12 KiB
Python
# Copyright The IETF Trust 2014-2019, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
import os
|
|
import datetime
|
|
import collections
|
|
import io
|
|
|
|
from importlib import import_module
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
from django.core.management.base import AppCommand
|
|
from django.db import models
|
|
from django.template import Template, Context
|
|
|
|
from tastypie.resources import ModelResource
|
|
|
|
|
|
resource_head_template = """# Copyright The IETF Trust {{date}}, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
# Generated by the makeresources management command {{date}}
|
|
|
|
|
|
from tastypie.resources import ModelResource
|
|
from tastypie.fields import ToManyField # pyflakes:ignore
|
|
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
|
|
from tastypie.cache import SimpleCache
|
|
|
|
from ietf import api
|
|
from ietf.api import ToOneField # pyflakes:ignore
|
|
|
|
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()
|
|
serializer = api.Serializer()
|
|
cache = SimpleCache()
|
|
#resource_name = '{{model.resource_name}}'
|
|
ordering = ['{{model.pk_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_config(self, app, **options):
|
|
# dotted path to app
|
|
if app.name:
|
|
print("\nInspecting %s .." % app.name)
|
|
resource_file_path = os.path.join(app.path, "resources.py")
|
|
|
|
app_models = app.get_models()
|
|
|
|
app_resources = {}
|
|
if os.path.exists(resource_file_path):
|
|
resources = import_module("%s.resources" % app.name)
|
|
for n,v in resources.__dict__.items():
|
|
if issubclass(type(v), type(ModelResource)):
|
|
app_resources[n] = v
|
|
|
|
missing_resources = []
|
|
for m in app_models:
|
|
model_name = m.__name__
|
|
rclass_name = model_name + "Resource"
|
|
if not rclass_name in app_resources:
|
|
missing_resources.append((m, rclass_name))
|
|
|
|
if missing_resources:
|
|
print("Updating resources.py for %s" % app.name)
|
|
with io.open(resource_file_path, "a") as rfile:
|
|
info = dict(
|
|
app=app.name,
|
|
app_label=app.label,
|
|
date=datetime.datetime.now()
|
|
)
|
|
new_models = {}
|
|
for model, rclass_name in missing_resources:
|
|
model_name = model.__name__
|
|
resource_name = model.__name__.lower()
|
|
imports = collections.defaultdict(lambda: collections.defaultdict(list))
|
|
print("Adding resource class for %s" % model_name)
|
|
foreign_keys = []
|
|
plain_names = []
|
|
fk_names = []
|
|
m2m_names = []
|
|
pk_name = model._meta.pk.name
|
|
#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.remote_field.to)')
|
|
#exit()
|
|
rel_app=field.remote_field.model._meta.app_label
|
|
rel_model_name=field.remote_field.model.__name__
|
|
if rel_model_name == model_name:
|
|
# foreign key to self class -- quote
|
|
# the rmodel_name
|
|
rmodel_name="'%s.resources.%sResource'" % (app.name, 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.remote_field.model,
|
|
model_name=rel_model_name,
|
|
rmodel_name=rmodel_name,
|
|
resource_name=field.remote_field.model.__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.remote_field.model)')
|
|
#exit()
|
|
rel_app=field.remote_field.model._meta.app_label
|
|
rel_model_name=field.remote_field.model.__name__
|
|
if rel_model_name == model_name:
|
|
# foreign key to self class -- quote
|
|
# the rmodel_name
|
|
rmodel_name="'%s.resources.%sResource'" % (app.name, 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.remote_field.model,
|
|
model_name=rel_model_name,
|
|
rmodel_name=rmodel_name,
|
|
resource_name=field.remote_field.model.__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.name.split('.')[-1],
|
|
model=model,
|
|
fields=model._meta.fields,
|
|
m2m_fields=model._meta.many_to_many,
|
|
name=model_name,
|
|
imports=[ v for k,v in list(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,
|
|
pk_name=pk_name,
|
|
)
|
|
|
|
# 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 = list(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 list(new_models.items()) ]
|
|
break
|
|
|
|
if rfile.tell() == 0:
|
|
print("Writing resource file head")
|
|
rfile.write(render(resource_head_template, info))
|
|
else:
|
|
print("\nNOTE: Not writing resource file head.\nYou may have to update the import from %s.models" % app.name)
|
|
|
|
info.update(dict(models=new_model_list))
|
|
rfile.write(render(resource_class_template, info))
|
|
else:
|
|
print(" nothing to do for %s" % app.name)
|