From 147cc96a51d0665c0adb817d8abfae97ba7f5320 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Sun, 14 Dec 2014 20:32:12 +0000 Subject: [PATCH] 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 --- .../management/commands/makeresources.py | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 ietf/utils/management/commands/makeresources.py diff --git a/ietf/utils/management/commands/makeresources.py b/ietf/utils/management/commands/makeresources.py new file mode 100644 index 000000000..125ba51bf --- /dev/null +++ b/ietf/utils/management/commands/makeresources.py @@ -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__)