11from datetime import timedelta
2-
32from django .conf import settings
43from django .contrib import admin
54from django .db .models import Count
65from django .http import HttpResponse
7-
86from drf_api_logger .utils import database_log_enabled
97
8+ # Ensure the API log model and related features are only used if enabled in settings
109if database_log_enabled ():
1110 from drf_api_logger .models import APILogsModel
1211 from django .utils .translation import gettext_lazy as _
1312 import csv
1413
15-
1614 class ExportCsvMixin :
15+ """
16+ Mixin class to enable exporting selected queryset to CSV in Django admin.
17+ """
18+
1719 def export_as_csv (self , request , queryset ):
20+ """
21+ Export selected objects as a CSV file.
22+ """
1823 meta = self .model ._meta
1924 field_names = [field .name for field in meta .fields ]
2025
2126 response = HttpResponse (content_type = 'text/csv' )
22- response ['Content-Disposition' ] = 'attachment; filename={}.csv' . format ( meta )
27+ response ['Content-Disposition' ] = f 'attachment; filename={ meta } .csv'
2328 writer = csv .writer (response )
2429
30+ # Write headers
2531 writer .writerow (field_names )
32+ # Write rows
2633 for obj in queryset :
2734 writer .writerow ([getattr (obj , field ) for field in field_names ])
2835
2936 return response
3037
3138 export_as_csv .short_description = "Export Selected"
3239
40+
3341 class SlowAPIsFilter (admin .SimpleListFilter ):
34- title = _ ('API Performance' )
42+ """
43+ Custom filter for Django admin to categorize API logs as 'slow' or 'fast'
44+ based on execution time threshold.
45+ """
3546
36- # Parameter for the filter that will be used in the URL query.
47+ title = _ ( 'API Performance' )
3748 parameter_name = 'api_performance'
3849
3950 def __init__ (self , request , params , model , model_admin ):
4051 super ().__init__ (request , params , model , model_admin )
52+ # Load and convert the slow API threshold setting from milliseconds to seconds
4153 if hasattr (settings , 'DRF_API_LOGGER_SLOW_API_ABOVE' ):
42- if isinstance (settings .DRF_API_LOGGER_SLOW_API_ABOVE , int ): # Making sure for integer value.
43- self ._DRF_API_LOGGER_SLOW_API_ABOVE = settings .DRF_API_LOGGER_SLOW_API_ABOVE / 1000 # Converting to seconds.
54+ if isinstance (settings .DRF_API_LOGGER_SLOW_API_ABOVE , int ):
55+ self ._DRF_API_LOGGER_SLOW_API_ABOVE = settings .DRF_API_LOGGER_SLOW_API_ABOVE / 1000
4456
4557 def lookups (self , request , model_admin ):
4658 """
47- Returns a list of tuples. The first element in each
48- tuple is the coded value for the option that will
49- appear in the URL query. The second element is the
50- human-readable name for the option that will appear
51- in the right sidebar.
59+ Returns lookup options for the filter in the admin sidebar.
5260 """
5361 slow = 'Slow'
5462 fast = 'Fast'
5563 if hasattr (settings , 'DRF_API_LOGGER_SLOW_API_ABOVE' ):
56- slow += ', >={}ms' .format (settings .DRF_API_LOGGER_SLOW_API_ABOVE )
57- fast += ', <{}ms' .format (settings .DRF_API_LOGGER_SLOW_API_ABOVE )
58-
64+ slow += f', >={ settings .DRF_API_LOGGER_SLOW_API_ABOVE } ms'
65+ fast += f', <{ settings .DRF_API_LOGGER_SLOW_API_ABOVE } ms'
5966 return (
6067 ('slow' , _ (slow )),
6168 ('fast' , _ (fast )),
6269 )
6370
6471 def queryset (self , request , queryset ):
6572 """
66- Returns the filtered queryset based on the value
67- provided in the query string and retrievable via
68- `self.value()`.
73+ Returns filtered queryset depending on whether 'slow' or 'fast'
74+ option is selected in the filter.
6975 """
70- # to decide how to filter the queryset.
7176 if self .value () == 'slow' :
7277 return queryset .filter (execution_time__gte = self ._DRF_API_LOGGER_SLOW_API_ABOVE )
7378 if self .value () == 'fast' :
7479 return queryset .filter (execution_time__lt = self ._DRF_API_LOGGER_SLOW_API_ABOVE )
75-
7680 return queryset
7781
82+
7883 class APILogsAdmin (admin .ModelAdmin , ExportCsvMixin ):
84+ """
85+ Custom admin class for the API logs model with filters, charts, export functionality,
86+ and restricted permissions.
87+ """
7988
8089 actions = ["export_as_csv" ]
8190
8291 def __init__ (self , model , admin_site ):
8392 super ().__init__ (model , admin_site )
93+
8494 self ._DRF_API_LOGGER_TIMEDELTA = 0
95+
96+ # Conditionally add the slow API filter if setting is provided
8597 if hasattr (settings , 'DRF_API_LOGGER_SLOW_API_ABOVE' ):
86- if isinstance (settings .DRF_API_LOGGER_SLOW_API_ABOVE , int ): # Making sure for integer value.
98+ if isinstance (settings .DRF_API_LOGGER_SLOW_API_ABOVE , int ):
8799 self .list_filter += (SlowAPIsFilter ,)
100+
101+ # Time delta used for adjusting timestamp display
88102 if hasattr (settings , 'DRF_API_LOGGER_TIMEDELTA' ):
89- if isinstance (settings .DRF_API_LOGGER_TIMEDELTA , int ): # Making sure for integer value.
103+ if isinstance (settings .DRF_API_LOGGER_TIMEDELTA , int ):
90104 self ._DRF_API_LOGGER_TIMEDELTA = settings .DRF_API_LOGGER_TIMEDELTA
91105
92106 def added_on_time (self , obj ):
107+ """
108+ Returns formatted 'added_on' timestamp adjusted by timedelta setting.
109+ """
93110 return (obj .added_on + timedelta (minutes = self ._DRF_API_LOGGER_TIMEDELTA )).strftime ("%d %b %Y %H:%M:%S" )
94111
95112 added_on_time .admin_order_field = 'added_on'
96113 added_on_time .short_description = 'Added on'
97114
115+ # Admin UI settings
98116 list_per_page = 20
99117 list_display = ('id' , 'api' , 'method' , 'status_code' , 'execution_time' , 'added_on_time' ,)
100118 list_filter = ('added_on' , 'status_code' , 'method' ,)
@@ -105,52 +123,77 @@ def added_on_time(self, obj):
105123 )
106124 exclude = ('added_on' ,)
107125
126+ # Custom admin templates
108127 change_list_template = 'charts_change_list.html'
109128 change_form_template = 'change_form.html'
110129 date_hierarchy = 'added_on'
111130
112131 def changelist_view (self , request , extra_context = None ):
132+ """
133+ Override to inject custom chart data for status codes and analytics into the context.
134+ """
113135 response = super (APILogsAdmin , self ).changelist_view (request , extra_context )
114136 try :
115137 filtered_query_set = response .context_data ["cl" ].queryset
116138 except Exception :
117139 return response
118- analytics_model = filtered_query_set .values ('added_on__date' ).annotate (total = Count ('id' )).order_by ('total' )
140+
141+ # Aggregate logs by date
142+ analytics_model = filtered_query_set .values ('added_on__date' ).annotate (
143+ total = Count ('id' )
144+ ).order_by ('total' )
145+
146+ # Count each unique status code
119147 status_code_count_mode = filtered_query_set .values ('id' ).values ('status_code' ).annotate (
120148 total = Count ('id' )).order_by ('status_code' )
121- status_code_count_keys = list ()
122- status_code_count_values = list ()
123- for item in status_code_count_mode :
124- status_code_count_keys . append ( item . get ( 'status_code' ))
125- status_code_count_values . append ( item . get ( 'total' ))
149+
150+ status_code_count_keys = [ item . get ( 'status_code' ) for item in status_code_count_mode ]
151+ status_code_count_values = [ item . get ( 'total' ) for item in status_code_count_mode ]
152+
153+ # Add chart data to context
126154 extra_context = dict (
127155 analytics = analytics_model ,
128156 status_code_count_keys = status_code_count_keys ,
129157 status_code_count_values = status_code_count_values
130158 )
159+
131160 response .context_data .update (extra_context )
132161 return response
133162
134163 def get_queryset (self , request ):
164+ """
165+ Ensure the queryset uses the correct database as configured in settings.
166+ """
135167 drf_api_logger_default_database = 'default'
136168 if hasattr (settings , 'DRF_API_LOGGER_DEFAULT_DATABASE' ):
137169 drf_api_logger_default_database = settings .DRF_API_LOGGER_DEFAULT_DATABASE
138170 return super (APILogsAdmin , self ).get_queryset (request ).using (drf_api_logger_default_database )
139171
140172 def changeform_view (self , request , object_id = None , form_url = '' , extra_context = None ):
173+ """
174+ If `export` is in the query parameters, return CSV export of a single object.
175+ """
141176 if request .GET .get ('export' , False ):
142177 drf_api_logger_default_database = 'default'
143178 if hasattr (settings , 'DRF_API_LOGGER_DEFAULT_DATABASE' ):
144179 drf_api_logger_default_database = settings .DRF_API_LOGGER_DEFAULT_DATABASE
180+
145181 export_queryset = self .get_queryset (request ).filter (pk = object_id ).using (drf_api_logger_default_database )
146182 return self .export_as_csv (request , export_queryset )
183+
147184 return super (APILogsAdmin , self ).changeform_view (request , object_id , form_url , extra_context )
148185
149186 def has_add_permission (self , request , obj = None ):
187+ """
188+ Prevent adding logs from the admin.
189+ """
150190 return False
151191
152192 def has_change_permission (self , request , obj = None ):
193+ """
194+ Prevent modifying logs from the admin.
195+ """
153196 return False
154197
155-
198+ # Register the model with the custom admin class
156199 admin .site .register (APILogsModel , APILogsAdmin )
0 commit comments