def get_instance(parent=None, parent_id=None, resource=None, instance=None, config=None, user=None, depth=0): """ Handles instances. Returns a dictionary of: - metadata about the instance; - the fields the user is entitled to GET, - relations (related resources) If the method for any relation is anything but GET we don't recurse into it. Note that the config has been helpfully pre-tapered down to just a single bit for each field and method. So at this level, we don't 'know' if the user is owner, group, world and what group. Doesn't matter, just do your work, you function. Some day, when we're not relying on Django model/instance methods, we can pass dictionaries around instead of instances and be, perhaps, more efficient with the database. Someday. """ allowed = permit.allowed_methods_of(config[resource]["methods"])["allowed"] # If we called this directly, we want to remove a redundant 'GET' # from the list of methods we'll call to the user. if not depth: allowed.remove("GET") # Even an empty set will return this: result = {"meta": {}, "fields": {}, "relations": {}} parent_url = parent and "/%s/%s" % (parent, parent_id) or "" # We now remake the config from the user. config = help.instance_permissions(user, instance) result["meta"] = { "url": "%s%s" % (parent_url, instance.url), "methods": allowed, "tagged_with": [resource], "resource": resource, } # Now we sort the config into the kind of dictionary we are looking # for: viewmap = help.viewmap_of(resource, config) # And now, the "I'd like to thank my parents" clause. # There are two reasons why we might stuff a link to the parent in this dictionary. # One is that this is exactly the resource asked for in the user's # request. The other is that the instance's parent is not the parent # sent to this function. if instance.parent_instance: parent_resource = instance.parent_instance._meta.verbose_name_plural if (not depth) or (parent_resource != parent): result["meta"]["parent"] = { "resource": parent_resource, "title": str(instance.parent_instance), "url": instance.parent_instance.url, } viewmap["traversals"] = [t for t in viewmap["traversals"] if t["resource"] != parent_resource] result["fields"] = {f: getattr(instance, f) for f in viewmap["fields"]["read"]} # Now we add the traversables and, if we have more to go, fire off a # call for each one: forward = help.valid_traversals(resource, config) for t in viewmap["traversals"]: if "GET" in t["methods"]: target_resource = t["resource"] is_collection = target_resource in forward["many"].keys() if VIEW_TRAVERSE_DEPTH - depth: if is_collection: opts = { "parent": resource, "parent_id": instance.pk, "resource": target_resource, "user": user, "config": config, "depth": depth + 1, } relation = get_collection(**opts) else: target_instance = getattr(instance, target_resource) opts = { "resource": reverbose(target_resource), "instance": target_instance, "user": user, "config": config, "depth": depth + 1, } relation = get_instance(**opts) else: # if we have no further loops to do, we'll just put a link to # the traversal. target = getattr(instance, target_resource) if is_collection: relation = {"meta": {"url": target.url(), "title": str(target), "methods": t["methods"]}} else: relation = {"meta": {"url": str(getattr(target, "url")), "title": str(target), "methods": ["GET"]}} result["relations"][target_resource] = relation result["depth left"] = VIEW_TRAVERSE_DEPTH - depth return result
def handler(request, parent=None, parent_id=None, resource=None, resource_id=None): """Our job is to thoroughly screen the request so we can hand it to our methods freely. """ # Until the day we hook Google's auth into our middleware: request.user = CustomUser.objects.all()[0] logging.info('User: %s, groups: %s' % (str(request.user), str(request.user.memberships.all()))) # Are these even in our defined list of resources? if resource not in RESOURCES or (parent and parent not in RESOURCES): raise Http404 # Does this parent (if there is one) even exist? if parent: try: parent_inst = model_for(parent).objects.get(pk=parent_id) except ObjectDoesNotExist: raise Http404 # We'll see if the parent and child are even in the same family tree. if not same_tree(request.user, parent_inst): raise Http404 # We need to see if the user is allowed to GET the parent_inst # because we will allow the resource to reveal some # information about its parent. conf = instance_permissions(request.user, parent_inst) if not conf[parent]['methods']['GET'] & 1: raise PermissionDenied else: conf = instance_permissions(request.user) # Now let's see if we can go from the parent (or index) # to this resource. traversals = sorted_traversals(resource=(parent or 'index'), config=conf) if not resource in traversals.get('many', []): raise PermissionDenied opts = {'resource': resource, 'user': request.user, 'parent': parent, 'parent_id': parent_id, 'config': conf } if resource_id: try: inst = model_for(resource).objects.get(pk=resource_id) except ObjectDoesNotExist: raise Http404 if not same_tree(request.user, inst): raise Http404 # Time to retrieve the user's permissions for this instance. conf = instance_permissions(request.user, inst) logging.info('config for resource %s: %s' % (resource, conf[resource])) # Now we find out of a user in this role can do this method to this instance. allowed = permit.allowed_methods_of(conf[resource]['methods'])['allowed'] if not request.method in allowed: logger.error('Got resource_id, not allowed from method %s, allowed=%s' % (request.method, allowed)) raise PermissionDenied if request.method == 'GET': opts['instance'] = inst opts['config'] = conf requested_form = request.GET.get('form') # We may be getting a request for a form, for PUTting or DELETEing. # If the user couldn't do this method anyway we just ignore the # QueryDict. if requested_form == 'edit' and 'PUT' in allowed: context = methods.get_put_form(**opts) template = 'put_form.html' if requested_form == 'delete' and 'DELETE' in allowed: context = methods.get_delete_form(**opts) template = 'delete_form.html' else: context = methods.get_instance(**opts) template = '%s_detail.html' % resource if request.method == 'PUT': pass if request.method == 'DELETE': pass else: if request.method == 'GET': if request.GET.get('form') == 'create': context = methods.get_post_form(**opts) template = 'post_form.html' else: context = methods.get_collection(**opts) # magic naming again. template = '%s_in_%s.html' % (resource, parent or 'index') if request.method == 'POST': # We'll either get a redirect or a response containing a new form. opts['request'] = request opts['parent_instance'] = parent and parent_inst or None post_result = methods.post_to_collection(**opts) # post_result sends a tuple of (True/False, context). template = post_result[0] and '%s_in_%s.html' % (resource, parent or 'index')\ or\ 'post_form.html' context = post_result[1] return HttpResponse(render(request, template, context), mimetype='text/html')
def get_collection(parent=None, parent_id=None, resource=None, user=None, config=None, depth=0): """ Requires a resource with a parent scope (which may be index if provided by the url router). Parent must come with an id. We set the depth to track our recursions and condition the views (deeper calls generally return less). User is a User instance. We will only recurse on GET (and perhaps someday OPTIONS), because it is idempotent. All the legal checks are in the handler. Once we recurse we assume our configuration is only throwing valid responses. """ allowed = permit.methods_for_traversal(parent or "index", resource, config) if not depth: # Since we're already GETting this, no need to offer it to the client # again. allowed.remove("GET") this_model = help.model_for(resource) # We get to use the model's manager directly if it's the root, # or if there is a parent we use the parent's manager. if parent: # And now a bit of magic naming. We expect the verbose name of the resource # to be an attribute of the parent model. parent_model = help.model_for(parent) parent_inst = parent_model.objects.get(pk=parent_id) collection = getattr(parent_inst, resource) else: collection = this_model.collection # Now we need our query filter. q = help.build_Q_set(user, resource) result = { "meta": { "resource": resource, "url": collection.url(), "methods": [m for m in allowed], "count": collection.count(), } } if parent and not depth: result["traversals"] = [{"url": parent_inst.url, "method": "GET"}] if VIEW_TRAVERSE_DEPTH - depth: # We don't retrieve collection.all() here. We have to figure: logger.info("q = %s" % str(q)) if q: instances = collection.filter(q).all() else: instances = collection.all() logger.info("The query was = %s" % str(instances.query)) result["objects"] = [ get_instance( resource=resource, instance=inst, parent=parent, parent_id=parent_id, user=user, config=help.instance_permissions(user, inst), depth=depth + 1, ) for inst in instances ] return result