Skip to content

Commit 147cc96

Browse files
committed
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
1 parent c39925f commit 147cc96

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
from __future__ import print_function
2+
3+
import os
4+
import datetime
5+
import collections
6+
7+
import debug # pyflakes:ignore
8+
9+
from django.core.management.base import AppCommand
10+
from django.db import models
11+
from django.utils.importlib import import_module
12+
from django.template import Template, Context
13+
14+
from tastypie.resources import ModelResource
15+
16+
17+
resource_head_template = """# Autogenerated by the mkresources management command {{date}}
18+
from tastypie.resources import ModelResource
19+
from tastypie.fields import ToOneField, ToManyField # pyflakes:ignore
20+
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
21+
22+
from ietf import api
23+
24+
from {{app}}.models import * # pyflakes:ignore
25+
"""
26+
27+
resource_class_template = """{% autoescape off %}
28+
{% for model in models %}{% for import in model.imports %}{% if import.module != app_label %}
29+
from ietf.{{ import.module }}.resources import {% for name in import.names %}{% if not forloop.first %}, {%endif%}{{name}}Resource{% endfor %}{% endif %}{% endfor %}
30+
class {{model.name}}Resource(ModelResource):{% if model.foreign_keys %}{% for fk in model.foreign_keys %}
31+
{{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 %}
32+
{{fk.name|ljust:"16"}} = ToManyField({{fk.rmodel_name}}, '{{fk.name}}', null=True){% endfor %}{% endif %}
33+
class Meta:
34+
queryset = {{model.name}}.objects.all()
35+
#resource_name = '{{model.resource_name}}'
36+
filtering = { {% for name in model.plain_names %}
37+
"{{ name }}": ALL,{%endfor%}{% for name in model.fk_names%}
38+
"{{ name }}": ALL_WITH_RELATIONS,{%endfor%}{% for name in model.m2m_names %}
39+
"{{ name }}": ALL_WITH_RELATIONS,{%endfor%}
40+
}
41+
api.{{app_label}}.register({{model.name}}Resource())
42+
{% endfor %}{% endautoescape %}
43+
"""
44+
45+
def render(template, dictionary):
46+
template = Template(template, None, None)
47+
context = Context(dictionary)
48+
return template.render(context)
49+
50+
class Command(AppCommand):
51+
52+
def handle_app(self, app, **options):
53+
if app.__package__:
54+
print("\nInspecting %s .." % app.__package__)
55+
resource_file_path = os.path.join(os.path.dirname(app.__file__), "resources.py")
56+
57+
app_models = models.get_models(app)
58+
59+
app_resources = {}
60+
if os.path.exists(resource_file_path):
61+
resources = import_module("%s.resources" % app.__package__)
62+
for n,v in resources.__dict__.items():
63+
if issubclass(type(v), type(ModelResource)):
64+
app_resources[n] = v
65+
66+
do_update_resources = False
67+
for m in app_models:
68+
model_name = m.__name__
69+
rclass_name = model_name + "Resource"
70+
resource_name = m.__name__.lower()
71+
if not rclass_name in app_resources:
72+
do_update_resources = True
73+
74+
if do_update_resources:
75+
print("Updating resources.py for %s" % app.__package__)
76+
with open(resource_file_path, "a") as rfile:
77+
info = dict(
78+
app=app.__package__,
79+
app_label=app.__package__.split('.')[-1],
80+
date=datetime.datetime.now()
81+
)
82+
new_models = {}
83+
for model in app_models:
84+
model_name = model.__name__
85+
rclass_name = model_name + "Resource"
86+
resource_name = model.__name__.lower()
87+
if not rclass_name in app_resources:
88+
imports = collections.defaultdict(lambda: collections.defaultdict(list))
89+
print("Adding resource class for %s" % model_name)
90+
foreign_keys = []
91+
plain_names = []
92+
fk_names = []
93+
m2m_names = []
94+
#debug.pprint('dir(model)')
95+
for field in model._meta.fields:
96+
if isinstance(field, (models.ForeignKey, models.OneToOneField)):
97+
#debug.show('field.name')
98+
#debug.pprint('dir(field.rel.to)')
99+
#exit()
100+
rel_app=field.rel.to._meta.app_label
101+
rel_model_name=field.rel.to.__name__
102+
if rel_model_name == model_name:
103+
# foreign key to self class -- quote
104+
# the rmodel_name
105+
rmodel_name="'%s.resources.%sResource'" % (app.__package__, rel_model_name)
106+
else:
107+
rmodel_name=rel_model_name+"Resource"
108+
foreign_keys.append(dict(
109+
field=field,
110+
name=field.name,
111+
app=rel_app,
112+
module=rel_app.split('.')[-1],
113+
model=field.rel.to,
114+
model_name=rel_model_name,
115+
rmodel_name=rmodel_name,
116+
resource_name=field.rel.to.__name__.lower(),
117+
))
118+
imports[rel_app]["module"] = rel_app
119+
imports[rel_app]["names"].append(rel_model_name)
120+
fk_names.append(field.name)
121+
else:
122+
plain_names.append(field.name)
123+
m2m_keys = []
124+
for field in model._meta.many_to_many:
125+
#debug.show('field.name')
126+
#debug.pprint('dir(field.rel.to)')
127+
#exit()
128+
rel_app=field.rel.to._meta.app_label
129+
rel_model_name=field.rel.to.__name__
130+
if rel_model_name == model_name:
131+
# foreign key to self class -- quote
132+
# the rmodel_name
133+
rmodel_name="'%s.resources.%sResource'" % (app.__package__, rel_model_name)
134+
else:
135+
rmodel_name=rel_model_name+"Resource"
136+
m2m_keys.append(dict(
137+
field=field,
138+
name=field.name,
139+
app=rel_app,
140+
module=rel_app.split('.')[-1],
141+
model=field.rel.to,
142+
model_name=rel_model_name,
143+
rmodel_name=rmodel_name,
144+
resource_name=field.rel.to.__name__.lower(),
145+
))
146+
imports[rel_app]["module"] = rel_app
147+
imports[rel_app]["names"].append(rel_model_name)
148+
m2m_names.append(field.name)
149+
# some special import cases
150+
if "auth" in imports:
151+
imports["auth"]["module"] = 'utils'
152+
if "contenttypes" in imports:
153+
imports["contenttypes"]["module"] = 'utils'
154+
for k in imports:
155+
imports[k]["names"] = set(imports[k]["names"])
156+
new_models[model_name] = dict(
157+
app=app.__package__.split('.')[-1],
158+
model=model,
159+
fields=model._meta.fields,
160+
m2m_fields=model._meta.many_to_many,
161+
name=model_name,
162+
imports=[ v for k,v in imports.items() ],
163+
foreign_keys=foreign_keys,
164+
m2m_keys=m2m_keys,
165+
resource_name=resource_name,
166+
plain_names=plain_names,
167+
fk_names=fk_names,
168+
m2m_names=m2m_names,
169+
)
170+
171+
# Sort resources according to internal FK reference depth
172+
new_model_list = []
173+
# Write out classes with FKs to classes in the same module
174+
# lower or equal to 'internal_fk_count_limit. Start out
175+
# by writing only leaf classes, then increase the limit if
176+
# needed:
177+
internal_fk_count_limit = 0
178+
while len(new_models) > 0:
179+
list_len = len(new_models)
180+
#debug.show('len(new_models)')
181+
keys = new_models.keys()
182+
for model_name in keys:
183+
internal_fk_count = 0
184+
for fk in new_models[model_name]["foreign_keys"]+new_models[model_name]["m2m_keys"]:
185+
#debug.say("if statement comparison on:")
186+
#debug.show('fk["model_name"]')
187+
#debug.show('model_name')
188+
#debug.say('if fk["model_name"] in new_models and not fk["model_name"] == model_name:')
189+
if fk["model_name"] in new_models and not fk["model_name"] == model_name:
190+
#print("Not a leaf model: %s: found fk to %s" % (model_name, fk["model"]))
191+
internal_fk_count += 1
192+
if internal_fk_count <= internal_fk_count_limit:
193+
#print("Ordered: "+model_name)
194+
new_model_list.append(new_models[model_name])
195+
del new_models[model_name]
196+
if list_len == len(new_models):
197+
#debug.show('list_len, len(new_models)')
198+
print("Circular FK dependencies -- cannot order resource classes")
199+
if internal_fk_count_limit < list_len:
200+
print("Attempting a partial ordering ...")
201+
internal_fk_count_limit += 1
202+
else:
203+
print("Failed also with partial ordering, writing resource classes without ordering")
204+
new_model_list = [ v for k,v in new_models.items() ]
205+
break
206+
207+
if rfile.tell() == 0:
208+
print("Writing resource file head")
209+
rfile.write(render(resource_head_template, info))
210+
211+
info.update(dict(models=new_model_list))
212+
rfile.write(render(resource_class_template, info))
213+
else:
214+
print(" nothing to do for %s" % app.__package__)

0 commit comments

Comments
 (0)