-
-
Save tashirka1/2da79c08357317fbfb23b7675aedd958 to your computer and use it in GitHub Desktop.
Use JSONField properties in Django admin filter Raw
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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] | |
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
you're welcome