def get(self, request, *args, **kwargs): try: project_id = request.GET["project_id"] except (KeyError, MultiValueDictKeyError): project_id = None try: calls = json.loads(request.GET["calls"]) except ValueError: return HttpResponseBadRequest() results = [] for call in calls: obj = get_object_by_dotted_name(call["id"], project_id=project_id) view_klass = globals()[obj.__class__.__name__ + "Detail"] view = view_klass().as_view()(request=request, object=obj, data=call["data"], format="json") if view.status_code == 500: return HttpResponseServerError(view.content) rendered = view.render().content results.append(json.loads(rendered)) return JsonResponse({"results": results})
def _get_children(self, klass, subdirectory=""): """Helper method to fetch children of a certain type""" li = [] # Our own t = select_template([self.project.relative_path + "metadata.json"]) pth = os.path.join(os.path.dirname(t.template.origin.name), "..", self.relative_path, subdirectory) if os.path.exists(pth): li = [ klass(id, self) for id in os.listdir(pth) if not id.startswith(".") and not id in RESERVED_IDS ] # Ask parents attr = klass.__name__.lower() + "s" for project_id in reversed(self.project.metadata.get("parents", [])): obj = get_object_by_dotted_name( ".".join([str(project_id)] + self.dotted_name.split(".")[1:])) li.extend(getattr(obj, attr)) # Remove duplicates processed = [] result = [] for l in li: if l.id not in processed: result.append(l) processed.append(l.id) return sorted(result, key=lambda item: item.metadata.get("position"))
def get(self, request, *args, **kwargs): try: calls = json.loads(request.GET["calls"]) except ValueError: return HttpResponseBadRequest() results = [] for call in calls: obj = get_object_by_dotted_name(call["id"]) view_klass = globals()[obj.__class__.__name__ + "Detail"] view = view_klass().as_view()(request=request, object=obj, data=call["data"], format="json") rendered = view.render().content results.append(json.loads(rendered)) return JsonResponse({"results": results})
def test_metadata_panel(self): """We inherit panel fully""" obj = get_object_by_dotted_name("myprojectchild.website.atoms.panel") self.assertEqual(obj.metadata["title"], "Panel")
def test_metadata_anchor(self): """We override anchor element but not data""" obj = get_object_by_dotted_name("myprojectchild.website.atoms.anchor") self.assertEqual(obj.metadata["title"], "Anchor")
def test_metadata_label(self): """We override label element and data""" obj = get_object_by_dotted_name("myprojectchild.website.atoms.label") self.assertEqual(obj.metadata["title"], "MyProjectChild label")
def render(self, context): # We must import late from mote.models import Project, Aspect, Pattern, Element, Variation element_or_identifier = self.element_or_identifier.resolve(context) # If element_or_identifier is a string convert it if isinstance(element_or_identifier, string_types): # The "self" project triggers a project lookup. It first checks for # a context variable (used internally by the Mote explorer) then # for a setting (used when calling render_element over the API). if element_or_identifier.startswith("self."): project_id = context.get("__mote_project_id__", None) if project_id is None: try: value = settings.MOTE["project"] except (AttributeError, KeyError): raise RuntimeError( "Define MOTE[\"project\"] setting for project lookup" ) if callable(value): project_id = value(context["request"]) else: project_id = value obj = get_object_by_dotted_name( element_or_identifier.replace("self.", project_id + ".")) else: obj = get_object_by_dotted_name(element_or_identifier) if not isinstance(obj, (Element, Variation)): raise template.TemplateSyntaxError("Invalid identifier %s" % element_or_identifier) else: obj = element_or_identifier # Set the object in the context as "element" with context.push(): context["element"] = obj # Resolve the kwargs resolved = {} for k, v in self.kwargs.items(): try: r = v.resolve(context) except VariableDoesNotExist: continue if isinstance(r, Promise): r = text_type(r) # Strings may be interpreted further if isinstance(r, string_types): # Attempt to resolve any variables by rendering t = template.Template(r) raw_struct = t.render(context) # Attempt to convert to JSON try: resolved[k] = json.loads(raw_struct) except ValueError: resolved[k] = r else: resolved[k] = r # Find the correct view and construct view kwargs view_kwargs = dict( project=obj.project.id, aspect=obj.aspect.id, pattern=obj.pattern.id, ) if isinstance(obj, Variation): view = VariationPartialView view_kwargs.update( dict(element=obj.element.id, variation=obj.id)) else: view = ElementPartialView view_kwargs.update(dict(element=obj.id)) # Compute a cache key before we pop from resolved li = [obj.modified, deephash(resolved)] li.extend(frozenset(sorted(view_kwargs.items()))) hashed = md5(u":".join([text_type(l) for l in li]).encode("utf-8")).hexdigest() cache_key = "render-element-%s" % hashed cached = cache.get(cache_key, None) if cached is not None: return cached # Automatically perform masking with the default data request = context["request"] masked = obj.data # Omit top-level key if masked: masked = masked[list(masked.keys())[0]] if "data" in request.GET: masked = deepmerge(masked, json.loads(request.GET["data"])) elif "data" in resolved: masked = deepmerge(masked, resolved.pop("data")) context["data"] = masked # Construct a final kwargs that includes the context final_kwargs = context.flatten() del final_kwargs["request"] final_kwargs.update(resolved) final_kwargs.update(view_kwargs) # Call the view. Let any error propagate. result = view.as_view()(request, **final_kwargs) if isinstance(result, TemplateResponse): # The result of a generic view result.render() html = result.rendered_content elif isinstance(result, HttpResponse): # Old-school view html = result.content # Make output beautiful for Chris if not settings.DEBUG: beauty = BeautifulSoup(html, "html.parser") html = beauty.prettify() cache.set(cache_key, html, 300) return html
def render(self, context): # We must import late from mote.models import Project, Aspect, Pattern, Element, Variation # To keep templates as simple as possible we don't require quotes to # denote a string. That requires special handling. try: element_or_identifier = self.element_or_identifier.resolve(context) except template.VariableDoesNotExist: element_or_identifier = \ context["element"].data[self.element_or_identifier.var] data = OrderedDict() if self.data: data = self.data.resolve(context) # Shortcut notation allows an element to be looked up from data if isinstance(element_or_identifier, dict): copied = deepcopy(element_or_identifier) element_or_identifier = copied.pop("id") data = copied # If element_or_identifier is a string convert it if isinstance(element_or_identifier, string_types): # The "self" project triggers a project lookup. It first checks for # a context variable (used internally by the Mote explorer) then # for a setting (used when calling render over the API). if element_or_identifier.startswith("self."): project_id = context.get("__mote_project_id__", None) if project_id is None: try: value = settings.MOTE["project"] except (AttributeError, KeyError): raise RuntimeError( "Define MOTE[\"project\"] setting for project lookup" ) if callable(value): project_id = value(context["request"]) else: project_id = value obj = get_object_by_dotted_name( element_or_identifier.replace("self.", project_id + ".")) # Non-self lookup else: obj = get_object_by_dotted_name(element_or_identifier) # Type check if not isinstance(obj, (Element, Variation)): raise template.TemplateSyntaxError("Invalid identifier %s" % element_or_identifier) elif isinstance(element_or_identifier, (Element, Variation)): obj = element_or_identifier data = context.get("data") else: raise RuntimeError("Cannot identify %r" % element_or_identifier) with context.push(): # We use a completely clean context to avoid leakage newcontext = {} # Set the object in the new context as "element" newcontext["element"] = obj # Convert self.data if possible if isinstance(data, Promise): data = text_type(data) # Strings may be interpreted further if isinstance(data, string_types): # Attempt to resolve any variables by rendering t = template.Template(data) raw_struct = t.render(context) # Attempt to convert to JSON try: data = json.loads(raw_struct) except ValueError: pass # Find the correct view and construct view kwargs view_kwargs = dict( project=obj.project.id, aspect=obj.aspect.id, pattern=obj.pattern.id, ) if isinstance(obj, Variation): view = VariationPartialView view_kwargs.update( dict(element=obj.element.id, variation=obj.id)) else: view = ElementPartialView view_kwargs.update(dict(element=obj.id)) # Compute a cache key li = [obj.checksum, deephash(data)] li.extend(frozenset(sorted(view_kwargs.items()))) hashed = md5(":".join([text_type(l) for l in li]).encode("utf-8")).hexdigest() cache_key = "render-element-%s" % hashed cached = cache.get(cache_key, None) if cached is not None: return cached # Automatically perform masking with the default data request = context["request"] masked = obj.data if "data" in request.GET: masked = deepmerge(masked, json.loads(request.GET["data"])) elif data: masked = deepmerge(masked, data) # Set data on new context. Also set the keys in data directly on # new context to make for cleaner templates. newcontext["data"] = masked for k, v in masked.items(): if k in ("data", "element", "original_element", "pretty_data"): raise RuntimeError("%s is a reserved key" % k) newcontext[k] = v # Update new context with the view kwargs newcontext.update(view_kwargs) # Make data legible in debug mode if settings.DEBUG: newcontext["pretty_data"] = json.dumps(newcontext["data"], indent=4) # Call the view. Let any error propagate. result = view.as_view()(request, **newcontext) if isinstance(result, TemplateResponse): # The result of a generic view result.render() html = result.rendered_content elif isinstance(result, HttpResponse): # Old-school view html = result.content # Make output beautiful for Chris. This is expensive but required # for production. Make it togglable for develop. if not settings.DEBUG or request.GET.get("beautify", False): beauty = BeautifulSoup(html, "html.parser") html = beauty.prettify() # Useful debug info if settings.DEBUG: html = "<!-- " + obj.dotted_name + " -->" + html cache.set(cache_key, html, 300) return html