How to use custom field for search in django admin - python

i have a model and is registered to the admin and i have used custom field to display in the list
class ReportsAdmin(admin.ModelAdmin):
def investment(self, inst):
return models.OrderDetail.objects.filter(user=inst.user).distinct().count()
list_display = ['investment']
search_fields = ['investment']
i want to search using the investment field in django admin but always getting Cannot resolve keyword 'investment' into field. choices are the Model fields.
is there any way by which i can search using the investment field?

In software, anything is possible... SMH at the accepted answer. You have to override get_search_results.
from django.db.models import Count
class ReportsAdmin(admin.ModelAdmin):
def investment(self, inst):
return models.OrderDetail.objects.filter(user=inst.user).distinct().count()
list_display = ['investment']
search_fields = ['investment']
def get_search_results(self, request, queryset, search_term):
# search_term is what you input in admin site
search_term_list = search_term.split(' ') #['apple','bar']
if not any(search_term_list):
return queryset, False
if 'investment' in search_term_list:
queryset = OrderDetail.objects.annotate(
user_count=Count('user')
).filter(user_count__gte=search_term_list['investment'])
return queryset, False

Well, this is not allowed:
ModelAdmin.search_fields
Set search_fields to enable a search box on the admin change list
page. This should be set to a list of field names that will be
searched whenever somebody submits a search query in that text box.
These fields should be some kind of text field, such as CharField or
TextField. You can also perform a related lookup on a ForeignKey or
ManyToManyField with the lookup API “follow” notation:
You don't have such a field at all (never mind that the field has to be a TextField or CharField). What you actually have is a method in your admin class, which cannot be searched at the database level. Ie what's in the search_fields translates to like '%search_term%' type queries executed at the db.

Related

Limiting choices in foreign key dropdown in Django using Generic Views ( CreateView )

I've two models:
First one:
class A(models.Model):
a_user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
a_title = models.CharField("A title", max_length=500)
Second one:
class B(models.Model):
b_a = models.ForeignKey(A, verbose_name=('A'), unique=False, on_delete=models.CASCADE)
b_details = models.TextField()
Now, I'm using CreateView to create form for Value filling :
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
Then using this to render these field in templates.
Now, my problem is, while giving the field b_a ( which is the dropdown ), it list downs all the values of model A, but the need is to list only the values of model A which belongs to the particular logged in user, in the dropdown.
I've seen all the answers, but still not able to solve the problem.
The things I've tried:
limit_choices_to in models : Not able to pass the value of A in the limit_choices
form_valid : Don't have the model A in the CreateView, as only B is reffered model in B_Create
passing primary key of A in templates via url : Then there is no instance of A in the template so can't access. Also, don't want to handle it in templates.
I'm new to Django and still learning, so don't know to override admin form.
Please suggest the implemented way, if possible to the problem. I've researched and tried most of the similar questions with no result for my particular problem. I feel like, this is a dumb question to ask, but I'm stuck here, so need help.
Thanks..
(Please feel free to suggest corrections.)
You have access to self.request.user in the form_valid of the view. But in order to limit the choices in the form you have to customize the form before it is served initially. You best override the view's get_form and set the form field's queryset:
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
def get_form(self, *args, **kwargs):
form = super(B_Create, self).get_form(*args, **kwargs)
form.fields['b_a'].queryset = self.request.user.a_set.all()
# form.fields['b_a'].queryset = A.objects.filter(a_user=self.request.user)
return form
Generally, there are three places where you can influence the choices of a ModelChoiceField:
If the choices need no runtime knowledge of your data, user, or form instance, and are the same in every context where a modelform might be used, you can set limit_choices_to on the ForeignKey field itself; as module level code, this is evaluated once at module import time. The according query will be built and executed every time a form is rendered.
If the choices need no runtime knowledge, but might be different in different forms, you can use custom ModelForms and set the queryset in the field definition of the respective form field.
If the queryset needs any runtime information, you can either override the __init__ of a custom form and pass it any information it needs to set the field's queryset or you just modify the queryset on the form after it is created which often is a quicker fix and django's default views provide nice hooks to do that (see the code above).
The #schwobaseggl answer is excellent.
Here is a Python 3 version. I needed to limit the projects dropdown input based on the logged-in user.
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
template_name = 'brand/product-create.html'
fields = '__all__'
def get_form(self, form_class=None):
form = super().get_form(form_class=None)
form.fields['project'].queryset = form.fields['project'].queryset.filter(owner_id=self.request.user.id)
return form

Django Admin, sort with custom function

How can I sort a column in Django admin by some simple custom method?
(All the answers I got through was by using annotate but I don't know how to use it my case).
Assume the model
class Shots(models.Model):
hits = models.PositiveIntegerField()
all = models.PositiveIntegerField()
In admin site I would like to sort by hits to all ratio:
class ShotAdmin(admin.ModelAdmin):
list_display = ['ratio']
def ratio(self, obj):
return obj.hits / obj.all * 100
I know the ratio is not a field in DB thus simple ratio.admin_order_field = 'ratio' won't work and I need to somehow append this as a field but I have no idea how.
By following:
The accepted answer to this post:
Django admin: how to sort by one of the custom list_display fields that has no database field
The
How to execute arithmetic operations between Model fields in django
(Disclaimer: I have composed that Q&A style example)
We can compose a solution to your problem:
from django.db.models import F
class ShotsAdmin(admin.ModelAdmin):
list_display = ('get_ratio',)
def get_queryset(self, request):
qs = super(ShotsAdmin, self).get_queryset(request)
qs = qs.annotate(ratio=F('hits') * 100 / F('all'))
return qs
def get_ratio(self, obj):
return obj.ratio
get_ratio.admin_order_field = 'ratio'
Explanation:
The get_queryset method will annotate a new field named ratio to
your queryset. That field's value is the application of your ratio function on the hits and all fields.
The get_ratio function returns the aforementioned field from a queryset instance.
Finally: get_ratio.admin_order_field = 'ratio' sets the ratio field as the ordering field for your queryset on the admin panel.

how to achieve comparison queries in Django Admin by input widget not just select boxes?

I am just a Django beginner. I want to achieve a functionality of comparison queries in Django Admin since search_fields and list_filter could not meet my requirements.
Here is an example:
I have a table Sports which has an attribute called Speed, so naturally I want to get the querysets of all the Speed are greater than 2.
First of all I could select gt, lt, gte or lte respectively, then I could set a value by input widget as a limit, at the end I could get the querysets by clicking on search button.
However, I have learned that list_filter can filter the item by select a value? Is that right? That is not what I want. Are there any methods to achieve the function?
Django QuerySet Field Lookups are very handy tools for such case:
Sports.objects.filter(speed__gt=2) will return the elements with speed greater than 2
in admin.py:
from datetime import date
from django.contrib import admin
class SportAdmin(admin.ModelAdmin):
"""
use raw_id_fields to tell django don't user select-box interface for this field
"""
raw_id_fields = ("speed",)
list_filter = (SportListFilter,)
class SportListFilter(admin.SimpleListFilter):
title = 'Sport'
# Parameter for the filter that will be used in the URL query.
parameter_name = 'speed'
def lookups(self, request, model_admin):
pass
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
return queryset.filter(speed__gt=int(self.value()))
class SportAdmin(admin.ModelAdmin):
list_filter = (SportListFilter,)
Check here for more detailed example
Check raw_id_fields

Set value of excluded field in django ModelForm programmatically

I have a ModelForm class where I want to exclude one of the fields (language) from the form and add it programmatically. I tried to add the language in the clean method, but got an IntegrityError ("Column 'language_id' cannot be null"). I assume this is because any data for language returned from clean() will be ignored since it's in the exclude tuple. I'm using the form in a ModelAdmin class. I haven't managed to find any way to deal with this, so any tips or hints will be much appreciated.
from django import forms
from myapp import models
from django.contrib import admin
class DefaultLanguagePageForm(forms.ModelForm):
def clean(self):
cleaned_data = super(DefaultLanguagePageForm, self).clean()
english = models.Language.objects.get(code="en")
cleaned_data['language'] = english
return cleaned_data
class Meta:
model = models.Page
exclude = ("language",)
class PageAdmin(admin.ModelAdmin):
form = DefaultLanguagePageForm
What you can do is when you save the form in the view, you don't commit the data. You can then set the value of the excluded field programmatically. I run into this a lot when I have a user field and I want to exclude that field and set to the current user.
form = form.save(commit=False)
form.language = "en"
form.save()
Here's a little more info on the commit=False --> https://docs.djangoproject.com/en/1.6/topics/forms/modelforms/#the-save-method

Django adminsite customize search_fields query

In the django admin you can set the search_fields for the ModelAdmin to be able to search over the properties given there. My model class has a property that is not a real model property, means it is not within the database table. The property relates to another database table that is not tied to the current model through relations.
But I want to be able to search over it, so I have to somehow customize the query the admin site creates to do the filtering when the search field was filled - is this possible and if, how?
I can query the database table of my custom property and it then returns the ids of the model classes fitting the search. This then, as I said, has to flow into the admin site search query.
Thanks!
Since django 1.6, you can customize the search by defining a get_search_results method in your ModelAdmin subclass.
It is well explained in django documentation. The following example is copied from this doc.
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'age')
search_fields = ('name',)
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super(PersonAdmin, self).get_search_results(request, queryset, search_term)
try:
search_term_as_int = int(search_term)
queryset |= self.model.objects.filter(age=search_term_as_int)
except:
pass
return queryset, use_distinct
You should use search_fields = ['foreign_key__related_fieldname']
like search_fields = ['user__email'] in the admin class that extends admin.ModelAdmin
read more here
this might can help
search_fields = ['foreign_key__related_fieldname']
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields

Categories