def alter_detail_data_to_serialize(self, request, bundle, nested=False): model = self.get_model() # Get relationships fields fields = [ f for f in model._meta.fields if f.get_internal_type() == 'Relationship'] node_rels = bundle.obj.node.relationships.all() # If the nested parameter is True, this set node_to_retreive = set() # Resolve relationships manualy for field in fields: # Get relationships for this fields field_rels = [ rel for rel in node_rels[:] if rel.type == field._type] # Filter relationships to keep only the well oriented relationships # get the related field informations related_field = [f for f in iterate_model_fields(model) if "rel_type" in f and f["rel_type"] == field._type and "name" in f and f["name"] == field._BoundRelationship__attname] if related_field: # Note (edouard): check some assertions in case I forgot something assert len(related_field) == 1, related_field assert related_field[0]["direction"] # choose the end point to check end_point_side = "start" if related_field[0]["direction"] == "out" else "end" # filter the relationship field_rels = [rel for rel in field_rels if getattr(rel, end_point_side).id == bundle.obj.id] # Get node ids for those relationships field_oposites = [ graph.opposite(rel, bundle.obj.id) for rel in field_rels ] # Save the list into properities bundle.data[field.name] = field_oposites # Nested mode to true: we need to retreive every node if nested: node_to_retreive = set(list(node_to_retreive) + field_oposites) # There is node to extract for the graph if len(node_to_retreive): # Build the query to get all node in one request query = "start n=node(%s) RETURN ID(n), n" % ",".join(map(str, node_to_retreive)) # Get all nodes as raw values to avoid unintended request to the graph nodes = connection.query(query, returns=(int, dict)) # Helper lambda to retreive a node retreive_node = lambda idx: next(n[1]["data"] for n in nodes if n[0] == idx) # Populate the relationships field with there node instance for field in fields: # Retreive the list of ids for i, idx in enumerate(bundle.data[field.name]): rel_node = retreive_node(idx) # Save the id which is not a node property rel_node["id"] = idx # Update value bundle.data[field.name][i] = self.validate(rel_node, field.target_model, allow_missing=True) # Show additional field following the model's rules rules = request.current_topic.get_rules().model(self.get_model()).all() # All additional relationships for key in rules: # Filter rules to keep only Neomatch instance. # Neomatch is a class to create programmaticly a search related to # this node. if isinstance(rules[key], Neomatch): bundle.data[key] = rules[key].query(bundle.obj.id) return bundle
def get_patch(self, request, **kwargs): pk = kwargs["pk"] # This should be a POST request self.method_check(request, allowed=['post']) self.throttle_check(request) # User must be authentication self.is_authenticated(request) bundle = self.build_bundle(request=request) # User allowed to update this model self.authorized_update_detail(self.get_object_list(bundle.request), bundle) # Get the node's data using the rest API try: node = connection.nodes.get(pk) # Node not found except client.NotFoundError: raise Http404("Not found.") # Load every relationship only when we need to update a relationship node_rels = None # Parse only body string body = json.loads(request.body) if type(request.body) is str else request.body # Copy data to allow dictionary resizing data = body.copy() # Received per-field sources if "field_sources" in data: # field_sources must not be treated here, see patch_source method field_sources = data.pop("field_sources") # Validate data. # If it fails, it will raise a ValidationError data = self.validate(data) # Get author list (or a new array if ) author_list = node.properties.get("_author", []) # This is the first time the current user edit this node if int(request.user.id) not in author_list: # Add the author to the author list data["_author"] = author_list + [request.user.id] # @TODO check that 'node' is an instance of 'model' # Set new values to the node for field_name in data: field = self.get_model_field(field_name) field_value = data[field_name] # The value can be a list of ID for relationship if field.get_internal_type() is 'Relationship': # Pluck id from the list field_ids = [ value for value in field_value if value is not int(pk) ] # Prefetch all relationship if node_rels is None: node_rels = node.relationships.all() # Get relationship name rel_type = self.get_model_field(field_name)._type # We don't want to add this relation twice so we extract # every node connected to the current one through this type # of relationship. "existing_rels_id" will contain the ids of # every node related to this one. existing_rels = [ rel for rel in node_rels if rel.type == rel_type ] existing_rels_id = [ graph.opposite(rel, pk) for rel in existing_rels ] # Get every ids from "field_ids" that ain't not in # the list of existing relationship "existing_rel_id". new_rels_id = set(field_ids).difference(existing_rels_id) # Get every ids from "existing_rels_id" that ain't no more # in the new list of relationships "field_ids". old_rels_id = set(existing_rels_id).difference(field_ids) # Start a transaction to batch import values with connection.transaction(commit=False) as tx: # Convert ids or related node to *node* instances new_rels_node = [ connection.nodes.get(idx) for idx in new_rels_id ] # Convert ids or unrelated node to *relationships* instances old_rels = [] # Convert ids list into relationship instances for idx in old_rels_id: # Find the relationship that match with this id matches = [ rel for rel in existing_rels if graph.connected(rel, idx) ] # Merge the list of relationships old_rels = old_rels + matches # Commit change when every field was treated tx.commit() # Start a transaction to batch insert/delete values with connection.transaction(commit=False) as tx: # Then create the new relationships (using nodes instances) # Outcoming relationship if field.direction == 'out': [ connection.relationships.create(node, rel_type, n) for n in new_rels_node ] # Incoming relationship elif field.direction == 'in': [ connection.relationships.create(n, rel_type, node) for n in new_rels_node ] # Then delete the old relationships (using relationships instance) [ rel.delete() for rel in old_rels ] # Commit change when every field was treated tx.commit() # Or a literal value # (integer, date, url, email, etc) else: # Current model model = self.get_model() # Fields fields = { x['name'] : x for x in iterate_model_fields(model) } # Remove the values if field_value in [None, '']: if field_name == 'image' and fields[field_name]['type'] == 'URLField': self.remove_node_file(node, field_name, True) # The field may not exists (yet) try: node.delete(field_name) # It's OK, it just means we don't have to remove it except client.NotFoundError: pass # We simply update the node property # (the value is already validated) else: if field_name in fields: if 'is_rich' in fields[field_name]['rules'] and fields[field_name]['rules']['is_rich']: data[field_name] = field_value = bleach.clean(field_value, tags=("br", "blockquote", "ul", "ol", "li", "b", "i", "u", "a", "p"), attributes={ '*': ("class",), 'a': ("href", "target") }) if field_name == 'image' and fields[field_name]['type'] == 'URLField': self.remove_node_file(node, field_name, True) try: image_file = download_url(data[field_name]) path = default_storage.save(os.path.join(settings.UPLOAD_ROOT, image_file.name) , image_file) data[field_name] = field_value = path.replace(settings.MEDIA_ROOT, "") except UnavailableImage: data[field_name] = field_value = "" except NotAnImage: data[field_name] = field_value = "" except OversizedFile: data[field_name] = field_value = "" node.set(field_name, field_value) # update the cache topic_cache.incr_version(request.current_topic) # And returns cleaned data return self.create_response(request, data)