Esempio n. 1
0
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
Esempio n. 2
0
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')
Esempio n. 3
0
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