How to Customize Django Admin Filter - django

Using django 1.7.7 admin page, I want to list the data in a table where I can sort and filter.
That's exactly what the picture on the documentation shows:
https://docs.djangoproject.com/en/1.7/ref/contrib/admin/
under the list_filter section.
I tried that and got an error:
The value of 'list_display[0]' refers to 'book_type', which is not a callable.
Then, I tried this:
# model.py:
class Interp():
""" specifies the interpretation model """
BOOK_TYPES = ['drama', 'scietific']
BOOK_TYPES = tuple(zip(BOOK_TYPES, BOOK_TYPES))
comment = models.CharField(max_length=20)
book_type = models.CharField(max_length=20, choices=BOOK_TYPES,
default='not specified')
def __unicode__(self):
return self.book_type
# admin.py:
class bookTypeFilter(SimpleListFilter):
title = 'book type'
parameter_name = 'book_type'
def lookups(self, request, model_admin):
types = set([t.book_type for t in model_admin.model.objects.all()])
return zip(types, types)
def queryset(self, request, queryset):
if self.value():
return queryset.filter(book_type__id__exact=self.value())
else:
return queryset
class AdminInterpStore(admin.ModelAdmin):
""" admin page setting """
search_fields = ('comment', 'book_type')
list_display = ('book_type',)
list_filter = (bookTypeFilter, )
this shows a sidebar and lists the values but as soon as I click any of them, I get an error:
FieldError: Unsupported lookup 'id' for CharField or join on the field not permitted
How can I make a filter, preferably a table view as well?
Is the a complete sample for django1.7.7 that I can look at for filtering admin page?

You must replace
return queryset.filter(book_type__id__exact=self.value())
by
return queryset.filter(book_type=self.value())
See https://docs.djangoproject.com/en/1.8/ref/models/querysets/#field-lookups for more information.
Also, in the lookups method, using:
types = model_admin.model.objects.distinct('book_type').values_list('book_type', flat=True)
would be much more efficient as you would not retrieve the whole data from the database to then filter it.
Edit:
I realized that the book_type field is a choice field.
First, the value passed to the choices argument must be
An iterable (e.g., a list or tuple) consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...] A being the value stored in the database, B a human readable value)
So passing a list of strings will fail.
Second, the Django admin's default behaviour for such fields is exactly what you are trying to achieve so setting list_filter = ('book_type', ) in AdminInterpStore is enough.

Related

django set ModelChoiceField's "to_field_name" to multiple columns

I want to provide multiple field names in Django's modelChoiceField's to_field_name
something like
field1 = forms.ModelChoiceField(queryset=myModel.objects.all(), required=False,
empty_label="--------",
to_field_name=["col1, col2"],
widget=forms.Select(attrs={'class':'form-control'}),
)
I have a model like
class Codes(models.Model):
item_code = models.CharField(max_length=50)
item_name = models.CharField(max_length=50)
def __str__(self):
return self.item_code + self.item_name
Now I can set queryset as
field1 = forms.ModelChoiceField(queryset=Codes.objects.all(), required=False,
empty_label="--------",
widget=forms.Select(attrs={'class':'form-control'}),
)
And I will get the combination of two fields in my select tag options
everything works but when I open the edit page, the select box doesn't show the default selected value
Also, I want a combination of fields because when item_code repeats in the database, to_field_name gives an error.
I believe you are interpreting to_field_name wrong:
According to the Django website (Django to_field_name explained):
This optional argument is used to specify the field to use as the value of the choices in the field’s widget. Be sure it’s a unique field for the model, otherwise the selected value could match more than one object. By default it is set to None, in which case the primary key of each object will be used...
Which means you can use the to_field_name to address the form object in HTML.
But it does not say anything about multiple to_field_name values.
Normally the primary key is used to address the object. If you set the to_field_name then that field will be used.
To actually add data into the modelChoiceField object, you would need to do something like this.
class CustomForm(ModelForm):
muscles = forms.ModelMultipleChoiceField(
queryset=None,
to_field_name='name',
required=False,)
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = Users.objects.all()

Show distinct values in dropdown menu with django-select2

I am using django-select2 with forms for creating a drop-down list, using ModelSelect2Widget.
My problem is that I filter on the field 'name' and several objects can have the same 'name' but different 'value' (see model description below). This leads to the same name appearing several times in the dropdown menu.
I would like to remove these duplicates. I tried to use .distinct('name') for selecting the queryset, but it doesn't seem to work.
example of result obtained with ModelSelect2Widget
Below is the description of the code I use:
I have two models linked by a foreign key
models.py
class Who(models.Model):
name = models.CharField(max_length=256)
value = models.CharField(max_length=256)
def __str__(self);
return str(self.name)
class Data(models.Model):
who = models.ForeignKey(Who)
And I use the form described here:
forms.py
from django_select2.forms import ModelSelect2Widget
...
class CreateDataForm(ModelForm):
class Meta:
model = Data
fields = ('who',)
widgets = {'who': ModelSelect2Widget(
queryset=Who.objects.all().distinct('name'),
search_fields=['name_icontains']
)}
Does anyone know how I could remove these duplicates?
I finally found the source of the problem.
In django_select2 ModelSelect2Mixin, the function filter_queryset which is used to filter the values to show on the dropdown menu ends by "return queryset.filter(select).distinct()". This problem is that this .distinct() prevails on the one from the queryset (.distinct("name"))
In order to solve my problem, I had to override the filter queryset function, and remove the .distinct():
class ModelSelect2WidgetWithoutDistinct(ModelSelect2Widget):
"""In dropdown list, shows objects from queryset without the initial .distinct()."""
def filter_queryset(self, request, term, queryset=None, **dependent_fields):
"""Same function as in select2 ModelSelect2Mixin except last line."""
if queryset is None:
queryset = self.get_queryset()
search_fields = self.get_search_fields()
select = Q()
term = term.replace('\t', ' ')
term = term.replace('\n', ' ')
for t in [t for t in term.split(' ') if not t == '']:
select &= reduce(lambda x, y: x | Q(**{y: t}), search_fields,
Q(**{search_fields[0]: t}))
if dependent_fields:
select &= Q(**dependent_fields)
return queryset.filter(select) # initially queryset.filter(select).distinct()
# You can define queryset like this
class CreateDataForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['who'].queryset = Who.objects.distinct()
class Meta:
model = Data
fields = ('who',)

DJANGO , custom LIST_FILTER

I am using only the django admin , and trying to creating a custom filter, where is to filter the date of another model.
My models
class Avaria(models.Model):
.....
class Pavimentacao(models.Model):
avaria = models.ForeignKey(Avaria, related_name='AvariaObjects',on_delete=models.CASCADE)
date= models.DateField(blank=True,null=True)
AvariaAdmin
class AvariaAdmin(admin.ModelAdmin):
list_filter = ('')
For example, Let's say you have a model and you have to add custom ContentTypeFilter to your model admin then. you can define a class which inherit SimpleListFilter and define lookups and queryset based on your requirement and add this class to list_filter like
list_filter = [ContentTypeFilter]
Refer to docs
Example class definition is like below:
class ContentTypeFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('content type')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'type'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
models_meta = [
(app.model._meta, app.model.__name__) for app in get_config()
]
return (item for item in models_meta)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if not self.value():
return
model = apps.get_model(self.value())
if model:
return queryset.models(model)
You have to add the field you want to filter. In your example if you want to filter on date you put list_filter('date'). Dont forget to register the model admin as seen here

How to filter a QuerySet with relation fields compared to a (dynamic) list

I have a Requests Model which has a one-to-many relation to its RequestDetails. I also have a RequestFilter that has a one-to-one relation to the auth.user. What i'm trying to do is to display all Requests in a generic ListView, which have at least one RequestDetail that has a category enabled by the RequestFilter settings. I tried to accomplish this in different ways but still end up with a "Relation fields do not support nested lookups" Error. Here is what I did:
Given Models:
A User specifies the Requests (not django user.request) he wants to receive in a settings menu based on the following RequestFilter Model:
class RequestFilter(models.Model):
user = models.OneToOneField(User)
category1_enabled = models.BooleanField(...)
category2_enabled = models.BooleanField(...)
category3_enabled = models.BooleanField(...)
...
def get_categories_enabled(self):
returns a list of category names which are set to True e.g. ['category1', 'category3']
The Requests themselves contain basic information and a reference code:
class Requests(models.Model):
request_id = models.IntegerField(unique=True, ...)
reference = models.CharField(max_length=10)
some_general_info = models.CharField(...)
...
One Request can have many RequestDetails (like Orders which have many Products)
class RequestDetail(models.Model):
request = models.ForeignKey(Requests, to_field='request_id', related_name='details')
product_id = models.IntegerField()
category_id = models.IntegerField()
...
def get_category_name:
returns the name of the category of the RequestDetail e.g. 'category3'
I have a generic class based ListView which should display all Requests that contain at least one RequestDetail that is of a category which the User has set to enabled in the settings (RequestFilter).
views.py
class DashManagerRequestsView(generic.ListView):
template_name = 'dashboard/dashmanagerrequests.html'
context_object_name = 'request_list'
def get_categories_enabled(self):
return self.request.user.requestfilter.get_categories_enabled()
def get_queryset(self):
"""Returns all requests as an ordered list."""
requestfilter_list = self.get_categories_enabled()
return Requests.objects.order_by('-date_add').filter(details__get_category_name__in=requestfilter_list)
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(DashManagerRequestsView, self).dispatch(*args, **kwargs)
I also tried using an F() expression but this doesn't work because it only compares the values of two different fields on the SAME model instance. The closest I got was to get a list of the enabled categories for the user and lookup if the category name is a part of this list with:
return Requests.objects.order_by('-date_add').filter(details__get_category_name__in=requestfilter_list)
but this raises a Relation fields do not support nested lookups Error.
thanks to schillingt, who pushed me into the right direction i came up with a solution.
since it is not possible to use model methods for filtering a QuerySet i generate a list of allowed ids during execution outside the filter function by relating to the RequestDetail model method using a second for loop. of course this can also be done using a list comprehension or something like that:
def get_queryset(self):
queryset = Requests.objects.order_by('-ps_date_add')
request_ids = []
for request in queryset:
for detail in request.details.all():
if detail.get_category_name() in self.get_categories_enabled():
request_ids.append(request.id)
q = q.filter(id__in=request_ids)
return q
this may not be the best solution in terms of efficiency if it comes to large amounts of data.

Django admin search_fields with model property

I'm trying to use a property in my model as a field in django admin (1.2).
Here's an example of my code:
class Case(models.Model):
reference = models.CharField(_(u'Reference'), max_length=70)
client_read = models.BooleanField(default=0)
def __unicode__(self):
return self.case_id
#property
def case_id(self):
""" unique case ID """
number = (settings.CASE_ID_LENGTH - len(str(self.id))) * "0" + str(self.id)
return '%(year)s%(unique_id)s' % {
'year': self.case_date.strftime("%y"),
'month': self.case_date.strftime("%m"),
'unique_id': number}
and the part of admin.py:
class OrderAdmin(ReadOnlyAdminFields, admin.ModelAdmin):
[...]
search_fields = ('id','case','req_surname','req_forename','req_company')
I can refer to the field as 'case' (like given in the example), but this gives me a TypeError: Related Field has invalid lookup: icontains
Of course it's working the way with related fields: so I can use case__id and then I'm able to use the id as search query.
But this is somewhat irritating to the users cause the caseid is shown different.
Is there a way to use the case_id as search query like it's shown (year+month+id)?
No you cannot use it that way, because this only works with attributes that represent columns in the database, not with properties. The only way to make this work would be using a subclass of contrib.admin.views.main.ChangeList for the change list and overwrite it's get_query_set method to achieve the desired behaviour!