Skip to content

Instantly share code, notes, and snippets.

@xfenix
Last active December 22, 2015 15:29
Show Gist options
  • Save xfenix/6492944 to your computer and use it in GitHub Desktop.
Save xfenix/6492944 to your computer and use it in GitHub Desktop.
Outputs tree resource (add /tree to resource url), cached
class ExtjsTreeResource(ExtJsModelResource):
max_limit = 1000
cache_group = ''
cache_ttl = 3600
cache_url_params = ['offset', 'page', 'limit', 'node']
tree_parent_key = ''
tree_childs_key = 'childs'
tree_id_key = 'id'
def prepend_urls(self):
return [
url(
r"^(?P<resource_name>%s)/tree%s$" % (
self._meta.resource_name,
trailing_slash()
),
self.wrap_view('get_tree'),
name='api_get_tree'
),
]
def build_prepare(self):
self.tree_items = {}
self.tree_parents = {}
self.tree_roots = {}
def build_tree_wrap(self, items, request):
"""
Wrapper for build tree
This is necessary method, since we do not
know which of the items is "root" (because offset and etc),
and which leaves are lost
"""
tree = []
tree_ids = []
for item in items:
pk = item.pk
tree_ids.append(pk)
self.tree_items[pk] = item
self.tree_parents[pk] = self.get_parent(item)
# check potential roots
for key, item in self.tree_items.items():
parent = self.tree_parents[key]
if parent is None or parent not in tree_ids:
self.tree_roots[item.pk] = item
# build tree branches
for item in self.tree_roots.values():
tree.append(
self.build_tree_item(item, request)
)
return tree
def build_tree(self, request, root):
"""
Recursive tree builder
"""
tree = []
for item in self.tree_items.values():
if self.tree_parents[item.pk] == root:
tree.append(self.build_tree_item(item, request))
return tree
def build_tree_item(self, item, request):
"""
Build current tree item with children
"""
preprocessed = self.full_dehydrate(
self.build_bundle(
obj=item,
request=request
),
for_list=True
)
processed = preprocessed.data
try:
del self.tree_items[item.pk]
del self.tree_roots[item.pk]
except:
pass
processed[self.tree_childs_key] = self.build_tree(request, item.pk)
processed['leaf'] = False if processed[self.tree_childs_key] else True
return processed
def build_filters(self, filters=None):
if 'id' in filters:
if filters[self.tree_id_key] == 'root':
del filters[self.tree_id_key]
else:
tmp = filters[self.tree_id_key]
try:
tmp = tmp.split('/')[-2]
except:
try:
tmp = int(tmp)
except:
tmp = 0
filters[self.tree_id_key] = tmp
build = super(ExtjsTreeResource, self).build_filters(filters)
return build
def apply_filters(self, request, applicable_filters):
key = '%s__exact' % self.tree_id_key
branch = None
if key in applicable_filters:
now = int(applicable_filters[key])
del applicable_filters[key]
tree = [item for item in self._meta.queryset.all()]
branch = self.get_branch(now, tree)
# branch = self.get_branch(now)
qset = super(ExtjsTreeResource, self).apply_filters(
request, applicable_filters
)
return qset.filter(pk__in=branch) if branch else qset
def get_parent(self, item):
return getattr(
getattr(item, self.tree_parent_key, None), 'pk', None
)
def get_branch(self, pk, tree):
"""
Calculate all children of item with
primary key = pk, and return full list of
their primary keys
"""
branch = []
for item in tree:
if self.get_parent(item) == pk:
branch.append(item.pk)
branch = branch + self.get_branch(item.pk, tree)
return branch
def get_cache_key(self, request):
parts = []
params = self.cache_url_params + [self.tree_id_key]
for param in params:
if param in request.GET:
parts.append(param + '_' + str(request.GET[param]))
return ''.join([self.cache_group, str(self._meta.max_limit)] + parts)
def get_tree(self, request, **kwargs):
"""
Main method
"""
# proxy POST requests (creation)
if request.META['REQUEST_METHOD'] == 'POST':
return self.post_list(request, **kwargs)
# check cache, if exists, return it back
cache = CacheGroup(self.cache_group, self.cache_ttl)
ckey = self.get_cache_key(request)
cached_response = cache.get(ckey)
if cached_response:
return cached_response
"""
This part of code actually copy of tastypie's get_list
You can find this method here, on line 1254 (actual at 12.09.13):
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/resources.py#L1254
"""
self.method_check(request, allowed=['get'])
# self.is_authenticated(request)
self.throttle_check(request)
self.build_prepare()
self._meta.max_limit = self.max_limit
cname = self._meta.collection_name
paginator = self._meta.paginator_class(
request.GET,
self.apply_sorting(
self.obj_get_list(
bundle=self.build_bundle(request=request),
**self.remove_api_resource_names(kwargs)
),
options=request.GET
),
resource_uri=self.get_resource_uri(),
limit=self._meta.max_limit,
max_limit=self._meta.max_limit,
collection_name=cname
)
serialize = paginator.page()
serialize[cname] = self.build_tree_wrap(serialize[cname], request)
serialize = self.alter_list_data_to_serialize(request, serialize)
self.log_throttled_access(request)
response = self.create_response(request, serialize)
cache.set(ckey, response)
return response
def wrap_obj_invalidate(self, obj, bundle, **kwargs):
CacheGroup.invalidate(self.cache_group)
method = getattr(super(ExtjsTreeResource, self), 'obj_' + obj)
return method(bundle, **kwargs)
def obj_create(self, bundle, **kwargs):
return self.wrap_obj_invalidate('create', bundle, **kwargs)
def obj_update(self, bundle, **kwargs):
return self.wrap_obj_invalidate('update', bundle, **kwargs)
def obj_delete(self, bundle, **kwargs):
return self.wrap_obj_invalidate('delete', bundle, **kwargs)
# db version of get branch
# def get_branch(self, id):
# q = self._meta.queryset.filter(**{self.tree_parent_key: id})
# branch = []
# if q:
# for item in q:
# branch.append(item.pk)
# branch = branch + self.get_branch(item.pk)
# return branch
# else:
# return []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment