使用Neo4J和Django创建REST API

时间:2021-08-07 23:31:01

I am trying to create a REST API with Neo4j and Django in the backend.

我正在尝试在后端创建一个带有Neo4j和Django的REST API。

The problem is that even when I have Django models using Neo4Django , I can't use frameworks like Tastypie or Piston that normally serialize models into JSON (or XML).

问题是,即使我使用Neo4Django的Django模型,我也不能使用像Tastypie或Piston这样的框架,它们通常将模型序列化为JSON(或XML)。

Sorry if my question is confusing or not clear, I am newbie to webservices.

对不起,如果我的问题令人困惑或不清楚,我是webservices的新手。

Thanks for you help

谢谢你的帮助


EDIT: So I started with Tastypie and followed the tutorial on this page http://django-tastypie.readthedocs.org/en/latest/tutorial.html. I am looking for displaying the Neo4j JSON response in the browser, but when I try to access to http://127.0.0.1:8000/api/node/?format=json I get this error instead:

编辑:所以我开始使用Tastypie并按照本页面上的教程http://django-tastypie.readthedocs.org/en/latest/tutorial.html。我正在寻找在浏览器中显示Neo4j JSON响应,但当我尝试访问http://127.0.0.1:8000/api/node/?format=json时,我得到此错误:

{"error_message": "'NoneType' object is not callable", "traceback": "Traceback (most recent call last):\n\n  File \"/usr/local/lib/python2.6/dist-packages/tastypie/resources.py\", line 217, in wrapper\n    response = callback(request, *args, **kwargs)\n\n  File \"/usr/local/lib/python2.6/dist-packages/tastypie/resources.py\", line 459, in dispatch_list\n    return self.dispatch('list', request, **kwargs)\n\n  File \"/usr/local/lib/python2.6/dist-packages/tastypie/resources.py\", line 491, in dispatch\n    response = method(request, **kwargs)\n\n  File \"/usr/local/lib/python2.6/dist-packages/tastypie/resources.py\", line 1298, in get_list\n    base_bundle = self.build_bundle(request=request)\n\n  File \"/usr/local/lib/python2.6/dist-packages/tastypie/resources.py\", line 718, in build_bundle\n    obj = self._meta.object_class()\n\nTypeError: 'NoneType' object is not callable\n"}

Here is my code :

这是我的代码:

api.py file:

class NodeResource (ModelResource): #it doesn't work with Resource neither
    class meta:
        queryset= Node.objects.all()
        resource_name = 'node'

urls.py file:

node_resource= NodeResource()

urlpatterns = patterns('',
    url(r'^api/', include(node_resource.urls)),

models.py file :

models.py文件:

class Node(models.NodeModel):
    p1 = models.StringProperty()
    p2 = models.StringProperty()

4 个解决方案

#1


6  

I would advise steering away from passing Neo4j REST API responses directly through your application. Not only would you not be in control of the structure of these data formats as they evolve and deprecate (which they do) but you would be exposing unnecessary internals of your database layer.

我建议不要直接通过你的应用程序传递Neo4j REST API响应。您不仅可以控制这些数据格式的结构,因为它们不断发展和弃用(他们这样做),但您将暴露数据库层不必要的内部结构。

Besides Neo4Django, you have a couple of other options you might want to consider. Neomodel is another model layer designed for Django and intended to act like the built-in ORM; you also have the option of the raw OGM layer provided by py2neo which may help but isn't Django-specific.

除了Neo4Django之外,您还可以考虑其他几种选择。 Neomodel是另一个为Django设计的模型层,旨在表现得像内置的ORM;你也可以选择py2neo提供的原始OGM层,这可能会有所帮助,但不是Django特有的。

It's worth remembering that Django and its plug-ins have been designed around a traditional RDBMS, not a graph database, so none of these solutions will be perfect. Whatever you choose, you're likely to have to carry out a fair amount of transformation work to create your application's API.

值得记住的是,Django及其插件是围绕传统的RDBMS而不是图形数据库设计的,因此这些解决方案都不是完美的。无论您选择什么,您都可能需要进行大量的转换工作来创建应用程序的API。

#2


3  

Django-Tastypie allows to create REST APIs with NoSQL databases as well as mentioned in http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html.

Django-Tastypie允许使用NoSQL数据库创建REST API,以及http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html中提到的。

The principle is to use tastypie.resources.Resource and not tastypie.resources.ModelResource which is SPECIFIC to RDBMS, then main functions must be redefined in order to provide a JSON with the desired parameters.

原则是使用tastypie.resources.Resource而不是tastypie.resources.ModelResource,它是SPECIFIC到RDBMS,然后必须重新定义主要功能,以便为JSON提供所需的参数。

So I took the example given in the link, modified it and used Neo4j REST Client for Python to get an instance of the db and perform requests, and it worked like a charm.

所以我采用链接中给出的示例,修改它并使用Neo4j REST Client for Python来获取数据库的实例并执行请求,它就像一个魅力。

Thanks for all your responses :)

谢谢你的所有回复:)

#3


2  

Thanks to recent contributions, Neo4django now supports Tastypie out of the box! I'd love to know what you think if you try it out.

感谢最近的贡献,Neo4django现在支持开箱即用的Tastypie!如果你尝试一下,我很想知道你的想法。

EDIT:

I've just run through the tastypie tutorial, and posted a gist with the resulting example. I noticed nested resources are a little funny, but otherwise it works great. I'm pretty sure the gents who contributed the patches enabling this support also know how to take care of nested resources- I'll ask them to speak up.

我刚刚完成了tastypie教程,并在结果示例中发布了一个要点。我注意到嵌套资源有点搞笑,但除此之外效果很好。我非常肯定那些提供补丁的人也知道如何处理嵌套资源 - 我会请他们说出来。

EDIT:

As long as relationships are specified in the ModelResource, they work great. If anyone would like to see examples, let me know.

只要在ModelResource中指定了关系,它们就能很好地工作。如果有人想看例子,请告诉我。

#4


2  

Well my answer was a bit vague so I'm gonna post how a solved the problem with some code:

好吧,我的答案有点模糊,所以我要发布如何通过一些代码解决问题:

Assume that I want to create an airport resource with some attributes. I will structure this in 3 different files (for readability reasons).

假设我想创建一个具有某些属性的机场资源。我将在3个不同的文件中构建它(出于可读性的原因)。

  • First : airport.py
  • 第一:airport.py

This file will contain all the resource attributes and a constructor too :

该文件还包含所有资源属性和构造函数:

from models import *

class Airport(object):

    def __init__ (self, iata, icao, name, asciiName, geonamesId, wikipedia, id, latitude, longitude):
        self.icao = icao
        self.iata = iata
        self.name = name
        self.geonamesId = geonamesId
        self.wikipedia = wikipedia
        self.id = id
        self.latitude = latitude
        self.longitude = longitude
        self.asciiName = asciiName

This file will be used in order to create resources.

将使用此文件来创建资源。


  • Then the second file : AirportResource.py: This file will contain the resource attributes and some basic methods depending on which request we want our resource to handle.

    然后是第二个文件:AirportResource.py:这个文件将包含资源属性和一些基本方法,具体取决于我们希望资源处理哪个请求。

    class AirportResource(Resource):
    
        iata = fields.CharField(attribute='iata')
        icao = fields.CharField(attribute='icao')
        name = fields.CharField(attribute='name')
        asciiName = fields.CharField(attribute='asciiName')
        latitude = fields.FloatField(attribute='latitude')
        longitude = fields.FloatField(attribute='longitude')
        wikipedia= fields.CharField(attribute='wikipedia')
        geonamesId= fields.IntegerField(attribute='geonamesId')
    
        class Meta:
            resource_name = 'airport'
            object_class = Airport 
            allowed_methods=['get', 'put'] 
            collection_name = 'airports'
            detail_uri_name = 'id'
    
        def detail_uri_kwargs(self, bundle_or_obj):
            kwargs = {}
            if isinstance(bundle_or_obj, Bundle):
                kwargs['id'] = bundle_or_obj.obj.id
            else:
                kwargs['id'] = bundle_or_obj.id
    
            return kwargs
    

As mentioned in the docs, if we want to create an API that handle CREATE, GET, PUT, POST and DELETE requests, we must override/implement the following methods :

正如文档中所提到的,如果我们想要创建一个处理CREATE,GET,PUT,POST和DELETE请求的API,我们必须覆盖/实现以下方法:

def obj_get_list(self, bundle, **kwargs) : to GET a list of objects

def obj_get_list(self,bundle,** kwargs):获取对象列表

def obj_get(self, bundle, **kwargs) : to GET an individual object

def obj_get(self,bundle,** kwargs):获取一个单独的对象

def obj_create(self, bundle, **kwargs) to create an object (CREATE method)

def obj_create(self,bundle,** kwargs)创建一个对象(CREATE方法)

def obj_update(self, bundle, **kwargs) to update an object (PUT method)

def obj_update(self,bundle,** kwargs)更新对象(PUT方法)

def obj_delete(self, bundle, **kwargs) to delete an object (DELETE method)

def obj_delete(self,bundle,** kwargs)删除对象(DELETE方法)

(see http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html)

Normally, in ModelResource all those methods are defined and implemented, so they can be used directly without any difficulty. But in this case, they should be customized according to what we want to do.

通常,在ModelResource中,所有这些方法都已定义和实现,因此可以毫无困难地直接使用它们。但在这种情况下,应根据我们想要的方式对其进行定制。

Let's see an example of implementing obj_get_list and obj_get :

让我们看一个实现obj_get_list和obj_get的例子:

For obj_get_list:

In ModelResource, the data is FIRSTLY fetched from the database, then it could be FILTERED according to the filter declared in META class ( see http://django-tastypie.readthedocs.org/en/latest/interacting.html). But I didn't wish to implement such behavior (get everything then filter), so I made a query to Neo4j given the query string parameters:

在ModelResource中,数据首先从数据库中获取,然后根据META类中声明的过滤器进行过滤(请参阅http://django-tastypie.readthedocs.org/en/latest/interacting.html)。但我不希望实现这样的行为(得到一切然后过滤),所以我给Neo4j查询给定查询字符串参数:

def obj_get_list(self,bundle, **kwargs):
    data=[]
    params= []
    for key in bundle.request.GET.iterkeys():
        params.append(key)
        if "search" in params :
            query= bundle.request.GET['search']
            try:
                results = manager.searchAirport(query) 
                data = createAirportResources(results)
            except Exception as e:
                raise NotFound(e)
            else:
                raise BadRequest("Non valid URL")
    return data

and for obj_get:

并为obj_get:

def obj_get(self, bundle, **kwargs):
    id= kwargs['id'] 
    try :
        airportNode = manager.getAirportNode(id)
        airport = createAirportResources([airportNode])
        return airport[0]
    except Exception as e : 
        raise NotFound(e)

and finally a generic function that takes as parameter a list of nodes and returns a list of Airport objects:

最后是一个泛型函数,它将节点列表作为参数并返回Airport对象列表:

def createAirportResources(nodes):
    data= []
    for node in nodes:
        iata = node.properties['iata']
        icao = node.properties['icao']
        name = node.properties['name']       
        asciiName = node.properties['asciiName']
        geonamesId = node.properties['geonamesId']
        wikipedia = node.properties['wikipedia']
        id = node.id
        latitude = node.properties['latitude']
        longitude = node.properties['longitude']
        airport = Airport(iata, icao, name, asciiName, geonamesId, wikipedia, id, latitude, longitude)
        data.append(airport)
    return data

  • Now the third manager.py : which is in charge of making queries to the database and returning results :
  • 现在是第三个manager.py:负责对数据库进行查询并返回结果:

First of all, I get an instance of the database using neo4j rest client framework :

首先,我使用neo4j rest客户端框架获取数据库的实例:

from neo4jrestclient.client import *
gdb= GraphDatabase("http://localhost:7474/db/data/")

then the function which gets an airport node :

那么获得机场节点的功能:

def getAirportNode(id):
    if(getNodeType(id) == type):
        n= gdb.nodes.get(id)
        return n
    else:
        raise Exception("This airport doesn't exist in the database")

and the one to perform search (I am using a server plugin, see Neo4j docs for more details):

和执行搜索的那个(我使用服务器插件,有关更多详细信息,请参阅Neo4j文档):

def searchAirport(query):
    airports= gdb.extensions.Search.search(query=query.strip(), searchType='airports', max=6)
    if len(airports) == 0:
        raise Exception('No airports match your query')
    else:
        return results

Hope this will help :)

希望这会有所帮助:)

#1


6  

I would advise steering away from passing Neo4j REST API responses directly through your application. Not only would you not be in control of the structure of these data formats as they evolve and deprecate (which they do) but you would be exposing unnecessary internals of your database layer.

我建议不要直接通过你的应用程序传递Neo4j REST API响应。您不仅可以控制这些数据格式的结构,因为它们不断发展和弃用(他们这样做),但您将暴露数据库层不必要的内部结构。

Besides Neo4Django, you have a couple of other options you might want to consider. Neomodel is another model layer designed for Django and intended to act like the built-in ORM; you also have the option of the raw OGM layer provided by py2neo which may help but isn't Django-specific.

除了Neo4Django之外,您还可以考虑其他几种选择。 Neomodel是另一个为Django设计的模型层,旨在表现得像内置的ORM;你也可以选择py2neo提供的原始OGM层,这可能会有所帮助,但不是Django特有的。

It's worth remembering that Django and its plug-ins have been designed around a traditional RDBMS, not a graph database, so none of these solutions will be perfect. Whatever you choose, you're likely to have to carry out a fair amount of transformation work to create your application's API.

值得记住的是,Django及其插件是围绕传统的RDBMS而不是图形数据库设计的,因此这些解决方案都不是完美的。无论您选择什么,您都可能需要进行大量的转换工作来创建应用程序的API。

#2


3  

Django-Tastypie allows to create REST APIs with NoSQL databases as well as mentioned in http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html.

Django-Tastypie允许使用NoSQL数据库创建REST API,以及http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html中提到的。

The principle is to use tastypie.resources.Resource and not tastypie.resources.ModelResource which is SPECIFIC to RDBMS, then main functions must be redefined in order to provide a JSON with the desired parameters.

原则是使用tastypie.resources.Resource而不是tastypie.resources.ModelResource,它是SPECIFIC到RDBMS,然后必须重新定义主要功能,以便为JSON提供所需的参数。

So I took the example given in the link, modified it and used Neo4j REST Client for Python to get an instance of the db and perform requests, and it worked like a charm.

所以我采用链接中给出的示例,修改它并使用Neo4j REST Client for Python来获取数据库的实例并执行请求,它就像一个魅力。

Thanks for all your responses :)

谢谢你的所有回复:)

#3


2  

Thanks to recent contributions, Neo4django now supports Tastypie out of the box! I'd love to know what you think if you try it out.

感谢最近的贡献,Neo4django现在支持开箱即用的Tastypie!如果你尝试一下,我很想知道你的想法。

EDIT:

I've just run through the tastypie tutorial, and posted a gist with the resulting example. I noticed nested resources are a little funny, but otherwise it works great. I'm pretty sure the gents who contributed the patches enabling this support also know how to take care of nested resources- I'll ask them to speak up.

我刚刚完成了tastypie教程,并在结果示例中发布了一个要点。我注意到嵌套资源有点搞笑,但除此之外效果很好。我非常肯定那些提供补丁的人也知道如何处理嵌套资源 - 我会请他们说出来。

EDIT:

As long as relationships are specified in the ModelResource, they work great. If anyone would like to see examples, let me know.

只要在ModelResource中指定了关系,它们就能很好地工作。如果有人想看例子,请告诉我。

#4


2  

Well my answer was a bit vague so I'm gonna post how a solved the problem with some code:

好吧,我的答案有点模糊,所以我要发布如何通过一些代码解决问题:

Assume that I want to create an airport resource with some attributes. I will structure this in 3 different files (for readability reasons).

假设我想创建一个具有某些属性的机场资源。我将在3个不同的文件中构建它(出于可读性的原因)。

  • First : airport.py
  • 第一:airport.py

This file will contain all the resource attributes and a constructor too :

该文件还包含所有资源属性和构造函数:

from models import *

class Airport(object):

    def __init__ (self, iata, icao, name, asciiName, geonamesId, wikipedia, id, latitude, longitude):
        self.icao = icao
        self.iata = iata
        self.name = name
        self.geonamesId = geonamesId
        self.wikipedia = wikipedia
        self.id = id
        self.latitude = latitude
        self.longitude = longitude
        self.asciiName = asciiName

This file will be used in order to create resources.

将使用此文件来创建资源。


  • Then the second file : AirportResource.py: This file will contain the resource attributes and some basic methods depending on which request we want our resource to handle.

    然后是第二个文件:AirportResource.py:这个文件将包含资源属性和一些基本方法,具体取决于我们希望资源处理哪个请求。

    class AirportResource(Resource):
    
        iata = fields.CharField(attribute='iata')
        icao = fields.CharField(attribute='icao')
        name = fields.CharField(attribute='name')
        asciiName = fields.CharField(attribute='asciiName')
        latitude = fields.FloatField(attribute='latitude')
        longitude = fields.FloatField(attribute='longitude')
        wikipedia= fields.CharField(attribute='wikipedia')
        geonamesId= fields.IntegerField(attribute='geonamesId')
    
        class Meta:
            resource_name = 'airport'
            object_class = Airport 
            allowed_methods=['get', 'put'] 
            collection_name = 'airports'
            detail_uri_name = 'id'
    
        def detail_uri_kwargs(self, bundle_or_obj):
            kwargs = {}
            if isinstance(bundle_or_obj, Bundle):
                kwargs['id'] = bundle_or_obj.obj.id
            else:
                kwargs['id'] = bundle_or_obj.id
    
            return kwargs
    

As mentioned in the docs, if we want to create an API that handle CREATE, GET, PUT, POST and DELETE requests, we must override/implement the following methods :

正如文档中所提到的,如果我们想要创建一个处理CREATE,GET,PUT,POST和DELETE请求的API,我们必须覆盖/实现以下方法:

def obj_get_list(self, bundle, **kwargs) : to GET a list of objects

def obj_get_list(self,bundle,** kwargs):获取对象列表

def obj_get(self, bundle, **kwargs) : to GET an individual object

def obj_get(self,bundle,** kwargs):获取一个单独的对象

def obj_create(self, bundle, **kwargs) to create an object (CREATE method)

def obj_create(self,bundle,** kwargs)创建一个对象(CREATE方法)

def obj_update(self, bundle, **kwargs) to update an object (PUT method)

def obj_update(self,bundle,** kwargs)更新对象(PUT方法)

def obj_delete(self, bundle, **kwargs) to delete an object (DELETE method)

def obj_delete(self,bundle,** kwargs)删除对象(DELETE方法)

(see http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html)

Normally, in ModelResource all those methods are defined and implemented, so they can be used directly without any difficulty. But in this case, they should be customized according to what we want to do.

通常,在ModelResource中,所有这些方法都已定义和实现,因此可以毫无困难地直接使用它们。但在这种情况下,应根据我们想要的方式对其进行定制。

Let's see an example of implementing obj_get_list and obj_get :

让我们看一个实现obj_get_list和obj_get的例子:

For obj_get_list:

In ModelResource, the data is FIRSTLY fetched from the database, then it could be FILTERED according to the filter declared in META class ( see http://django-tastypie.readthedocs.org/en/latest/interacting.html). But I didn't wish to implement such behavior (get everything then filter), so I made a query to Neo4j given the query string parameters:

在ModelResource中,数据首先从数据库中获取,然后根据META类中声明的过滤器进行过滤(请参阅http://django-tastypie.readthedocs.org/en/latest/interacting.html)。但我不希望实现这样的行为(得到一切然后过滤),所以我给Neo4j查询给定查询字符串参数:

def obj_get_list(self,bundle, **kwargs):
    data=[]
    params= []
    for key in bundle.request.GET.iterkeys():
        params.append(key)
        if "search" in params :
            query= bundle.request.GET['search']
            try:
                results = manager.searchAirport(query) 
                data = createAirportResources(results)
            except Exception as e:
                raise NotFound(e)
            else:
                raise BadRequest("Non valid URL")
    return data

and for obj_get:

并为obj_get:

def obj_get(self, bundle, **kwargs):
    id= kwargs['id'] 
    try :
        airportNode = manager.getAirportNode(id)
        airport = createAirportResources([airportNode])
        return airport[0]
    except Exception as e : 
        raise NotFound(e)

and finally a generic function that takes as parameter a list of nodes and returns a list of Airport objects:

最后是一个泛型函数,它将节点列表作为参数并返回Airport对象列表:

def createAirportResources(nodes):
    data= []
    for node in nodes:
        iata = node.properties['iata']
        icao = node.properties['icao']
        name = node.properties['name']       
        asciiName = node.properties['asciiName']
        geonamesId = node.properties['geonamesId']
        wikipedia = node.properties['wikipedia']
        id = node.id
        latitude = node.properties['latitude']
        longitude = node.properties['longitude']
        airport = Airport(iata, icao, name, asciiName, geonamesId, wikipedia, id, latitude, longitude)
        data.append(airport)
    return data

  • Now the third manager.py : which is in charge of making queries to the database and returning results :
  • 现在是第三个manager.py:负责对数据库进行查询并返回结果:

First of all, I get an instance of the database using neo4j rest client framework :

首先,我使用neo4j rest客户端框架获取数据库的实例:

from neo4jrestclient.client import *
gdb= GraphDatabase("http://localhost:7474/db/data/")

then the function which gets an airport node :

那么获得机场节点的功能:

def getAirportNode(id):
    if(getNodeType(id) == type):
        n= gdb.nodes.get(id)
        return n
    else:
        raise Exception("This airport doesn't exist in the database")

and the one to perform search (I am using a server plugin, see Neo4j docs for more details):

和执行搜索的那个(我使用服务器插件,有关更多详细信息,请参阅Neo4j文档):

def searchAirport(query):
    airports= gdb.extensions.Search.search(query=query.strip(), searchType='airports', max=6)
    if len(airports) == 0:
        raise Exception('No airports match your query')
    else:
        return results

Hope this will help :)

希望这会有所帮助:)