Skip to content

Instantly share code, notes, and snippets.

@gauravvjn
Last active August 17, 2023 20:27
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save gauravvjn/b264562ba66067f85da7a55063cbfa64 to your computer and use it in GitHub Desktop.
Save gauravvjn/b264562ba66067f85da7a55063cbfa64 to your computer and use it in GitHub Desktop.
Use JSONField properties in Django admin filter Raw
"""
More details on the implementation and usage can be found at
https://www.pyscoop.com/django-jsonfield-attributes-in-admin-filter/
"""
from django.contrib import admin
from django.core.exceptions import ImproperlyConfigured
class JSONFieldFilter(admin.SimpleListFilter):
"""
Base JSONFilter class to use by individual attribute filter classes.
e.g data in JSON field
{
"name": "Gaurav",
"company": "pyscoop",
"address": {
"city": "Jaipur",
"country": {"name": "India", "code": "IN"}
}
}
"""
model_json_field_name = None # name of the json field column in the model
json_data_property_name = None # name of one attribute from json data
def get_child_value_from_json_field_data(self, json_field_data):
key_list = self.json_data_property_name.split('__')
for key in key_list:
if isinstance(json_field_data, dict):
json_field_data = json_field_data[key]
return json_field_data
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 2nd element is the human-readable name for the option that will appear in the right sidebar.
"""
if self.model_json_field_name is None:
raise ImproperlyConfigured(f'Filter class {self.__class__.__name__} does not specify "model_json_field_name"')
if self.json_data_property_name is None:
raise ImproperlyConfigured(f'Filter class {self.__class__.__name__} does not specify "json_data_property_name"')
field_value_set = set()
for json_field_data in model_admin.model.objects.values_list(self.model_json_field_name, flat=True):
field_value_set.add(self.get_child_value_from_json_field_data(json_field_data))
return [(v, v) for v in field_value_set]
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value provided in the query string & retrievable via `self.value()`
"""
if self.value():
json_field_query = {f'{self.model_json_field_name}__{self.json_data_property_name}': self.value()}
return queryset.filter(**json_field_query)
else:
return queryset
class CompanyFilter(JSONFieldFilter):
model_json_field_name = 'jsonfield'
json_data_property_name = 'company' # property/field in json data
title = 'Company' # for admin sidebar
parameter_name = 'js_company' # Prefixing with `js_` because one might have another column named `company`, will be used in the URL query
@BartlomiejSkwira
Copy link

This is awesome, thanks for sharing! 👍

@JakubDotPy
Copy link

JakubDotPy commented Oct 8, 2021

Doesn't work with integers.
See my fork with improvements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment