I'm setting up a webstore manager and I'm relying on django-tables2 package to show a list of products using the SimpleTableMixin. I want to add the filter/search capability to the view. As recommended by the django-tables2 package one can rely on django-filter package to provide filtering. However, it the case of a model with many fields it becomes almost impossible to query and develop forms for those efficiently. My goal is to use django-haystack to have a single input search form as a mean to query the model instances which should be displayed in a similar table/form.
I've tried to add the SimpleTableMixin to the django-haystack package generic SearchView. However I keep on getting the following error:
TypeError at /manager/front/products/
Argument data to ProductTable is required
So far my implementation goes as follow:
view:
# ############## Products ############## (previous implementation with django-filter)
# #method_decorator(staff_member_required, name='dispatch')
# class ProductList(SingleTableMixin, FilterView):
# model = Product
# table_class = tables.ProductTable
# filterset_class = filters.ProductFilter
# template_name = 'manager/front/products/product_list.html'
############## Products ##############
#method_decorator(staff_member_required, name='dispatch')
class ProductList(SingleTableMixin, SearchView):
model = Product
table_class = tables.ProductTable
template_name = 'manager/front/products/product_list.html'
tables:
import django_tables2 as tables
from front.models import Product
class ProductTable(tables.Table):
class Meta:
model = Product
template_name = 'django_tables2/bootstrap.html'
search_indexes.py:
from haystack import indexes
from front.models import Product
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Product
urls:
path('front/products',views.ProductList.as_view(),name="manage_products"),
template:
{% extends 'base_site.html' %}
{% load render_table from django_tables2 %}
{% block content_title %}Manage Products{% endblock%}
{% block content %}
<form action="" method="get" class="form form-inline">
{{ form }}
<button class="btn btn-default" type="submit">Search</button>
</form>
{% render_table table %}
{% endblock %}
How can I remove that error and provide efficient search abilities to my list views?
Are you sure that you are using haystack.generic_views.SearchView and not haystack.views.SearchView? Please notice that on https://django-haystack.readthedocs.io/en/latest/views_and_forms.html it says:
As of version 2.4 the views in haystack.views.SearchView are deprecated in favor of the new generic views in haystack.generic_views.SearchView which use the standard Django class-based views which are available in every version of Django which is supported by Haystack.
Thus if you're using haystack.views.SearchView then the get_context_data of SingleTableMixin will never be called thus no table will be put in your context (i.e table will be empty). Actually because I dislike the behavior of {% render_table %} when its parameter is empty (it behaves different than the other Django tags/filters i.e it throws an exception while the django ones silently ignore that) I usually put it inside some {% if table %} checks.
UPDATE
It seems that for whatever reason the data is not passed to the table. I am not sure why, I can't test it right now but from a quick look at the source code your implementation should have been working (considering that SearchView has a get_queryset and TableMixin uses the get_queryset to retrieve its data). In any case you try overriding some methods of TableMixin to make sure that the table is properly returned (take a look at TableMixin here: https://django-tables2.readthedocs.io/en/latest/_modules/django_tables2/views.html).
I think the most definite solution would be to just bite the bullet and override get_table yourself. So, try adding something like this to your class:
def get_table(self, **kwargs):
table_class = self.get_table_class()
# I only change this line from the original to make sure that the self.get_queryset() is called to return the data
table = table_class(data=self.get_queryset(), **kwargs)
return RequestConfig(self.request, paginate=self.get_table_pagination(table)).configure(
table
)
One idea that just popped in my mind. Is there a possibility that the get_queryset() method of SearchView returns None due to bad configuration or whatever?
Related
First section of code works fine; it is for reference.
#Basic Model
class MyTestModel(models.Model):
record = models.CharField(max_length=100)
def __str__(self):
return self.record
#Specify verbose_name
class Meta:
verbose_name = 'UniqueNameExample'
verbose_name_plural = verbose_name
#Generic ListView.
class MyTemplateView(ListView):
model = MyTestModel
template_name = 'base.html'
context_object_name = 'model_list'
ordering = ['record']
#Python block in HTML template. So far, so good.
{% for item in model_list %}
{{ item.record }}<br>
#{{ item }} also works
{% endfor %}
I am trying to access the Model's verbose_name ('UniqueNameExample') AND the model_list in the view. I've tried registering a filter, a tag, and simple_tag.
Something like: templatetags/verbose.py
from django import template
register = template.Library()
#register.filter (or #register.tag or #register.simple_tag)
def verbose_name(obj):
#Could be verbose_name(model) or whatever input
return obj._meta.verbose_name
And then after
{% load verbose %}
in my HTML (which also works fine), I'll try something like this:
{{ object|verbose_name }}
And I'll get the error 'str' object has no attribute '_meta'. Error is the same if using a tag:
{% verbose_name object %}
Note: tags apparently worked for earlier versions, but maybe I'm using them incorrectly? Not asking to access the Model field verbose_name for "record," btw -- that's answered adequately on SO.
The one thing I've tried that gets the answer half right is if I set the following under MyTemplateView:
queryset = model._meta.verbose_name
The problem with this is it overrides the model_list, and the only result I'm left with is 'UniqueNameExample' without being able to access the record(s) I've used in the model.
I know private=True for _meta (not sure if that's relevant or worth exploring/possibly breaking), but Django admin displays the verbose_name (if set) in the list of created models, so I don't see why I can't do the same (also had a rough time tracing back exactly how it does it in the source code). Maybe it's not a generic ListView but a MixIn? Function-based?
Large(ish) db with thousands of models, each with unique verbose_name[s]; would very much like to keep it simple.
EDIT: Found a fantastic solution from Dominique Barton # https://blog.confirm.ch/accessing-models-verbose-names-django-templates/
First, create a templatags folder at the app level and populate with an init file. Next, create a template tag file. Something like verbose.py.
from django import template
register = template.Library()
#register.simple_tag
def verbose_name(value):
#Django template filter which returns the verbose name of a model.
#Note: I set my verbose_name the same as the plural, so I only need one tag.
if hasattr(value, 'model'):
value = value.model
return value._meta.verbose_name
Next, the ListView should be modified.
from django.views.generic.list import ListView as DjangoListView
from .models import MyTestModel
class ListView(DjangoListView):
#Enhanced ListView which includes the `model` in the context data,
#so that the template has access to its model class.
#Set normally
model = MyTestModel
template_name = 'base.html'
context_object_name = 'model_list'
ordering = ['record']
def get_context_data(self):
#Adds the model to the context data.
context = super(ListView, self).get_context_data()
context['model'] = self.model
return context
Don't forget to add the path to urls.py:
path('your_extension/', views.ListView.as_view(), name='base')
Lastly, load the tag and iterate through the "records" normally:
{% load verbose %}
<h1> {% verbose_name model%} </h1>
<ul style='list-style:none'>
{% for item in model_list %}
<li>{{ item }}}</a></li>
{% endfor %}
</ul>
Pagination also works as advertised.
I'm trying to build a search system, and I want to search by multiple fieldsname, state, city, in my django models. I wrote the below code, yet I've been unable to figure out how to go about it.
Models:
class Finhall(models.Model):
user=models.ForeignKey(User)
name=models.CharField(max_length=250, unique=True)
address=models.CharField(max_length=200)
city=models.CharField(max_length=200)
state=models.CharField(max_length=200)
def __unicode__(self):
return u'%s' % (self.name)
Views.py
def hup_find(request):
if ('q' in request.GET) and request.GET['q'].strip():
query_string=request.GET.get('q')
seens=Finhall.objects.filter(name__icontains=query_string)
else:
seens=None
return render_to_response('find.html',{'seens':seens},context_instance=RequestContext(request))
Template:
{% block content %}
<body>
<form action="" method="GET">
<input type="text" name="q" />
<button type="submit">search</button>
</form>
{% for seen in seens %}
<p> {{seen.name}}</p>
{% empty %}
<p> no search </p>
{% endfor %}
</body>
{% endblock %}
How can I go about this? I don't want to use haysatck due to some personal reasons.
you can use django Q objects to do OR query,
or if you want to ANDyour queries together just use the current lookups as kwargs
seens = Finhall.objects.filter(
name__icontains=query_string,
address__icontains=query_string
)
You should really consider full text search or haystack (which makes search easy) because icontains issues a %LIKE% which is not remotely scalable
EDIT: Just noticed it is Postgres only
Apparently in django 1.10 SearchVector class was added.
Usage from the docs:
Searching against a single field is great but rather limiting. The Entry instances we’re searching belong to a Blog, which has a tagline field. To query against both fields, use a SearchVector:
>>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate(
... search=SearchVector('body_text', 'blog__tagline'),
... ).filter(search='Cheese')
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
To search same text in multiple fields you can use this :
from django.db.models import Q
class SearchAPI(APIView):
def get(self, request, search_text, format=None, **kwargs):
Model.objects.filter(Q(search_tags__contains=search_text) | Q(auto_tags__contains=search_text)
As Robin Nemeth suggested, if you are using postgres db, can use the SearchVector, (I'm just making it straight to the question, get full details in django documentation.)
First add django.contrib.postgres to the INSTALLED_APPS, helps leveraging PostgreSQL’s full text search engine.
in the views.py of the search,
from django.contrib.postgres.search import SearchVector
# view function
def hup_find(request):
search_vector = SearchVector('name', 'state', 'city')
if ('q' in request.GET) and request.GET['q'].strip():
query_string=request.GET['q']
seens = Findhall.objects.annotate(
search=search_vector).filter(search=query_string)
return render(request, 'find.html', {'seens':seens})
For large databases the search process takes some time, you can improve the performance using use GIN-(Generalized Inverted Index) feature of postgresql, These are some helpful links other than the official documentation,
Mastering django Search.
This stackoverflow question.
There is different approach found, I think employing search in a more pythonish way by Julien Phalip,
I am trying to pull data from two models within a DetailsView template (in Django). There is of course a primary model (eg. Articles) associated with the view, which is easy to access. However, I want to access data from a model (eg. Terms). I do not want to use ForeignKey because I will be using many 'Terms' in each 'Article,' and since ForeignKey will allow me to link to only row in the Terms model, I will have to set-up mutiple ForeignKey fields, which can get messy fast.
I was thinking that this could be accomplished using get_context_data or templatetags, but haven't have had any luck yet. Any thoughts?
From Django Documentation you can add any queryset or context value you like to call in your template context like book_list below will list all books and it doesn't has to be linked with any other models..
#views.py
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
#yourtemplate.html
{% for book in book_list %}
{% if book %}
{{ book.title }}
{% endif %}
{% empty %}
No book_list found.
{% endfor %}
Just for testing, I have a Python that scripts that enters IP addresses into the database.
I have the following values:
127.0.0.1
127.0.0.1
10.42.1.42
10.42.1.42
10.42.1.42
Model:
class Invalid(models.Model):
access = models.DataTimeField()
ip = models.CharField()
login = models.CharField()
In the template, this is what I have for the drop down list:
<p>Choose:
<select>
{% for item in results %}
<option value="{{ item.ip }}">{{ item.ip }}</option>
{% endfor %}
</select>
<button type="button">Filter</button>
</p>
However, the problem with this method is that it lists all the values. As far as I'm aware, there is no way to create an array/lists although I may be mistaken.
Is there any way to only have distinct values show up in the list, that is:
127.0.0.1
10.42.1.42
assuming results is a queryset :
If you have a backend that supports (postgres) it you could use distinct on the ip field
results.distinct('ip')
Select DISTINCT individual columns in django?
Maybe you could also build a unique collection in python
uniques_ips = {}
for item in results:
if item.ip not in unique_ips:
unique_ips[item.ip] = item
unique_ips.values()
In your views.py file you would subset your queryset to get the distinct values only. Then you would only return these distinct values to your template.
So, using your example, you have a context object called results. You don't define how you came to populate that object, or what your model looks like, so lets assume the following model for your application for the sake of argument:
from django.db import models
class MyModel(models.Model):
ip = models.IPAddressField()
Now, lets take a look at views.py and here we are using Generic Class Based Views:
from django.views.generic.list import ListView
class IpListView(ListView):
def get_queryset(self):
queryset = super(IpListView, self).get_queryset()
unique_ips = queryset.values('ip').distinct()
return unique_ips
Note: You could also put the distinct() method above directly into the urls.py queryset object below...
In your urls.py you would make sure you route IpListView to your template. Something like:
from django.conf.urls import patterns, url
from myapp.models import MyModel
from myapp.views import IpListView
urlpatterns = patterns('',
url(r'^$',
IpListView.as_view(
queryset=MyModel.objects.order_by('ip'),
context_object_name='results',
template_name='myapp/mytemplate.html',
), name='mymodel-ips'
)
)
Note here how results is your context object name. This means that all your ip field values will be available in your template mytemplate.html as:
for r in results:
ip_option = r.ip
And because you have already subset the queryset to only have the unique IPs, you get what you needed. So you just need to loop through:
<p>Choose:
<select>
{% for item in results %}
<option value="{{ item.ip }}">{{ item.ip }}</option>
{% endfor %}
</select>
<button type="button">Filter</button>
</p>
One caveat here: you don't state if you need any other fields from your model. If you do, the queryset I listed above will modify the results you get, and you might not get back what you expect if you wanted to use other values entered to be returned...
Suppose I have a model:
from django.db import models
class Test(models.Model):
name=models.CharField(max_length=255, verbose_name=u'custom name')
How do I get my model's field's verbose name in templates? The following doesn't work:
{{ test_instance.name.verbose_name }}
I would very much appreciate the solution, something on lines as we do when using forms, using label attribute in template:
{{ form_field.label }}
You can use following python code for this
Test._meta.get_field("name").verbose_name.title()
If you want to use this in template then it will be best to register template tag for this. Create a templatetags folder inside your app containing two files (__init__.py and verbose_names.py).Put following code in verbose_names.py:
from django import template
register = template.Library()
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
return instance._meta.get_field(field_name).verbose_name.title()
Now you can use this template tag in your template after loading the library like this:
{% load verbose_names %}
{% get_verbose_field_name test_instance "name" %}
You can read about Custom template tags in official django documentation.
The method in the accepted answer is awesome!
And maybe you'll like this if you want to generate a field list.
Adding an iterable to the class Test makes it convenient to list fields' verbose name and value.
Model
class Test(models.Model):
...
def __iter__(self):
for field in self._meta.fields:
yield (field.verbose_name, field.value_to_string(self))
Template
{% for field, val in test_instance %}
<div>
<label>{{ field }}:</label>
<p>{{ val }}</p>
</div>
{% endfor %}
based on this answer https://stackoverflow.com/a/14498938 .in Django Model i added
class Meta:
app_name = 'myapp'
in listview i have
from django.core import serializers
context['data'] = serializers.serialize( "python", self.get_queryset() )
inside mylist.html i have
{% for field, value in data.0.fields.items %}
<th style="text-align:center;">{% get_verbose_field_name data.0.model field %}</th>
{% endfor %}
in filter:
from django import template
register = template.Library()
from .models import Mymodel
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
myinstance = eval(instance.split('.')[1].title())
return myinstance._meta.get_field(field_name).verbose_name.title()
instance in the abbove filter for the specific example is myapp.mymodel i evalute instance into model object and the i return field verbose name
it works in django 1.9
It's probably too late for an answer but I had the same issue until I realised that I caused the problem by overriding the fields in the form.py (self.fields['fieldname'] = ..). If you do that you also need to set a label otherwise it uses a label derived from the fieldname.
Hope this quick reply makes sense.