Django search functionality - bug with search query of length 2 - django

As I am an impressed reader of Stack Overflow I want to ask my first question here. Since I encountered a problem with a snippet and I do not know whether I made a mistake or it's a bug in the code I'm using.
I adapted this code for my own site:
http://blog.tkbe.org/archive/django-admin-search-functionality/
It works fine and it's really a great snippet.
But if my search query has length 2, I think that the results are not correct.
So for example if I search for "re" in first name and last name, I get the following results:
Mr. Tom Krem
Ms. Su Ker
Which is pretty strange. For queries with length > 2 I do not encounter this problem.
So maybe this post read somebody who is using the snippet above and can tell me whether he/she encounters the same problem.
If nobody else encounters the problem I know at least that I have a bug somewhere in my code. Maybe in the form I'm using, or something is messed up in the request context.
How can I solve this problem?
Edit 1:
The inclusion tag:
from django import template
from crm.views import SEARCH_VAR
def my_search_form(context):
return {
'context': context,
'search_var': SEARCH_VAR
}
register = template.Library()
register.inclusion_tag('custom_utilities/my_search_form.html')(my_search_form)
The my_search_form.html:
<div id="toolbar"><form
id="changelist-search"
action=""
method="get">
<div><!-- DIV needed for valid HTML -->
<label
for="searchbar"><img src="{{ context.media_url }}/crm/img/search.png"
class="icon"
alt="Search" /></label>
<input
type="text"
size="40"
name="{{ search_var }}"
value="{{ context.query }}"
id="searchbar" />
<input type="submit" value="Search" />
</div>
</form>
</div>
<script
type="text/javascript">document.getElementById("searchbar").focus();
</script>
The view:
#login_required
def crm_contacts(request):
query = request.GET.get('q', '')
#pass additional params to the SortHeaders function
#the additional params will be part of the header <a href...>
#e.g. use it for pagination / use it to provide the query string
additional_params_dict = {'q': query}
foundContacts = search_contact(request,query)
sort_headers = SortHeaders(request, LIST_HEADERS, default_order_field=1, additional_params=additional_params_dict)
if foundContacts is not None:
contact_list = foundContacts.order_by(sort_headers.get_order_by())
else:
contact_list = Contact.objects.order_by(sort_headers.get_order_by())
context = {
'contact_list' : contact_list,
'headers': list(sort_headers.headers()),
'query' : query,
}
return render_to_response("crm/contact_list.html", context,
context_instance=RequestContext(request))
The contact search form:
#models
from crm.models import Contact
from django.db.models import Q
'''
A search form from
http://blog.tkbe.org/archive/django-admin-search-functionality/
adapted to search for contacts.
'''
def search_contact(request,terms=None):
if terms is None:
return Contact.objects.all()
query = Contact.objects
for term in terms:
query = query.filter(
Q(first_name__icontains=term)
| Q(last_name__icontains=term))
return query
Another edit:
I'm using this snippet to sort the table. Probably one should know this in order to understand the code posted above.
Since I can not post links (spam protection) I will try to explain where to find it. Go to Google. Type in: django snippet table sort
Then it should be the second hit. Sort table headers. snippet nr. 308.
Edit: Add the SortHeaders() function
ORDER_VAR = 'o'
ORDER_TYPE_VAR = 'ot'
class SortHeaders:
"""
Handles generation of an argument for the Django ORM's
``order_by`` method and generation of table headers which reflect
the currently selected sort, based on defined table headers with
matching sort criteria.
Based in part on the Django Admin application's ``ChangeList``
functionality.
"""
def __init__(self, request, headers, default_order_field=None,
default_order_type='asc', additional_params=None):
"""
request
The request currently being processed - the current sort
order field and type are determined based on GET
parameters.
headers
A list of two-tuples of header text and matching ordering
criteria for use with the Django ORM's ``order_by``
method. A criterion of ``None`` indicates that a header
is not sortable.
default_order_field
The index of the header definition to be used for default
ordering and when an invalid or non-sortable header is
specified in GET parameters. If not specified, the index
of the first sortable header will be used.
default_order_type
The default type of ordering used - must be one of
``'asc`` or ``'desc'``.
additional_params:
Query parameters which should always appear in sort links,
specified as a dictionary mapping parameter names to
values. For example, this might contain the current page
number if you're sorting a paginated list of items.
"""
if default_order_field is None:
for i, (header, query_lookup) in enumerate(headers):
if query_lookup is not None:
default_order_field = i
break
if default_order_field is None:
raise AttributeError('No default_order_field was specified and none of the header definitions given were sortable.')
if default_order_type not in ('asc', 'desc'):
raise AttributeError('If given, default_order_type must be one of \'asc\' or \'desc\'.')
if additional_params is None: additional_params = {}
self.header_defs = headers
self.additional_params = additional_params
self.order_field, self.order_type = default_order_field, default_order_type
# Determine order field and order type for the current request
params = dict(request.GET.items())
if ORDER_VAR in params:
try:
new_order_field = int(params[ORDER_VAR])
if headers[new_order_field][1] is not None:
self.order_field = new_order_field
except (IndexError, ValueError):
pass # Use the default
if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
self.order_type = params[ORDER_TYPE_VAR]
def headers(self):
"""
Generates dicts containing header and sort link details for
all defined headers.
"""
for i, (header, order_criterion) in enumerate(self.header_defs):
th_classes = []
new_order_type = 'asc'
if i == self.order_field:
th_classes.append('sorted %sending' % self.order_type)
new_order_type = {'asc': 'desc', 'desc': 'asc'}[self.order_type]
yield {
'text': header,
'sortable': order_criterion is not None,
'url': self.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
'class_attr': (th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
}
def get_query_string(self, params):
"""
Creates a query string from the given dictionary of
parameters, including any additonal parameters which should
always be present.
"""
params.update(self.additional_params)
return '?%s' % '&'.join(['%s=%s' % (param, value) \
for param, value in params.items()])
def get_order_by(self):
"""
Creates an ordering criterion based on the current order
field and order type, for use with the Django ORM's
``order_by`` method.
"""
return '%s%s' % (
self.order_type == 'desc' and '-' or '',
self.header_defs[self.order_field][1],
)

If you run manage.py shell and then:
>>> from crm.models import Contact
>>> from django.db.models import Q
>>> list=Contact.objects.filter(Q(first_name__icontains='re')|Q(last_name__icontains='re'))
>>> print list
What is the output?
Edit: Right, so if you try:
>>> list=Contact.objects.filter(Q(first_name__icontains='mot')|Q(last_name__icontains='mot'))
>>> print list
(I'm trying to narrow down on the terms that are giving you problem and I saw your last comment)
What is the output?
Edit: If both of the above queries work in the shell, something else is modifying your queryset somewhere and adding some additional criteria...
Are you sure sort_headers() is not modifying the queryset with more than just an order by clause? Could you post sort_headers() to your question?

Related

Can you use a Django form multiple times in one view?

I have a Django view that uses one form multiple times. The form is just a boolean field form that is supposed to initialize to True but then the user can decide to uncheck the boxes or not.
The problem I'm having is that the all of the fields evaluate to True no matter what the user leaves checked. Is this a problem with using the same form multiple times, or did I mess something else up?
The form looks like this:
class DataTypeForm(forms.Form):
def __init__(self,*args,**kwargs):
section_label = kwargs.pop('section_label')
initial_value = kwargs.pop('initial_value')
super(DataTypeForm,self).__init__(*args,**kwargs)
self.fields['dataType'].label=mark_safe(section_label)
self.fields['dataType'].initial=initial_value
self.fields['dataType'].required=False
dataType = forms.BooleanField(required=False)
This is the view:
def Manual_Request(request):
form_dict = {}
arg_dict = {}
message = message = {'last_url':'Nominal_Request'}
if request.method == 'POST':
logger.info("Query submitted, beginning query results render for:")
form_NOM = DataTypeForm(request.POST or None,section_label="ENG_NOM",initial_value=True)
form_DDM = DataTypeForm(request.POST or None,section_label="SCI_DDM",initial_value=True)
form_RAW = DataTypeForm(request.POST or None,section_label="SCI_RAW",initial_value=False)
if form_NOM.is_valid():
NOM = form_NOM.cleaned_data['dataType']
arg_dict.update({'ENG_NOM':str(NOM)})
logger.info("ENG_NOM: {val}".format(val=NOM))
if form_DDM.is_valid():
DDM = form_DDM.cleaned_data['dataType']
arg_dict.update({'SCI_DDM':str(DDM)})
logger.info("SCI_DDM: {val}".format(val=DDM))
if form_RAW.is_valid():
RAW = form_RAW.cleaned_data['dataType']
arg_dict.update({'SCI_RAW':str(RAW)})
logger.info("SCI_RAW: {val}".format(val=RAW))
return Request_Results(request,args_dict)
else:
logger.info("Rendering query page")
form_NOM = DataTypeForm(section_label="ENG_NOM",initial_value=True)
form_DDM = DataTypeForm(section_label="SCI_DDM",initial_value=True)
form_RAW = DataTypeForm(section_label="SCI_RAW",initial_value=True)
form_dict.update({'form_NOM':...etc})
return render(request,'InterfaceApp/COMMRequest_Manual.html',form_dict)
Help much appreciated!
I haven't run your code, but my best guess is that yes, it's a problem with using the same form multiple times in the same view. The reason? All of your <input type="checkbox" name="..." ... /> tags will have the same name, 'dataType'. The user's browser knows nothing of your back-end, and will just send, for example, dataType=on&dataType=on as POST data for the three fields if two are checked and one is not.
Seeing the problem here? How is django supposed to know which of those dataType fields are for your NOM, DDM, or RAW forms? It can't know.
You should be able to solve this using form prefixes. In short, there's a kwarg that you can pass to a form's __init__() that will cause a prefix to be added to all of the form items in the rendered HTML. So, for example:
form_NOM = DataTypeForm(request.POST or None, section_label="ENG_NOM",
initial_value=True, prefix="NOM")
form_DDM = DataTypeForm(request.POST or None, section_label="SCI_DDM",
initial_value=True, prefix="DDM")
form_RAW = DataTypeForm(request.POST or None, section_label="SCI_RAW",
initial_value=False, prefix="RAW")
Hope that helps!
This is exactly what Django formsets are for. They allows you to create a set of the same type of form. It handles prefixes, and adds a management form so that Django doesn't get confused as to what data comes from what form.
https://docs.djangoproject.com/en/1.8/topics/forms/formsets/

Prevent django admin from running SELECT COUNT(*) on the list form

Every time I use Admin to list the entries of a model, the Admin count the rows in the table. Worse yet, it seems to be doing so even when you are filtering your query.
For instance if I want to show only the models whose id is 123, 456, 789 I can do:
/admin/myapp/mymodel/?id__in=123,456,789
But the queries ran (among others) are:
SELECT COUNT(*) FROM `myapp_mymodel` WHERE `myapp_mymodel`.`id` IN (123, 456, 789) # okay
SELECT COUNT(*) FROM `myapp_mymodel` # why???
Which is killing mysql+innodb. It seems that the problem is partially acknowledged in this ticket, but my issue seems more specific since it counts all the rows even if it is not supposed to.
Is there a way to disable that global rows count?
Note: I am using django 1.2.7.
Django 1.8 lets you disable this by setting show_full_result_count = False.
https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count
Okay, I think I found a solution. As Peter suggested, the best approach is to work on the count property and it can be done by overriding it with custom query set (as seen in this post) that specialises the count with an approximate equivalent:
from django.db import connections, models
from django.db.models.query import QuerySet
class ApproxCountQuerySet(QuerySet):
"""Counting all rows is very expensive on large Innodb tables. This
is a replacement for QuerySet that returns an approximation if count()
is called with no additional constraints. In all other cases it should
behave exactly as QuerySet.
Only works with MySQL. Behaves normally for all other engines.
"""
def count(self):
# Code from django/db/models/query.py
if self._result_cache is not None and not self._iter:
return len(self._result_cache)
is_mysql = 'mysql' in connections[self.db].client.executable_name.lower()
query = self.query
if (is_mysql and not query.where and
query.high_mark is None and
query.low_mark == 0 and
not query.select and
not query.group_by and
not query.having and
not query.distinct):
# If query has no constraints, we would be simply doing
# "SELECT COUNT(*) FROM foo". Monkey patch so the we
# get an approximation instead.
cursor = connections[self.db].cursor()
cursor.execute("SHOW TABLE STATUS LIKE %s",
(self.model._meta.db_table,))
return cursor.fetchall()[0][4]
else:
return self.query.get_count(using=self.db)
Then in the admin:
class MyAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(MyAdmin, self).queryset(request)
return qs._clone(klass=ApproxCountQuerySet)
The approximate function could mess things up on page number 100000, but it is good enough for my case.
I found Nova's answer very helpful, but i use postgres. I modified it slightly to work for postgres with some slight alterations to handle table namespaces, and slightly different "detect postgres" logic.
Here's the pg version.
class ApproxCountPgQuerySet(models.query.QuerySet):
"""approximate unconstrained count(*) with reltuples from pg_class"""
def count(self):
if self._result_cache is not None and not self._iter:
return len(self._result_cache)
if hasattr(connections[self.db].client.connection, 'pg_version'):
query = self.query
if (not query.where and query.high_mark is None and query.low_mark == 0 and
not query.select and not query.group_by and not query.having and not query.distinct):
# If query has no constraints, we would be simply doing
# "SELECT COUNT(*) FROM foo". Monkey patch so the we get an approximation instead.
parts = [p.strip('"') for p in self.model._meta.db_table.split('.')]
cursor = connections[self.db].cursor()
if len(parts) == 1:
cursor.execute("select reltuples::bigint FROM pg_class WHERE relname = %s", parts)
else:
cursor.execute("select reltuples::bigint FROM pg_class c JOIN pg_namespace n on (c.relnamespace = n.oid) WHERE n.nspname = %s AND c.relname = %s", parts)
return cursor.fetchall()[0][0]
return self.query.get_count(using=self.db)
The Nova's solution (ApproxCountQuerySet) works great, however in newer versions of Django queryset method got replaced with get_queryset, so it now should be:
class MyAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyAdmin, self).get_queryset(request)
return qs._clone(klass=ApproxCountQuerySet)
If this is a serious problem you may have to take Drastic Actions™.
Looking at the code for a 1.3.1 install, I see that the admin code is using the paginator returned by get_paginator(). The default paginator class appears to be in django/core/paginator.py. That class has a private value called _count which is set in Paginator._get_count() (line 120 in my copy). This in turn is used to set a property of the Paginator class called count. I think that _get_count() is your target. Now the stage is set.
You have a couple of options:
Directly modify the source. I do not recommend this, but since you seem to be stuck at 1.2.7 you may find that it is the most expedient. Remember to document this change! Future maintainers (including possibly yourself) will thank you for the heads up.
Monkeypatch the class. This is better than direct modification because a) if you don't like the change you just comment out the monkeypatch, and b) it is more likely to work with future versions of Django. I have a monkeypatch going back over 4 years because they still have not fixed a bug in the template variable _resolve_lookup() code that doesn't recognize callables at the top level of evaluation, only at lower levels. Although the patch (which wraps the method of a class) was written against 0.97-pre, it still works at 1.3.1.
I did not spend the time to figure out exactly what changes you would have to make for your problem, but it might be along the lines of adding a _approx_count member to appropriate classes class META and then testing to see if that attr exists. If it does and is None then you do the sql.count() and set it. You might also need to reset it if you are on (or near) the last page of the list. Contact me if you need a little more help on this; my email is in my profile.
It is possible to change the default paginator used by the admin class. Here's one that caches the result for a short period of time: https://gist.github.com/e4c5/6852723
I managed to create a custom paginator that shows the current page numbe, a next button and a show full count link. It allows for the use of the original paginator if needed.
The trick used is to take per_page + 1 elements from db in order to see if we have more elements and then provide a fake count.
Let's say that we want the the third page and the page has 25 elements => We want object_list[50:75]. When calling Paginator.count the queryset will be evaluated for object_list[50:76](note that we take 75+1 elements) and then return either the count as 76 if we got 25+1 elements from db or 50 + the number of elements received if we didn't received 26 elements.
TL;DR:
I've created a mixin for the ModelAdmin:
from django.core.paginator import Paginator
from django.utils.functional import cached_property
class FastCountPaginator(Paginator):
"""A faster paginator implementation than the Paginator. Paginator is slow
mainly because QuerySet.count() is expensive on large queries.
The idea is to use the requested page to generate a 'fake' count. In
order to see if the page is the final one it queries n+1 elements
from db then reports the count as page_number * per_page + received_elements.
"""
use_fast_pagination = True
def __init__(self, page_number, *args, **kwargs):
self.page_number = page_number
super(FastCountPaginator, self).__init__(*args, **kwargs)
#cached_property
def count(self):
# Populate the object list when count is called. As this is a cached property,
# it will be called only once per instance
return self.populate_object_list()
def page(self, page_number):
"""Return a Page object for the given 1-based page number."""
page_number = self.validate_number(page_number)
return self._get_page(self.object_list, page_number, self)
def populate_object_list(self):
# converts queryset object_list to a list and return the number of elements until there
# the trick is to get per_page elements + 1 in order to see if the next page exists.
bottom = self.page_number * self.per_page
# get one more object than needed to see if we should show next page
top = bottom + self.per_page + 1
object_list = list(self.object_list[bottom:top])
# not the last page
if len(object_list) == self.per_page + 1:
object_list = object_list[:-1]
else:
top = bottom + len(object_list)
self.object_list = object_list
return top
class ModelAdminFastPaginationMixin:
show_full_result_count = False # prevents root_queryset.count() call
def changelist_view(self, request, extra_context=None):
# strip count_all query parameter from the request before it is processed
# this allows all links to be generated like this parameter was not present and without raising errors
request.GET = request.GET.copy()
request.GET.paginator_count_all = request.GET.pop('count_all', False)
return super().changelist_view(request, extra_context)
def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
# use the normal paginator if we want to count all the ads
if hasattr(request.GET, 'paginator_count_all') and request.GET.paginator_count_all:
return Paginator(queryset, per_page, orphans, allow_empty_first_page)
page = self._validate_page_number(request.GET.get('p', '0'))
return FastCountPaginator(page, queryset, per_page, orphans, allow_empty_first_page)
def _validate_page_number(self, number):
# taken from Paginator.validate_number and adjusted
try:
if isinstance(number, float) and not number.is_integer():
raise ValueError
number = int(number)
except (TypeError, ValueError):
return 0
if number < 1:
number = 0
return number
The pagination.html template:
{% if cl and cl.paginator and cl.paginator.use_fast_pagination %}
{# Fast paginator with only next button and show the total number of results#}
{% load admin_list %}
{% load i18n %}
{% load admin_templatetags %}
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{% if forloop.last %}
{% fast_paginator_number cl i 'Next' %}
{% else %}
{% fast_paginator_number cl i %}
{% endif %}
{% endfor %}
{% endif %}
{% show_count_all_link cl "showall" %}
</p>
{% else %}
{# use the default pagination template if we are not using the FastPaginator #}
{% include "admin/pagination.html" %}
{% endif %}
and templatetags used:
from django import template
from django.contrib.admin.views.main import PAGE_VAR
from django.utils.html import format_html
from django.utils.safestring import mark_safe
register = template.Library()
DOT = '.'
#register.simple_tag
def fast_paginator_number(cl, i, text_display=None):
"""Generate an individual page index link in a paginated list.
Allows to change the link text by setting text_display
"""
if i == DOT:
return '… '
elif i == cl.page_num:
return format_html('<span class="this-page">{}</span> ', i + 1)
else:
return format_html(
'<a href="{}"{}>{}</a> ',
cl.get_query_string({PAGE_VAR: i}),
mark_safe(' class="end"' if i == cl.paginator.num_pages - 1 else ''),
text_display if text_display else i + 1,
)
#register.simple_tag
def show_count_all_link(cl, css_class='', text_display='Show the total number of results'):
"""Generate a button that toggles between FastPaginator and the normal
Paginator."""
return format_html(
'<a href="{}"{}>{}</a> ',
cl.get_query_string({PAGE_VAR: cl.page_num, 'count_all': True}),
mark_safe(f' class="{css_class}"' if css_class else ''),
text_display,
)
You can use it this way:
class MyVeryLargeModelAdmin(ModelAdminFastPaginationMixin, admin.ModelAdmin):
# ...
Or an even simpler version that does not show the Next button and Show the total number of results :
from django.core.paginator import Paginator
from django.utils.functional import cached_property
class FastCountPaginator(Paginator):
"""A faster paginator implementation than the Paginator. Paginator is slow
mainly because QuerySet.count() is expensive on large queries.
The idea is to use the requested page to generate a 'fake' count. In
order to see if the page is the final one it queries n+1 elements
from db then reports the count as page_number * per_page + received_elements.
"""
use_fast_pagination = True
def __init__(self, page_number, *args, **kwargs):
self.page_number = page_number
super(FastCountPaginator, self).__init__(*args, **kwargs)
#cached_property
def count(self):
# Populate the object list when count is called. As this is a cached property,
# it will be called only once per instance
return self.populate_object_list()
def page(self, page_number):
"""Return a Page object for the given 1-based page number."""
page_number = self.validate_number(page_number)
return self._get_page(self.object_list, page_number, self)
def populate_object_list(self):
# converts queryset object_list to a list and return the number of elements until there
# the trick is to get per_page elements + 1 in order to see if the next page exists.
bottom = self.page_number * self.per_page
# get one more object than needed to see if we should show next page
top = bottom + self.per_page + 1
object_list = list(self.object_list[bottom:top])
# not the last page
if len(object_list) == self.per_page + 1:
object_list = object_list[:-1]
else:
top = bottom + len(object_list)
self.object_list = object_list
return top
class ModelAdminFastPaginationMixin:
show_full_result_count = False # prevents root_queryset.count() call
def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
page = self._validate_page_number(request.GET.get('p', '0'))
return FastCountPaginator(page, queryset, per_page, orphans, allow_empty_first_page)
def _validate_page_number(self, number):
# taken from Paginator.validate_number and adjusted
try:
if isinstance(number, float) and not number.is_integer():
raise ValueError
number = int(number)
except (TypeError, ValueError):
return 0
if number < 1:
number = 0
return number

How can I redirect url with a frendly when user fill the form in django

I use a form in template,and I want to redirect a friendly url when user fill the form
I search many issue in stackoverflow,I think my problem like this issue: Django form redirect,
but I can't understand the template tag,so I can't solve my problem.
I write the form:
<form method="GET" action="/stock/search">
search:<input class="search" name="search" type="text" value="" id="serach" >
<input type="submit" value="Go"/>
</form>
and write the urls:
url(r'^(?P<number>\w+)/$', 'stock.views.stocknumber'),
url(r'^search/$', 'stock.views.search'),
and the views:
def stocknumber(request, number):
"""
stock info
"""
stock = Stock.objects.filter(number="%s"%number)
stock_number = Stock.objects.filter(number="%s"%number)
stock_reportinfo = Reportinfo.objects.filter(number="%s"%number)
stock_blockinfo = Blockinfo.objects.filter(number="%s"%number)
stock_stockinfo = Stockinfo.objects.filter(number="%s"%number)
data = Stockhq.objects.filter(number="%s"%number).values('timeStamps','openData','highData','lowData', 'closeData', 'volData').reverse()
datalist=[ ]
for item in data:
d =[item['timeStamps'].toordinal(),item['openData'],item['highData'],item['lowData'],item['closeData'],item['volData']]
datalist.append(d)
hisdata = datalist
return render_to_response(
'stock/stock.html',
{"stock_number": stock_number,
"stock_reportinfo": stock_reportinfo,
"stock_blockinfo": stock_blockinfo,
"stock_stockinfo": stock_stockinfo,
"hisdata":simplejson.dumps(hisdata) ,
},
context_instance=RequestContext(request))
def search(request):
return HttpResponseRedirect('/stock/%s/'%request.GET['search'])
and now I hope the user input the stock number and redriect to the friendly url like:http://..../stock/YHOO
and this url is get the stock info to render template,is this idea is right?
if it is ,what is the correct code ?
I don't think that question is really similar to your question, if I'm understanding correctly. Because that one already predetermines the action of the form, the redirected URL doesn't seem to depend on the user input. So I'm not entirely sure what your question is.
Are you implementing a simple search that, when a user inputs the stock's number (I'm assuming you consider "YHOO" a number? Is the "number" input something like "YHOO" or is it actually a number?), the stock with the number (so "YHOO" stock) is displayed with all of its information on a new page with the URL "http://.../stock/YHOO"?
In any case, here are some observations.
stock = Stock.objects.filter(number="%s"%number)
stock_number = Stock.objects.filter(number="%s"%number)
Is there a reason why you have both when you don't use the first one? In addition, are numbers unique? If so, you can just do Stock.objects.get(number=number), because:
You would use "get" instead of "filter" because you'd expect a
single record to match that.
Number is already a string when
passed in, so you don't need to do "%s" % number.
You can also try the following instead of using the actual URL, to make it more Django-like.
from django.core.urlresolvers import reverse
...
def search(request):
number = request.GET['search']
return HttpResponseRedirect(reverse('stock.views.stocknumber', args=(number,)))

Help understanding a Django view

I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.

Next previous links from a query set / generic views

I have a quite simple query set and a related generic views:
f_detail = {
'queryset': Foto.objects.all(),
'template_name': 'foto_dettaglio.html',
"template_object_name" : "foto",
}
urlpatterns = patterns('',
# This very include
(r'^foto/(?P<object_id>\d+)/$', list_detail.object_detail, f_detail, ),
)
Just a template for generating a detail page of a photo: so there's no view.
Is there an easy way to have a link to previous | next element in the template
without manualy coding a view ?
Somthing like a:
{% if foto.next_item %}
Next
{% endif}
class Foto(model):
...
def get_next(self):
next = Foto.objects.filter(id__gt=self.id)
if next:
return next.first()
return False
def get_prev(self):
prev = Foto.objects.filter(id__lt=self.id).order_by('-id')
if prev:
return prev.first()
return False
you can tweak these to your liking. i just looked at your question again... to make it easier than having the if statement, you could make the methods return the markup for the link to the next/prev if there is one, otherwise return nothing. then you'd just do foto.get_next etc. also remember that querysets are lazy so you're not actually getting tons of items in next/prev.
The Foto version above has a couple of shortcomings:
Doing a boolean evaluation like if next: can be slow since it basically loads the entire QuerySet result. Use next.exists() or the try/except like in my version.
The get_prev() result is wrong because you need to reverse the ordering in this case.
So FWIW here is my version, which is for a generic primary key:
def get_next(self):
"""
Get the next object by primary key order
"""
next = self.__class__.objects.filter(pk__gt=self.pk)
try:
return next[0]
except IndexError:
return False
def get_prev(self):
"""
Get the previous object by primary key order
"""
prev = self.__class__.objects.filter(pk__lt=self.pk).order_by('-pk')
try:
return prev[0]
except IndexError:
return False
If you'll accept Model.objects.all() as your queryset, and you are ok with grabbing next / previous items by a date field (usually a 'created' field with auto_now_add=True will give the same order as object id's), you can use get_next_by_foo() and get_previous_by_foo(), where 'foo' is the date field.
For next / previous links from a more complicated QuerySet, using the Paginator with threshold set to one seems like it might be the best option.