Custom input field and custom searchqueryset - django

I followed the get started doc and everyting work well! :)
But i would like to replace the form in the /search/search.html by a custom form without selectable model checkbox.
In the form i would like to add a button which on click, order search results by a criteria
My questions are:
Which files i need to create or modified to perfoms that and what are their roles?
My codes are:
search_indexes.py
from haystack import indexes
from models import ProduitCommerce
class ProduitIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
commerce = indexes.CharField(model_attr = 'nom_commerce')
nom = indexes.CharField(model_attr = 'nom_produit')
price = indexes.DecimalField(model_attr = 'prix') #Field to filter ON
def get_model(self):
return ProduitCommerce
search/search.html
{% extends 'base.html' %}
{% block content %}
<h2>Search</h2>
<form method="get" action=".">
<table>
{{ form.as_table }} <!------ FORM TO CHANGE BY A CUSTOM FORM BLOCK TO INCLUDE WITH DJANGO ------->
<tr>
<td> </td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% if query %}
<h3>Results</h3>
<!---------------------------- PLACE TO INCLUDE THE BUTTON TO FILTER ON result.object.prix------------------>
{% for result in page.object_list %}
<p>
{{ result.object.nom_produit }}{{result.object.prix}}
</p>
{% empty %}
<p>No results found.</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %}
|
{% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %}
</div>
{% endif %}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
</form>
produitcommerce_text.txt
{{ object.nom_produit }}
{{ object.nom_commerce }}
{{ object.description }}
{{ object.prix }}
PS: I'm working on a django project with the 1.5 .1 version and whoosh like haystack backend.
Thanks for you help :)

You are getting the selectable model checkboxes because you're extending the ModelSearchForm:
#forms.py
from haystack.forms import ModelSearchForm
class ProduitForm(ModelSearchForm):
You can easily avoid this behaviour by extending the simple search form variant, provided by the haystack:
#forms.py
from haystack.forms import SearchForm
class ProduitForm(SearchForm):
Then you're going to add something like the following to you application's urls.py:
#urls.py
from haystack.query import SearchQuerySet
from haystack.views import SearchView, search_view_factory
from myapp.models import Produit
urlpatterns = patterns('',
url(r'^produit/search/$', search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=SearchQuerySet().models(Resume),
form_class=ProduitForm
), name='produit_search'),
)
Please note that there is a bug with .models() filter when trying to use it with latest versions of Haystack and Whoosh. If you would experience any kind of problems with the strategy above, then you should make sure, that your Haystack and Whoosh are of versions 2.0.0 and 2.4.1 relatively - tested and works well.
Also, not related to your question, you might want to avoid using HAYSTACK_SEARCH_RESULTS_PER_PAGE in your settings.py. It also has a very ugly bug. Just sharing my experience. Note that this info is related to Whoosh, everything should work fine with any other backend.

Related

Django_Filters :no need to show all items using object.all() at the first load

I am new for Django_Filter
As I notice, django_filter always loads all objects on the firs loading despite not filtered yet. Due to a large amount of data in the table in the database. So it results in poor performance for loading all objects unnecessarily.
it is rather unreasonable in my view. Thus the better way, it should filter a result whenever click button only.
How to set Django_Filter in order to show the result when only click the search button(I don't want to list all items by using objects.all()).
These are my code related to this problem
model.py
from django.db import models
class Product_Type(models.Model):
productype_name=models.CharField(max_length=100)
class Inventory(models.Model):
serial_number=models.CharField('Serial Number',max_length=50)
product_type=models.ForeignKey( Product_Type,on_delete=models.CASCADE,verbose_name='Product Type')
filter.py
import django_filters
from django_filters import *
from app.models import *
class InventoriesForCopy_Filter(django_filters.FilterSet):
class Meta:
model=Inventory
fields=['product_type','serial_number']
views.py
def list_inventory_for_copy(request, proj_id):
inventories_all = Inventory.objects.all()
xfilter = InventoriesForCopy_Filter(request.GET, queryset=inventories_all)
inventories = xfilter.qs
context = {'project_id': proj_id, 'xfilter': xfilter, 'inventories': inventories}
return render(request, 'app/inventory_add_copy.html', context)
inventory_add_copy.html
{% extends "app/layout.html" %}
{% block content %}
{% if messages %}
<ul>
{% for message in messages %}
<li><strong>{{ message }}</strong></li>
{% endfor %}
</ul>
{% endif %}
<form method="get">
{{xfilter.form}}
<button type="submit">Get Inventories</button>
</form>
Create Inventory for Project ID : {{ project_id }}
<table>
<tr>
<td>SerialNo</td>
<td>ProductType</td>
</tr>
{% for item in inventories %}
<tr>
<td>{{ item.serial_number }}</td>
<td>{{ item.product_type }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
The picture below describes itself:

How to add custom view to django admin interface?

My django admin interface looks like this:
Now I would like to add a view which does not correspond to a model.
I could overwrite the template of above page and add a custom link. But I think this would look ugly.
Example for overwriting admin/index.html:
{% extends "admin/index.html" %}
{% block content %}
{{ block.super }}
<div class="app-sonstiges module">
....
</div>
{% endblock %}
But maybe there is an official way to do add a custom view to the admin interface?
In my case I want to provide a form which can execute tcptraceroute to a remote server. The admin of my app needs this.
I used the same html tags. Now the link "tcptraceroute" looks nice, but unfortunately the messages moved down:
Is there a way to get a custom part like "Sontiges ... tcptraceroute" like in the screenshot, without moving the latest actions down?
Here is how the html structure looks like. My <div class="app-sonstiges"> is below content-main:
You have 3 options here:
Using third-party packages which provide menu
This is pretty straight forward, there are some good packages out there which support menu for admin, and some way to add your item to the menu.
An example of a 3rd party package would be django-admin-tools. The drawback is that it is a bit hard to learn. Another option would be to use django-adminplus but at the time of writing this, it does not support Django 2.2.
Hacking with django admin templates
You can always extend admin templates and override the parts you want, as mentioned in #Sardorbek answer, you can copy some parts from admin template and change them the way you want.
Using custom admin site
If your views are supposed to only action on admin site, then you can extend adminsite (and maybe change the default), beside from adding links to template, you should use this method to define your admin-only views as it's easier to check for permissions this way.
Considering you already extended adminsite, now you can use the previous methods to add your link to template, or even extend get_app_list method, example:
class MyAdminSite(admin.AdminSite):
def get_app_list(self, request):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom App",
"app_label": "my_test_app",
# "app_url": "/admin/test_view",
"models": [
{
"name": "tcptraceroute",
"object_name": "tcptraceroute",
"admin_url": "/admin/test_view",
"view_only": True,
}
],
}
]
return app_list
You can also check if current staff user can access to module before you show them the links.
It will look like this at then end:
Problem here that your div is not inside main-content. I suggest extending admin/index.html
Put in app/templates/admin/index.html
{% extends "admin/index.html" %}
{% load i18n static %}
{% block content %}
<div id="content-main">
{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
<!-- here you could put your div -->
<div class="app-sonstiges module">
....
</div>
<!-- here you could put your div -->
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
The another approach is to add custom app to app_list. But it is far more ugly. So I suggest overriding template.
I assume you are overriding get_urls in AdminSite.
If you need a custom page to handle user input (from form), you may want to give django-etc 1.3.0+ a try:
from etc.admin import CustomModelPage
class MyPage(CustomModelPage):
title = 'My custom page' # set page title
# Define some fields you want to proccess data from.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement data handling.
super().save()
# Register the page within Django admin.
MyPage.register()

Django select 2 multiple select slow load

I'm a beginner and I've been playing around with the multiple select option of select2.js. In a historical school database we have over 300k student_id's. I can get the select2 option to work, but it's extremely slow and takes forever to load. I've seen other pages with select 2 load massive amounts of data and work fine.
I'm using the following to javascript to load select2.
$(document).ready(function () {
$('.js-example-basic-multiple').select2();
});
In Django i'm loading the data in my template with:
<script src= "{% static '/search/user_select2.js' %}" type="text/javascript"></script>
<div class="col"><h4 style="margin-top: 0"><strong>Student ID List</strong></h4><select data-placeholder="Choose a list of 3-4 User ids..." class="js-example-basic-multiple" value = "{{ userid }}" style="width: 1110px" required>
{% for user in userid %}
<option value="{{ user }}"> {{ user }}</option>
{% endfor %}
</select>
userid is defined with the following arg in my view:
def multisearch(request):
userid = STudent.objects.filter(student_status_id = 1).values('studentid')
print(userid)
args = {'userid':userid}
return render(request, 'multisearch.html',args)
It takes a long time to load be cause you preload all options of your select2 input. I would suggest you to use select2 through django-autocomplete-light to avoid this issue. It will provide you tools to setup an autocomplete system and load matching options while typing text on your select2 input. Moreover, results paginated so that they are loaded as you scroll the select2 dropdown.
Basically you will have an autocomplete view:
from dal import autocomplete
from your_app.models import Student
class Status1StudentsAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Student.objects.filter(student_status_id=1)
if self.q: # self.q is the user-typed query
qs = qs.filter(name__istartswith=self.q)
return qs
That, of course, needs to be routed:
from your_app.views import Status1StudentsAutocomplete
urlpatterns = [
url(
r'^autocomplete/students/status-1/$',
Status1StudentsAutocomplete.as_view(),
name='students_status1_autocomplete',
),
]
Then use an autocomplete widget for your field:
from dal import autocomplete
from django import forms
class SomeForm(forms.Form):
student = forms.ModelChoiceField(
queryset=Student.objects.filter(student_status_id=1),
widget=autocomplete.ModelSelect2(url='student_status1_autocomplete')
)
Finally just display your form as you usually would and don't forget to include the custom css/js with {{ form.media }}.
{% extend "your_layout.html" %}
{% block extrahead %} {# assuming extrahead block is within the <head> element #}
{{ block.super }}
{{ form.media }}
{% endblock %}
{% block content %} {# assuming you have a content block within your <body> element #}
<form method="POST">
{% csrf_token %}
{{ form }}
<button type="submit">Go!</button>
</form>
{% endblock %}

Django nested QuerySet yielding nothing

I am using Django 1.8 with Postgres 9.2 on a Windows 8 machine.
I have two pieces of nearly identical code from two of my projects. One works and the other doesn't.
Here's the code that works:
# views.py
from django.shortcuts import render
from models import Artist, Track
def music(request):
artists = Artist.objects.all().order_by('orderName')
artistTrackCollections = []
for artist in artists:
artist.tracks = Track.objects.filter(artist=artist).order_by('order')
artistTrackCollections.append(artist)
return render(request, 'music.html', {'artistTrackCollections': artistTrackCollections,})
And the relevant template code:
{% for artist in artistTrackCollections %}
<dl>
<dt>
{% if artist.website %}
<h2>{{ artist.name }}</h2>
{% else %}
<h2>{{ artist.name }}</h2>
{% endif %}
</dt>
<dd>
<ul>
{% for track in artist.tracks %}
<li>“{{ track.title }}”
<i>({{ track.album.title }})</i>
{% endfor %}
</ul>
</dd>
</dl>
{% endfor %}
Now here's pretty much the exact same code from a different project of mine that doesn't work anymore:
def index(request):
productList = PartModel.objects.filter(isBuild=True, isActive=True).order_by('name')
productCollection = []
for product in productList:
product.part_list = product.buildpart.all().order_by('family__type')[:5]
productCollection.append(product)
return render(request, 'index.html', { 'productCollection': productCollection, })
and its corresponding template:
{% for product in productCollection %}
<p>{{ product.name }}
<p>${{ product.price }}
<ul>
{% for part in product.part_list %}
<li>{{ part.family.type.name }} | {{ part.name }}
{% endfor %}
</ul>
{% endfor %}
This code used to work but now it doesn't. In the code that works I succeed in going through each artist and attaching that artist's tracks. In the code that fails I try to go through each PartModel instance that is a build and get that PartModel instance's corresponding parts. They are identical as far as I can tell. productCollection gets populated but the part_list data for some reason is blank. This leads me to believe the problem is with this line:
product.part_list = product.buildpart.all().order_by('family__type')[:5]
But I cannot discern the difference from this line:
artist.tracks = Track.objects.filter(artist=artist).order_by('order')
Thanks in advance for any help!

Thumbnails in the django admin panel using sorl

I am trying to have the pictures I upload in the Django admin panel to show up as thumbnails instead of the path. I have sorl installed and can make thumbnails that show up in my views.
I have found 2 snippets (http://www.djangosnippets.org/snippets/579/ and http://www.djangosnippets.org/snippets/934/) that I have tried to implement, but both attempts have failed because of the meager documentation and my as yet shallow understanding of the Django framework.
Could someone please provide a dumbed-down step-by-step guide of how I can get this to work?
Thanks!
I know this is an old post, but I think I may have found a shorter way than the admin template override method above (though that is a pretty awesome solution - I'll have to use it in another circumstance). This simply does a call directly to the sorl function to generate the thumbnail.
In myapp/admin.py
from myapp import models
from sorl.thumbnail import default
ADMIN_THUMBS_SIZE = '60x60'
class MyModelAdmin(admin.ModelAdmin):
model = models.MyModel
list_display = ['my_image_thumb', 'my_other_field1', 'my_other_field2', ]
def my_image_thumb(self, obj):
if obj.image:
thumb = default.backend.get_thumbnail(obj.image.file, ADMIN_THUMBS_SIZE)
return u'<img width="%s" src="%s" />' % (thumb.width, thumb.url)
else:
return "No Image"
my_image_thumb.short_description = 'My Thumbnail'
my_image_thumb.allow_tags = True
I based this off of the following question but added the sorl functionality.
Django admin and showing thumbnail images
Yeah :) I can ;)
First you need to create a custom template tag that handles the thumbnail:
from django.template import Library
from django.utils.safestring import mark_safe
from django.contrib.admin.templatetags.admin_list import result_headers
register = Library()
def results(cl):
out = []
for item in cl.result_list:
url = cl.url_for_result(item)
code = '%(img)s <div>%(title)s</div>' % {
'url': url,
'img': item.preview.thumbnail_tag,
'title': item.title,
}
out.append(mark_safe(code))
return out
def gallery_result_list(cl):
return {'cl': cl,
'result_headers': list(result_headers(cl)),
'results': results(cl)}
result_list = register.inclusion_tag("admin/app_name/model/change_list_results.html")(gallery_result_list)
where item.preview.thumbnail_tag is the thumnail created by sorl :)
[I got the original code from the default template tag]
Second you need to create a template for your model (that uses the new custom template tag), it must be in this directory schema:
templates_dir/admin/app_name/model/change_list.html
and have the following code:
{% extends "admin/change_list.html" %}
{% load adminmedia admin_list my_admin_tags i18n %}
{% block result_list %}
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
{% gallery_result_list cl %}
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
{% endblock %}
as you can see in the tag function you need to create one more template (called change_list_result.html) for display the image correctly:
<style>
td.page { text-align: center; }
td.page a { font-weight: bold; }
</style>
{% if results %}
<table cellspacing="0">
<tbody>
<tr>
{% for result in results %}
<td class="page">
{{ result }}
</td>
{% if forloop.counter|divisibleby:3 %}
</tr><tr>
{% endif %}
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
so at the end you'll have 3 files:
templates_dir/admin/app_name/model_name/change_list.html
templates_dir/admin/app_name/model_name/change_list_result.html
your_project/app_name/templatetags/my_admin_tags.py
and, of course, templatetags must be added to INSTALLED_APP in settings ;)
this is all ;) Hope this can be helpful.