Django adminsite customize search_fields query - python

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

Related

Parameterized property in Django

For example, I have two models:
class Page(models.Model):
# Some fields
...
#property
def title(self):
return PageTranslation.objects.get(page=self, language=language).title # I can not pass property to the parameter
class PageTranslation(models.Model):
page = models.ForeignKey(Page)
title = models.CharField()
And some DRF view, which get_queryset method looks like this:
def get_queryset(self):
return Page.objects.all()
And serializer:
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = (..., 'title',) # title = property
I want to return QuerySet with Page model instances, and use title property in serializer, but I can not pass language (that is set somewhere in the request — headers, query param, etc) there.
What is the correct way to do this?
from django.utils.translation import get_language
from django.config import settings
class Page(models.Model):
#property
def title(self):
language = get_language() or settings.LANGUAGE_CODE
return PageTranslation.objects.get(page=self, language=language).title
get_language() gives you the current active language, if i18n is disabled it gives you None, and for that we have the settings.LANGUAGE_CODE fallback.
For the serializer part, I think you are supposed to explicitly say that your property is a field, ModelSerializer only finds the actual database fields for you, nothing else.
class PageSerializer(serializers.ModelSerializer):
title = serializers.Field()
class Meta:
model = Page
fields = (..., 'title',)
Register django.middleware.locale.LocaleMiddleware in your project settings. This makes LANGUAGE_CODE property available in the request
In DRF view where there is the request context, filter QuerySet by the language.
def get_queryset(self):
return Page.objects.filter(language=self.request.LANGUAGE_CODE)
Then computed title property declared in the Page model is not necessary and inefficient.
This is why:
N queries are executed per record in the original QuerySet to serialise a value for the title field. There is great likelihood for an N+1 problem to occur when another model has a many-to-many relation with Page.
Also, serialised results can be inconsistent because title value can be null in cases where the record doesn't exist for the language.

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

Update Model Instance via Django REST POST

I've got a model "Assignment" that I want to be able to update through the api.
Updated per comments
urls.py
router = routers.DefaultRouter()
router.register(r'assignments', views.AssignmentList, base_name='Assignments')
urlpatterns = [
url(r'^api/v1/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
serializers.py
class AssignmentSerializer(serializers.ModelSerializer):
class Meta:
model = Assignment
fields = (
'id', 'company', 'farm', 'sensor', 'name', 'location', 'notes', 'units', 'max_height', 'frequency',
'interval', 'max_threshold', 'min_threshold', 'notify', 'last_alert_time', 'last_alert_water_level')
views.py
class AssignmentList(viewsets.ModelViewSet):
serializer_class = AssignmentSerializer
pagination_class = None
def get_queryset(self):
queryset = Assignment.objects.all()
company_id = self.request.query_params.get('company_id', None)
sensor_id = self.request.query_params.get('sensor_id', None)
if company_id is not None:
queryset = Assignment.objects.filter(company_id=company_id)
if sensor_id is not None:
queryset = Assignment.objects.filter(sensor_id=sensor_id)
return queryset
Currently my view allows for easy filtering based on two of the fields, 'company_id' and 'sensor_id'. This allows for easy access of the data in json. Unfortunately I can't figure out how to POST back even with the built in API form. I would like to be able to filter down to a single instance and edit a single field, let's say "assignment.name" for now.
It's my understanding that...
The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). (DRF Docs)
So what do I need to do to leverage them to edit a model instance via url? Or honestly just edit one period. I've been running in circles trying different Mixins, views (UpdateAPIView, RetrieveUpdateAPIView etc.) and this question in particular Stack Overflow: Django Rest Framework update field.
Why you're lacking the required foreign key fields is entirely the question.
The reason is that presumably your fields are called company, farm and sensor, not company_id etc. Because the underlying database fields are called company_id and so on, DRF detects them as read-only properties on the model and so allows you to specify those names in the fields tuple without showing an error, but doesn't display the field in the browsable API. Change the fields tuple to include the actual names of the Django fields.
Note also that since you're using HyperlinkedModelSerializer, DRF it expects a linked ViewSet for those related models. If you don't have those defined, chnage it to a basic ModelSerializer.

How to use custom field for search in django admin

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.

Set a custom queryset (select_related) for a list field in the admin change page?

I have an using the Django admin interface to manage a lot of objects, and one of the page is giving me issue, this page has a field to a related object (Foreign Key) that has a __str__ that also goes to its related objects, this make a lot of queries and is barely useable (Around 3000 queries to show the page as there are a LOT of objects).
I would like to know if there is a way to set a custom queryset ? I would like to add a select_related or prefetch_related to this element.
The part causing issue is this certificate requests list :
The page model (Certificate has the following attribute:
class Certificate(models.Model):
certificate_request = models.OneToOneField(
"CertificateRequest",
verbose_name=_("Certificate request"),
related_name="certificate",
blank=True,
null=True
)
And the related model has this :
class CertificateRequest(models.Model):
domain = models.ForeignKey(
"Domain",
verbose_name=_("Domain"),
related_name="certificate_requests"
)
def __str__(self):
return "{state} certificate request for {domain} from {creation_date}".format(
state=dict(self.STATUS).get(self.status),
domain=self.domain.fqdn,
creation_date=self.creation_date
)
What would be the way to fix this ? How can I set a queryset on this part ?
EDIT: I added more informations.
I tried using a custom form, but this didn't do any change :
class CertificateForm(forms.ModelForm):
certificate_request = forms.ModelChoiceField(queryset=CertificateRequest.objects.select_related("domain"))
class Meta:
model = Certificate
fields = "__all__"
#admin.register(Certificate)
class CertificateAdmin(CompareVersionAdmin):
model = Certificate
class Meta:
form = CertificateForm
You can create a custom ModelForm for your admin where you specify a ModelChoiceField for the ForeignKey. Here you can specify the queryset parameter:
# forms.py
class MyForm(forms.ModelForm):
certificate_request = forms.ModelChoiceField(queryset=CertReq.objects.foo().bar())
# select/prefetch-------^^^^^^^^^^^
class Meta:
model = Foo
# admin.py
class YourAdmin(ModelAdmin):
form = MyForm
The get_object method on the ModelAdmin class is what is responsible for retrieving the object to edit. You could certainly extend that method in your subclass to use select_related as necessary.

Categories