def render(self, context): var = self.var.resolve(context) request = context["request"] if self.name in request.GET: var = deepmerge(var, json.loads(request.GET[self.name])) elif self.name in context: var = deepmerge(var, context[self.name]) context[self.name] = var return ""
def test_key_order(self): # Delta key range does not span source key range. Source order wins. source = OrderedDict([ ("one", "One"), ("two", "Two"), ("three", "Three"), ]) delta = OrderedDict([("three", "Three X"), ("two", "Two X")]) result = deepmerge(source, delta) self.assertEqual(["one", "two", "three"], list(result.keys())) # Delta key range spans source key range. Delta order wins. source = OrderedDict([ ("one", "One"), ("two", "Two"), ("three", "Three"), ]) delta = OrderedDict([("three", "Three X"), ("two", "Two X"), ("one", "One X"), ("four", "Four X")]) result = deepmerge(source, delta) self.assertEqual(["three", "two", "one", "four"], list(result.keys()))
def test_list_with_nones(self): """`None` values are discarded from lists. """ source = { "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, { "actor": { "name": "Denzel", "surname": "Washington" } }, ], } delta = { "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, None, ], } result = deepmerge(source, delta) self.assertEqual( result, {"three": [{ "actor": { "name": "Tom", "surname": "Hanks" } }]})
def test_arbitrary_keys(self): """Introduce keys that are not in source. """ source = { "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, ], } delta = { "three": [ { "actor": { "name": "Tom", "surname": "Hanks", "age": 50 } }, ], "four": 1 } result = deepmerge(source, delta) self.assertEqual( result, { "three": [{ "actor": { "name": "Tom", "surname": "Hanks", "age": 50 } }], "four": 1 })
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
def test_deepmerge(self): source = { "one": { "aaa": 1, "bbb": 2 }, "two": [1, 2, 3], "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, { "actor": { "name": "Denzel", "surname": "Washington" } }, ], "four": [ { "actor": { "name": "Alec", "surname": "Baldwin" } }, { "actor": { "name": "Brad", "surname": "Pitt" } }, ], "five": [{ "movie": { "title": "Good Will Hunting", "actors": [{ "actor": { "name": "Ben", "surname": "Affleck" } }] } }], "six": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, { "actor": { "name": "Denzel", "surname": "Washington" } }, ], } delta = { "one": { "bbb": 777, "ccc": 888 }, "two": [3, 4, 5], "three": { "actor": { "name": "Colin" } }, "four": [{ "actor": { "name": "Stephen" } }, { "actor": { "name": "Harrison", "surname": "Ford" } }, { "actor": { "name": "William" } }], "five": [{ "movie": { "title": "Good Will Hunting", "actors": [{ "actor": { "name": "Matt", "surname": "Damon" } }, { "actor": { "name": "Casey" } }] } }], "six": [{ "archetype": False, "director": { "name": "Guillermo" } }, { "score": { "name": "Hans" } }], "infinity": { "a": 0 } } result = deepmerge(source, delta) self.assertEqual( { "one": { "aaa": 1, "bbb": 777, "ccc": 888 }, "two": [3, 4, 5], "three": [{ "actor": { "name": "Colin", "surname": "Hanks" } }], "four": [{ "actor": { "name": "Stephen", "surname": "Baldwin" } }, { "actor": { "name": "Harrison", "surname": "Ford" } }, { "actor": { "name": "William", "surname": "Baldwin" } }], "five": [{ "movie": { "title": "Good Will Hunting", "actors": [{ "actor": { "name": "Matt", "surname": "Damon" } }, { "actor": { "name": "Casey", "surname": "Affleck" } }] } }], "six": [{ "archetype": False, "director": { "name": "Guillermo" } }, { "score": { "name": "Hans" } }], "infinity": { "a": 0 } }, result)
def test_deepmerge_nones(self): # If both source and delta are None the result must be None source = None delta = None result = deepmerge(source, delta) assert (result, None) # If delta is None, we want to retain source source = { "one": { "aaa": 1, "bbb": 2 }, "two": [1, 2, 3], "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, { "actor": { "name": "Denzel", "surname": "Washington" } }, ], "four": [ { "actor": { "name": "Alec", "surname": "Baldwin" } }, { "actor": { "name": "Brad", "surname": "Pitt" } }, ], "five": [{ "movie": { "title": "Good Will Hunting", "actors": [{ "actor": { "name": "Ben", "surname": "Affleck" } }] } }] } delta = None result = deepmerge(source, delta) self.assertEqual(result, source, "Result must be == source!") # If source is None we want to retain source source = None delta = { "one": { "aaa": 1, "bbb": 2 }, "two": [1, 2, 3], "three": [ { "actor": { "name": "Tom", "surname": "Hanks" } }, { "actor": { "name": "Denzel", "surname": "Washington" } }, ], "four": [ { "actor": { "name": "Alec", "surname": "Baldwin" } }, { "actor": { "name": "Brad", "surname": "Pitt" } }, ], "five": [{ "movie": { "title": "Good Will Hunting", "actors": [{ "actor": { "name": "Ben", "surname": "Affleck" } }] } }] } result = deepmerge(source, delta) self.assertEqual(result, source, "Result must be == None!")