I am trying to create a very simple custom view for my application.
Lets imagine I have a simply model:
class Person(models.Model):
Name = models.CharField(max_length = 255)
pass
class Company(models.Model):
Title = models.CharField(max_length = 255)
pass
I would like to display a list of objects. On one url - list of person, on another - list of companies. So I create simply views:
def PersonListView (request):
_persons = Person.objects.all()
context = {
"object_list": _persons
}
return render (request, "PersonListView.html", context)
def CompanyListView (request):
_companies = Person.objects.all()
context = {
"object_list": _companies
}
return render (request, "CompanyListView.html", context)
Then add path to urls - /Person for list of persons and /Company for list of companies.
urlpatterns = [
path('/Person', PersonListView),
path('/Company', CompanyListView)
]
All working well. But I already have a questions:
Why my view function have a request variable, but I can call this function from urls without defining this variable?
Why I can't use this syntax path('/Person', PersonListView())? What is wrong with this brackets?
And another part of my question.
But then I decided to make some refactoring - I intended to use one view both for person and companies, so I have to pass a variable context to view function. And I basically know how to do it:
def ObjectList (request, _context):
_objects = _context.objects.all()
data = {
"object_list": _objects
}
return render (request, "ListView.html", data)
Here _context - concrete class (Person or Company).
My problem is in urls.py I have to call ObjectList and pass 2 variables:
_context - there is no problem here, I know how to do it
and request.
Here I faced a wall, because I don't understand how can I pass it to my function. If I just let it empty, I have an error
"ObjectList() missing 1 required positional argument: 'request'"
So how can I fix it? SHould I somehow pass it from my urls? Is there a way to use a view function with multiple arguments?
Here is the traceback:
Internal Server Error: /Entities/Person/
Traceback (most recent call last):
File "D:\Work\Python\virtualenvs\TestProject\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "D:\Work\Python\virtualenvs\TestProject\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "D:\Work\Python\virtualenvs\TestProject\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
TypeError: ObjectList() got multiple values for argument '_context'
[25/Jul/2019 00:25:37] "GET /Entities/Person/ HTTP/1.1" 500 62910
Your second question answers your first. You don't call the view from the URL; Django does that internally when it encounters a matching request. If you try to call it from there, then you get the error you see.
I don't understand though why you think you need to pass request from the URL in the third question. You don't, that's the whole point of your questions 1 and 2.
Your URL pattern should either capture the arguments for the view, or pass them as the third parameter to path. So, in your case, you could use a single pattern which captures the variable:
path('/<str:object_type>/', object_list, name='object_list')
and then:
def object_list(request, object_type):
types = {'Person': models.Person, 'Company': models.Company}
if object_type not in types:
raise Http404
objects = types[object_type].objects.all()
Alternatively, use two separate URL patterns and pass the type as the third parameter explicitly:
urlpatterns = [
path('/Person', object_list_view, {'object_type': Person}, name='person_list'),
path('/Company', object_list_view, {'object_type': Company}, name='company_list'),
]
and your view can be:
def object_list(request, object_type):
objects = object_type.objects.all()
Finally, though, for this very simple use case you should consider using a generic list view; then you wouldn't need to define any views at all:
from django.views.generic import ListView
urlpatterns = [
path('/Person', ListView.as_view(model=Person)),
path('/Company', ListView.as_view(model=Company))
]
Related
I'm trying to write a simple unit test for a view but I'm having trouble passing extra keyword arguments to the view when I'm using RequestFactory to set up the request.
To start, here's the urlpattern:
# app/urls.py
# Example URL: localhost:8000/run/user/1/foo
urlpatterns = [
url(r'^user/(?P<uid>\d+)/(?P<uname>\w+)/$',
views.user_kw,
name='user-kw'),
]
Here's the view I'm testing:
# app/views.py
def user_kw(request, *args, **kwargs):
uid = kwargs['uid']
uname = kwargs['uname']
return render(request, 'run/user.html', context)
Finally, here's the test:
# app/tests.py
def test_user_kw(self):
factory = RequestFactory()
# ???
request = factory.post('user/')
response = views.user_kw(request)
self.assertEqual(response.status_code, 200)
As you might expect, when I run the test, I get this error:
======================================================================
ERROR: test_user_kw (run.tests.TestViews)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/jones/code/django/testing/run/tests.py", line 53, in test_user_kw
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
File "/Users/jones/code/django/testing/run/views.py", line 28, in user_kw
uid = kwargs['uid']
KeyError: 'uid'
----------------------------------------------------------------------
The Django documentation on the RequestFactory object doesn't discuss this situation. I looked at the RequestFactory code itself but I couldn't figure out how to set up the object to account for the two keyword arguments contained in the URL. I also couldn't find anything online addressing this situation.
I should add that I did manage to write a test for the case in which I used positional arguments and it works:
def test_user_pos(self):
factory = RequestFactory()
request = factory.post('user/')
response = views.user_pos(request, 1, 'foo')
self.assertEqual(response.status_code, 200)
I just can't figure out how to rewrite the test for keyword arguments. Perhaps I've been looking at the problem for too long and the answer is staring me in the face, but I just don't see it.
You can pass keyword arguments to the user_pos method the normal way:
response = views.user_kw(request, uid=1, uname='foo')
Your error message shows that you tried:
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
This isn't passing keyword arguments, it's passing a dictionary as a positional argument. Note that you can use ** to unpack the dictionary:
response = views.user_kw(request, **{"uid": 1, "uname": "foobar"})
I am working on a Django template associated with a particular model. Within the view for this template, I am trying to access a record from a different model (Terms containing scientific terms), based on the parameter I would pass in the template.
I tried being using get_context_data to query a random term in the database, and then use a custom filter tag to replace the term to the one I want from within the template. Django is smarter, though and wouldn't let me do that.
Currently, I am trying to define the context within my views.py
class ArticlesView(DetailView):
model = models.Articles
template_name = 'web/articles-details.html'
context_object_name = 'Articles_details'
def get_context_data(self, *args, **kwargs):
ctx = super(ArticlesView, self).get_context_data(*args, **kwargs)
ctx['featured'] = Articles.objects.filter(featured=True)
ctx['term','arg'] = Articles.objects.filter('slug'=='arg')
return ctx
In the above code, the 'featured' context works fine, but not the 'term' one. It is clearly wrong; I know that... but I can't figure out what the correct syntax would be, and how I would provide the parameter from within the template. (I am trying to print out just the slug of the 'scientific term' in this example).
Any thoughts?
I know that I can set a ForeignKey within my models to connect them. The problem with that is that I would have at least 4-5 'scientific terms' on any given page, and therefore, I would have to add at least 5 related terms for each page, and then add them in a particular manner... a lot of repetition and messy.
Thanks for your assistance, in advance.
What you're doing with the context won't quite work as you perhaps think. To illustrate what you've done with that context dictionary;
>>> context = dict()
>>> context['slug', 'arg'] = 'test'
>>> context
{('slug', 'arg'): 'test'}
>>> context['slug']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'slug'
>>> context['arg']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'arg'
>>> context['slug', 'arg']
'test'
So in your template if you did something like {{ slug, arg }} you'd get your object, but I don't think that's valid in the template syntax.
So based on what you've got I suspect you'd want to do something like;
arg = 'my-slug'
ctx['term'] = Articles.objects.filter(slug=arg)
ctx['arg'] = arg
That would give you the arg used to match the slug on the Articles model with the queryset assigned to term.
Im trying to get the variable "segundos" by GET in the Detail View, im trying to get it by the method get:
the js file:
$(document).ready(function(){
var segundos=340;
console.log(segundos);
$.ajax({
data : {'segundos':segundos},
url : '/ajax/puzzle-1/',
type : 'GET',
});
});
views.py
class PuzzleView(DetailView):
model = Puzzle
template_name = 'puzzle.html'
def get (self,request,*args,**kwargs):
seconds = request.GET["segundos"]
self.object = self.get_object()
ranking = Ranking.objects.create(puzzle_id=self.object.id,usuario=self.request.user,segundos=seconds,puesto=89)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
class RankingView(ListView):
model = Ranking
template_name = 'ranking.html'
queryset = Ranking.objects.filter(puzzle_id=1).order_by('segundos')[:3]
class PuzzleAjaxView(TemplateView):
template_name = 'ranking.html'
But i get the famous error "MultiValueDictKeyError". If i try the same method "get" but with a TemplateView, i can get the variable, but not with DetailView
Just in case, my urls.py:
urlpatterns = patterns('puzzle.views',
url(r'^actividad/puzzle/(?P<slug>[-_\w]+)/$',PuzzleView.as_view(),name='puzzle'),
url(r'^ajax/puzzle-1/.*$', PuzzleAjaxView.as_view(),name='ajax'),
url(r'^ranking/.*$', RankingView.as_view(),name='ranking'),
)
seconds = request.GET("segundos")
You can't just call the GET MultiValueDict. You must access by dictionary lookup. It is a subclass of a dict.
request.GET.get('segundos')
request.GET['segundos']
Update
For future reference, your exception traceback would have explained all of this, but the error message should be pretty clear: something along the lines of segundos not being a valid key in the MultiValueDict.
I assume your PuzzleView (what you are calling the DetailView) never gets passed any GET parameters because your example shows GET params only with your AJAX call, which is mapped to your PuzzleAjaxView (what you are calling the TemplateView
What is determining whether or not your get function works or not isn't based on the fact that your view class is a TemplateView or DetailView, it's the fact that segundos is only passed to your AJAX view.
In other words.. any view (TemplateView, DetailView, doesn't matter) accessing a GET dict via direct lookup request.GET['foobar'] will fail if that get parameter isn't passed in.
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()
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.
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 :)
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 :)
I'm trying to setup Django Haystack to search based on some pretty urls. Here is my urlpatterns.
urlpatterns += patterns('',
url(r'^search/$', SearchView(),
name='search_all',
),
url(r'^search/(?P<category>\w+)/$', CategorySearchView(
form_class=SearchForm,
),
name='search_category',
),
)
My custom SearchView class looks like this:
class CategorySearchView(SearchView):
def __name__(self):
return "CategorySearchView"
def __call__(self, request, category):
self.category = category
return super(CategorySearchView, self).__call__(request)
def build_form(self, form_kwargs=None):
data = None
kwargs = {
'load_all': self.load_all,
}
if form_kwargs:
kwargs.update(form_kwargs)
if len(self.request.GET):
data = self.request.GET
kwargs['searchqueryset'] = SearchQuerySet().models(self.category)
return self.form_class(data, **kwargs)
I keep getting this error running the Django dev web server if I try and visit /search/Vendor/q=Microsoft
UserWarning: The model u'Vendor' is not registered for search.
warnings.warn('The model %r is not registered for search.' % model)
And this on my page
The model being added to the query must derive from Model.
If I visit /search/q=Microsoft, it works fine. Is there another way to accomplish this?
Thanks for any pointers
-Jay
There are a couple of things going on here. In your __call__ method you're assigning a category based on a string in the URL. In this error:
UserWarning: The model u'Vendor' is not registered for search
Note the unicode string. If you got an error like The model <class 'mymodel.Model'> is not registered for search then you'd know that you haven't properly created an index for that model. However this is a string, not a model! The models method on the SearchQuerySet class requires a class instance, not a string.
The first thing you could do is use that string to look up a model by content type. This is probably not a good idea! Even if you don't have models indexed which you'd like to keep away from prying eyes, you could at least generate some unnecessary errors.
Better to use a lookup in your view to route the query to the correct model index, using conditionals or perhaps a dictionary. In your __call__ method:
self.category = category.lower()
And if you have several models:
my_querysets = {
'model1': SearchQuerySet().models(Model1),
'model2': SearchQuerySet().models(Model2),
'model3': SearchQuerySet().models(Model3),
}
# Default queryset then searches everything
kwargs['searchqueryset'] = my_querysets.get(self.category, SearchQuerySet())