21
21
from timed .permissions import IsAuthenticated , IsInternal , IsSuperUser
22
22
from timed .projects .models import Customer , Project , Task
23
23
from timed .reports import serializers
24
+ from timed .reports .models import ReportStatistic
24
25
from timed .tracking .filters import ReportFilterSet
25
26
from timed .tracking .models import Report
26
27
from timed .tracking .views import ReportViewSet
35
36
from timed .employment .models import User
36
37
37
38
38
- class BaseStatisticQuerysetMixin :
39
+ class BaseStatisticQuerysetMixin ( AggregateQuerysetMixin ) :
39
40
"""Base statistics queryset mixin.
40
41
41
42
Build and filter the statistics queryset according to the following
@@ -51,7 +52,7 @@ class BaseStatisticQuerysetMixin:
51
52
52
53
For this to work, each viewset defines two properties:
53
54
54
- * The `qs_fields` define which fields are to be selected
55
+ * The `select_fields` is a list of fields to select (and implicitly GROUP BY)
55
56
* The `pk_field` is an expression that will be used as a primary key in the
56
57
REST sense (not really related to the database primary key, but serves as
57
58
a row identifier)
@@ -61,30 +62,29 @@ class BaseStatisticQuerysetMixin:
61
62
"""
62
63
63
64
def get_queryset (self ):
64
- return (
65
- Report . objects . all ()
66
- . select_related ( "user" , "task" , "task__project" , "task__project__customer" )
67
- . annotate ( year = ExtractYear ( "date" ))
68
- . annotate ( month = ExtractYear ( "date" ) * 100 + ExtractMonth ( "date" ))
65
+ # TODO we're doing select_related() here, which makes a JOIN inside
66
+ # the VIEW superfluous. Refactor this to drop it in the VIEW and join
67
+ # normally here - should be slightly faster. But "first make it correct"
68
+ return ReportStatistic . objects . all (). select_related (
69
+ "user" , "task" , "project" , "customer"
69
70
)
70
71
71
72
def filter_queryset (self , queryset ):
72
73
queryset = super ().filter_queryset (queryset )
73
- if isinstance (self .qs_fields , dict ):
74
- # qs fields need to be aliased
75
- queryset = queryset .annotate (** self .qs_fields )
76
74
77
- queryset = queryset .values (* list (self .qs_fields ))
75
+ # need to name it `total_duration` as `duration` is already
76
+ # taken on the `Report` model
77
+ queryset = queryset .values (* self .select_fields )
78
78
queryset = queryset .annotate (duration = Sum ("duration" ))
79
79
queryset = queryset .annotate (pk = F (self .pk_field ))
80
- return queryset
80
+ return queryset # noqa: RET504
81
81
82
82
83
83
class YearStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
84
84
"""Year statistics calculates total reported time per year."""
85
85
86
86
serializer_class = serializers .YearStatisticSerializer
87
- filterset_class = ReportFilterSet
87
+ filterset_class = filters . ReportStatisticFilterset
88
88
ordering_fields = (
89
89
"year" ,
90
90
"duration" ,
@@ -97,15 +97,15 @@ class YearStatisticViewSet(BaseStatisticQuerysetMixin, ReadOnlyModelViewSet):
97
97
),
98
98
)
99
99
100
- qs_fields = ("year" , "duration" )
101
100
pk_field = "year"
101
+ select_fields = ("year" ,)
102
102
103
103
104
104
class MonthStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
105
105
"""Month statistics calculates total reported time per month."""
106
106
107
107
serializer_class = serializers .MonthStatisticSerializer
108
- filterset_class = ReportFilterSet
108
+ filterset_class = filters . ReportStatisticFilterset
109
109
ordering_fields = (
110
110
"year" ,
111
111
"month" ,
@@ -122,20 +122,18 @@ class MonthStatisticViewSet(BaseStatisticQuerysetMixin, ReadOnlyModelViewSet):
122
122
),
123
123
)
124
124
125
- qs_fields = ("year" , "month" , "duration" )
126
125
pk_field = "month"
126
+ select_fields = ("year" , "month" )
127
127
128
128
129
129
class CustomerStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
130
130
"""Customer statistics calculates total reported time per customer."""
131
131
132
132
serializer_class = serializers .CustomerStatisticSerializer
133
- filterset_class = ReportFilterSet
133
+ filterset_class = filters . ReportStatisticFilterset
134
134
ordering_fields = (
135
- "task__project__customer__name " ,
135
+ "customer__name " ,
136
136
"duration" ,
137
- "estimated_time" ,
138
- "remaining_effort" ,
139
137
)
140
138
141
139
ordering = ("name" ,)
@@ -145,22 +143,17 @@ class CustomerStatisticViewSet(BaseStatisticQuerysetMixin, ReadOnlyModelViewSet)
145
143
(IsInternal | IsSuperUser ) & IsAuthenticated
146
144
),
147
145
)
148
- qs_fields = { # noqa: RUF012
149
- "year" : F ("year" ),
150
- "month" : F ("month" ),
151
- "name" : F ("task__project__customer__name" ),
152
- "customer_id" : F ("task__project__customer_id" ),
153
- }
154
146
pk_field = "customer_id"
147
+ select_fields = ("customer__name" ,)
155
148
156
149
157
- class ProjectStatisticViewSet (AggregateQuerysetMixin , ReadOnlyModelViewSet ):
150
+ class ProjectStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
158
151
"""Project statistics calculates total reported time per project."""
159
152
160
153
serializer_class = serializers .ProjectStatisticSerializer
161
- filterset_class = ReportFilterSet
154
+ filterset_class = filters . ReportStatisticFilterset
162
155
ordering_fields = (
163
- "task__project__name " ,
156
+ "project__name " ,
164
157
"duration" ,
165
158
"estimated_time" ,
166
159
"remaining_effort" ,
@@ -173,20 +166,20 @@ class ProjectStatisticViewSet(AggregateQuerysetMixin, ReadOnlyModelViewSet):
173
166
),
174
167
)
175
168
176
- qs_fields = { # noqa: RUF012
177
- "year" : F ("year" ),
178
- "month" : F ("month" ),
179
- "name" : F ("task__project__name" ),
180
- "project_id" : F ("task__project_id" ),
181
- }
182
169
pk_field = "project_id"
170
+ select_fields = (
171
+ "customer__name" ,
172
+ "project__name" ,
173
+ "task__estimated_time" ,
174
+ "task__remaining_effort" ,
175
+ )
183
176
184
177
185
178
class TaskStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
186
179
"""Task statistics calculates total reported time per task."""
187
180
188
181
serializer_class = serializers .TaskStatisticSerializer
189
- filterset_class = ReportFilterSet
182
+ filterset_class = filters . ReportStatisticFilterset
190
183
ordering_fields = (
191
184
"task__name" ,
192
185
"duration" ,
@@ -195,25 +188,23 @@ class TaskStatisticViewSet(BaseStatisticQuerysetMixin, ReadOnlyModelViewSet):
195
188
)
196
189
ordering = ("task__name" ,)
197
190
permission_classes = (
198
- (
199
- # internal employees or super users may read all customer statistics
200
- (IsInternal | IsSuperUser ) & IsAuthenticated
201
- ),
191
+ # internal employees or super users may read all customer statistics
192
+ ((IsInternal | IsSuperUser ) & IsAuthenticated ),
202
193
)
203
194
204
- qs_fields = { # noqa: RUF012
205
- "year" : F ("year" ),
206
- "month" : F ("month" ),
207
- "name" : F ("task__name" ),
208
- }
209
195
pk_field = "task_id"
196
+ select_fields = (
197
+ "task__name" ,
198
+ "task__estimated_time" ,
199
+ "task__remaining_effort" ,
200
+ )
210
201
211
202
212
203
class UserStatisticViewSet (BaseStatisticQuerysetMixin , ReadOnlyModelViewSet ):
213
204
"""User calculates total reported time per user."""
214
205
215
206
serializer_class = serializers .UserStatisticSerializer
216
- filterset_class = ReportFilterSet
207
+ filterset_class = filters . ReportStatisticFilterset
217
208
ordering_fields = (
218
209
"user__username" ,
219
210
"duration" ,
@@ -227,6 +218,7 @@ class UserStatisticViewSet(BaseStatisticQuerysetMixin, ReadOnlyModelViewSet):
227
218
)
228
219
229
220
pk_field = "user"
221
+ select_fields = ("user__username" ,)
230
222
231
223
232
224
class WorkReportViewSet (GenericViewSet ):
@@ -236,7 +228,7 @@ class WorkReportViewSet(GenericViewSet):
236
228
in several projects work reports will be returned as zip.
237
229
"""
238
230
239
- filterset_class = ReportFilterSet
231
+ filterset_class = filters . ReportStatisticFilterset
240
232
ordering = ReportViewSet .ordering
241
233
ordering_fields = ReportViewSet .ordering_fields
242
234
0 commit comments