Skip to content

Commit

Permalink
add functionality with queryset modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrDlouhy committed Oct 12, 2023
1 parent 9898d16 commit 1260f68
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 10 deletions.
2 changes: 1 addition & 1 deletion admin_tools_stats/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class DashboardStatsAdmin(admin.ModelAdmin):
"fields": (
"graph_key",
"graph_title",
("model_app_name", "model_name", "date_field_name"),
("model_app_name", "model_name", "date_field_name", "queryset_modifiers"),
("operation_field_name", "distinct"),
("user_field_name", "show_to_users"),
("allowed_type_operation_field_name", "type_operation_field_name"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.3 on 2023-10-12 11:26

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("admin_tools_stats", "0021_auto_20230210_1102"),
]

operations = [
migrations.AddField(
model_name="dashboardstats",
name="queryset_modifiers",
field=models.JSONField(
blank=True,
help_text=(
"Additional queryset modifiers in JSON format:<br>"
"<pre>"
"[<br>"
' {"filter": {"status": "active"}},<br>'
' {"exclude": {"status": "deleted"}}<br>'
' {"my_annotion_function": {}}<br>'
"]"
"</pre>"
"Ensure the format is a valid JSON array of objects."
),
null=True,
verbose_name="Queryset modifiers",
),
),
]
44 changes: 35 additions & 9 deletions admin_tools_stats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ class DashboardStats(models.Model):
"Can contain multiple fields divided by comma.",
),
)
queryset_modifiers = JSONField(
verbose_name=_("Queryset modifiers"),
null=True,
blank=True,
help_text=mark_safe(
"Additional queryset modifiers in JSON format:<br>"
"<pre>"
"[<br>"
' {"filter": {"status": "active"}},<br>'
' {"exclude": {"status": "deleted"}}<br>'
' {"my_annotion_function": {}}<br>'
"]"
"</pre>"
"Ensure the format is a valid JSON array of objects."
),
)
distinct = models.BooleanField(
default=False,
null=False,
Expand Down Expand Up @@ -437,12 +453,24 @@ def allowed_chart_types_choices(self):
def get_model(self):
return apps.get_model(self.model_app_name, self.model_name)

def get_queryset(self):
qs = self.get_model().objects
if self.queryset_modifiers:
for modifier in self.queryset_modifiers:
method_name = list(modifier.keys())[0]
method_args = modifier[method_name]

Check warning on line 461 in admin_tools_stats/models.py

View check run for this annotation

Codecov / codecov/patch

admin_tools_stats/models.py#L460-L461

Added lines #L460 - L461 were not covered by tests
if isinstance(method_args, dict):
qs = getattr(qs, method_name)(**method_args)

Check warning on line 463 in admin_tools_stats/models.py

View check run for this annotation

Codecov / codecov/patch

admin_tools_stats/models.py#L463

Added line #L463 was not covered by tests
else:
qs = getattr(qs, method_name)(*method_args)

Check warning on line 465 in admin_tools_stats/models.py

View check run for this annotation

Codecov / codecov/patch

admin_tools_stats/models.py#L465

Added line #L465 was not covered by tests
return qs

def get_operation_field(self, operation):
query = self.get_model().objects.all().query
query = self.get_queryset().all().query
return query.resolve_ref(operation).field

def get_date_field(self):
query = self.get_model().objects.all().query
query = self.get_queryset().all().query
return query.resolve_ref(self.date_field_name).field

def clean(self, *args, **kwargs):
Expand Down Expand Up @@ -526,7 +554,6 @@ def get_time_series(
interval: Interval,
):
"""Get the stats time series"""
model_name = apps.get_model(self.model_app_name, self.model_name)
kwargs = {}
dynamic_kwargs: List[Optional[Q]] = []
if not user.has_perm("admin_tools_stats.view_dashboardstats") and self.user_field_name:
Expand Down Expand Up @@ -596,7 +623,7 @@ def get_time_series(

# TODO: maybe backport values_list support back to django-qsstats-magic and use it again for the query
time_range = {"%s__range" % self.date_field_name: (time_since, time_until)}
qs = model_name.objects
qs = self.get_queryset()
qs = qs.filter(**time_range)
qs = qs.filter(**kwargs)
if isinstance(self.get_date_field(), DateTimeField):
Expand Down Expand Up @@ -881,9 +908,9 @@ def get_dynamic_criteria_field_name(self):
return self.prefix + self.criteria.dynamic_criteria_field_name
return self.criteria.dynamic_criteria_field_name

def get_dynamic_field(self, model):
def get_dynamic_field(self):
field_name = self.get_dynamic_criteria_field_name()
query = model.objects.all().query
query = self.stats.get_queryset().all().query
return query.resolve_ref(field_name).field

@memoize(60 * 60 * 24 * 7)
Expand All @@ -896,7 +923,6 @@ def _get_dynamic_choices(
operation_field_choice=None,
user=None,
) -> "Optional[OrderedDict[str, Tuple[Union[str, bool, List[str]], str]]]":
model = self.stats.get_model()
field_name = self.get_dynamic_criteria_field_name()
if self.criteria.criteria_dynamic_mapping:
return OrderedDict(self.criteria.criteria_dynamic_mapping)
Expand All @@ -909,7 +935,7 @@ def _get_dynamic_choices(
("False", (False, "Non blank")),
)
)
field = self.get_dynamic_field(model)
field = self.get_dynamic_field()
if field.__class__ == models.BooleanField:
return OrderedDict(
(
Expand Down Expand Up @@ -940,7 +966,7 @@ def _get_dynamic_choices(
)
end_time = time_until
date_filters["%s__lte" % self.stats.date_field_name] = end_time
choices_queryset = model.objects.filter(
choices_queryset = self.stats.get_queryset().filter(
**date_filters,
)
if user and not user.has_perm("admin_tools_stats.view_dashboardstats"):
Expand Down

0 comments on commit 1260f68

Please sign in to comment.