Question

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

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).

Sorry if my question is confusing or not clear, I am newbie to 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:

{"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 :

class Node(models.NodeModel):
    p1 = models.StringProperty()
    p2 = models.StringProperty()
Était-ce utile?

La solution 2

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.

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.

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.

Thanks for all your responses :)

Autres conseils

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.

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.

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.

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.

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.

EDIT:

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

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).

  • First : 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.

    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 :

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

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

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

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

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

(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.

Let's see an example of implementing obj_get_list and 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:

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:

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:

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 :

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

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):

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 :)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top