Skip to content

Instantly share code, notes, and snippets.

@tashirka1
Forked from gauravvjn/admin.py
Created December 10, 2018 16:39
Show Gist options
  • Save tashirka1/2da79c08357317fbfb23b7675aedd958 to your computer and use it in GitHub Desktop.
Save tashirka1/2da79c08357317fbfb23b7675aedd958 to your computer and use it in GitHub Desktop.
Use JSONField properties in Django admin filter Raw
# You have a model something like this
from django.contrib.postgres.fields import JSONField
class MyModel(models.Model):
jsonfield = JSONField() # {"name": "Gaurav", "age": "25", "address": {"country": "India", "city": "Jaipur"}}
# few more fields...
# And in admin you want to create filter for jsonfield properties/keys
# for e.g. in above case we want to show filter for age and country
# something like below
class MyModelAdmin(admin.ModelAdmin):
list_filter = ["jsonfield__age", "jsonfield__address__country"]
# But as expected, this will raise an error, we can't query on jsonfield like this
# So to create that we need to extend inbuilt filter class
from django.contrib.admin import SimpleListFilter
class JSONFieldFilter(SimpleListFilter):
"""
"""
def __init__(self, *args, **kwargs):
super(JSONFieldFilter, self).__init__(*args, **kwargs)
assert hasattr(self, 'title'), (
'Class {} missing "title" attribute'.format(self.__class__.__name__)
)
assert hasattr(self, 'parameter_name'), (
'Class {} missing "parameter_name" attribute'.format(self.__class__.__name__)
)
assert hasattr(self, 'json_field_name'), (
'Class {} missing "json_field_name" attribute'.format(self.__class__.__name__)
)
assert hasattr(self, 'json_field_property_name'), (
'Class {} missing "json_field_property_name" attribute'.format(self.__class__.__name__)
)
def lookups(self, request, model_admin):
"""
# Improvemnt needed: if the size of jsonfield is large and there are lakhs of row
"""
if '__' in self.json_field_property_name: # NOTE: this will cover only one nested level
keys = self.json_field_property_name.split('__')
field_value_set = set(
data[keys[0]][keys[1]] for data in model_admin.model.objects.values_list(self.json_field_name, flat=True)
)
else:
field_value_set = set(
data[self.json_field_property_name] for data in model_admin.model.objects.values_list(self.json_field_name, flat=True)
)
return [(v, v) for v in field_value_set]
def queryset(self, request, queryset):
if self.value():
json_field_query = {"{}__{}".format(self.json_field_name, self.json_field_property_name): self.value()}
return queryset.filter(**json_field_query)
else:
return queryset
# Now Extend this class to create custom admin filter for JSON field properties.
# admin.py
class AgeFilter(JSONFieldFilter):
"""
"""
title = 'Age' # for admin sidebar (above the filter options)
parameter_name = 'jsonage' # Parameter for the filter that will be used in the URL query
json_field_name = 'jsonfield'
json_field_property_name = 'age' # property/field in json data
class CountryFilter(JSONFieldFilter):
"""
"""
title = 'Country' # for admin sidebar (above the filter options)
parameter_name = 'jsoncountry' # Parameter for the filter that will be used in the URL query
json_field_name = 'jsonfield'
json_field_property_name = 'address__country' # property/field in json data
# and then in model admin class
class MyModelAdmin(admin.ModelAdmin):
list_filter = [AgeFilter, CountryFilter]
@LucianoAlmeida
Copy link

That was really helpful, thanks!

@ivardu
Copy link

ivardu commented Sep 5, 2021

Great job Man.. !!

@tashirka1
Copy link
Author

That was really helpful, thanks!

you're welcome

@tashirka1
Copy link
Author

Great job Man.. !!

you're welcome

@drn8
Copy link

drn8 commented Dec 19, 2022

To overcome the limitation of line 47, I removed the if/else statement (l.47- 55) and replaced it with the following code:

field_value_set = set(
    model_admin.model.objects.values_list(
        f"{self.json_field_name}__{self.json_field_property_name}",
        flat=True,
    )
)

If you want to add a control to filter only objects that have data:

has_data = {
    f"{self.json_field_name}__isnull": False
}

field_value_set = set(
    model_admin.model.objects.filter(**has_data).values_list(
        f"{self.json_field_name}__{self.json_field_property_name}",
        flat=True,
    )
)

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