Skip to content

Commit 813e997

Browse files
2 parents 80fd0f7 + 8cc3c82 commit 813e997

File tree

16 files changed

+233
-90
lines changed

16 files changed

+233
-90
lines changed

application/pay_parking/import_export/admin.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
from .import_data import import_data
44
from .export_data import export_data
55
from django.http import HttpResponse
6-
from rest_framework import status
7-
from rest_framework.response import Response
86
import json
97
from .forms import ImportForm
108
from django.shortcuts import redirect
119
from django.contrib import messages
10+
from django.http import HttpResponseBadRequest
1211

1312

1413
class AdminSite(admin.AdminSite):
@@ -33,7 +32,7 @@ def import_view(self, request):
3332
except Exception as e:
3433
messages.error(request, "Ошибка при чтении файла")
3534
return redirect('admin:index')
36-
return Response(status=status.HTTP_400_BAD_REQUEST)
35+
return HttpResponseBadRequest()
3736

3837
def export_view(self, request):
3938
if request.method == 'GET':
@@ -42,7 +41,7 @@ def export_view(self, request):
4241
response = HttpResponse(content, content_type='application/json')
4342
response['Content-Disposition'] = 'attachment; filename="export_data.json"'
4443
return response
45-
return Response(status=status.HTTP_400_BAD_REQUEST)
44+
return HttpResponseBadRequest()
4645

4746

4847
admin.site = AdminSite('')

application/pay_parking/parking/admin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
MaxTotalLotsFilter, MinTotalLotsFilter,
88
MinPricePerHourFilter, MaxPricePerHourFilter
99
)
10-
from .forms import ParkingFilterForm
11-
from pay_parking.filters import FakeFilterWithForm
10+
from .forms import ParkingFilterForm, ParkingStatisticsForm
1211
from pay_parking.change_list import CustomChangeList
1312
from django.urls import reverse
1413
from django.utils.safestring import mark_safe
@@ -28,7 +27,7 @@ class ParkingAdmin(CustomModelAdmin):
2827
)
2928
search_fields = ('address',)
3029
list_filter = (
31-
FakeFilterWithForm, ParkingZoneFilter, AddressFilter,
30+
ParkingZoneFilter, AddressFilter,
3231
MinLatitudeFilter, MaxLatitudeFilter,
3332
MinLongitudeFilter, MaxLongitudeFilter,
3433
MinAvailableLotsFilter, MaxAvailableLotsFilter,
@@ -60,5 +59,7 @@ def payments_link(self, parking):
6059
def available_lots(self, parking):
6160
return parking.available_lots
6261

62+
statistics_form_class = ParkingStatisticsForm
63+
6364

6465
admin.site.register(Parking, ParkingAdmin)

application/pay_parking/parking/forms.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django import forms
2-
from pay_parking.forms import FormWithFormsets
2+
from pay_parking.forms import FormWithFormsets, StatisticsForm
33
from django.core.validators import MinValueValidator, MaxValueValidator
44
from .models import Parking
55

@@ -103,4 +103,19 @@ class Meta:
103103
('Цена за час', {
104104
'fields': ('min_price_per_hour', 'max_price_per_hour')
105105
}),
106-
)
106+
)
107+
108+
109+
class ParkingStatisticsForm(StatisticsForm):
110+
y_choices = [
111+
("total_lots", "Всего мест"),
112+
("latitude", "Широта"),
113+
("longitude", "Долгота"),
114+
("price_per_hour", "Цена за час"),
115+
("available_lots", "Свободные места"),
116+
]
117+
x_choices = y_choices + [
118+
("parking_zone", "Зона парковки"),
119+
]
120+
x_attribute = forms.ChoiceField(choices=x_choices, label='Атрибут X', initial="parking_zone")
121+
y_attribute = forms.ChoiceField(choices=y_choices, label='Атрибут Y', initial="price_per_hour")

application/pay_parking/pay_parking/admin.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from django.template.response import SimpleTemplateResponse
66
from django.contrib.admin.options import IncorrectLookupParameters
77
from django.http import HttpResponseRedirect
8-
8+
from django.db import models
9+
from .forms import StatisticsForm
10+
import plotly.express as px
11+
from django.http import HttpResponseBadRequest
912

1013
class UserSite(admin.AdminSite):
1114
enable_nav_sidebar = False
@@ -20,19 +23,30 @@ def has_permission(self, request):
2023
class CustomModelAdmin(admin.ModelAdmin):
2124
show_facets = admin.ShowFacets.NEVER
2225
list_per_page = 10
26+
statistics_form_class = StatisticsForm
27+
has_customisable_statistics = True
2328

2429
def get_urls(self):
25-
info = self.opts.app_label, self.opts.model_name
26-
new_urls = [
27-
path(
28-
'customisable_statistics/',
29-
self.admin_site.admin_view(self.customisable_statistics),
30-
name="%s_%s_customisable_statistics" % info
31-
),
32-
]
30+
new_urls= []
31+
if self.has_customisable_statistics:
32+
info = self.opts.app_label, self.opts.model_name
33+
new_urls = [
34+
path(
35+
'customisable_statistics/',
36+
self.admin_site.admin_view(self.customisable_statistics),
37+
name="%s_%s_customisable_statistics" % info
38+
),
39+
]
3340
return new_urls + super().get_urls()
41+
42+
def changelist_view(self, request, extra_context=None):
43+
extra_context = extra_context or {}
44+
extra_context['has_customisable_statistics'] = self.has_customisable_statistics
45+
return super().changelist_view(request, extra_context)
3446

3547
def customisable_statistics(self, request):
48+
if not self.has_customisable_statistics:
49+
return HttpResponseBadRequest('This model has no customisable statistics')
3650
try:
3751
cl = self.get_changelist_instance(request)
3852
except IncorrectLookupParameters:
@@ -44,11 +58,37 @@ def customisable_statistics(self, request):
4458
},
4559
)
4660
return HttpResponseRedirect(request.path + "?" + ERROR_FLAG + "=1")
47-
queryset = cl.queryset # уже отфильтрованный
48-
61+
queryset = cl.queryset
62+
chart = None
63+
form = self.statistics_form_class(request.GET or None)
64+
if form.is_valid():
65+
x_attribute = form.cleaned_data["x_attribute"]
66+
y_attribute = form.cleaned_data["y_attribute"]
67+
queryset = queryset.values(x_attribute).annotate(
68+
**{y_attribute: models.Avg(y_attribute)}
69+
).order_by(x_attribute)
70+
for item in queryset:
71+
print(type(item[x_attribute]))
72+
x_list = [item[x_attribute] for item in queryset]
73+
y_list = [item[y_attribute] for item in queryset]
74+
dict_choices = dict(form.fields['x_attribute'].choices)
75+
fig = px.line(
76+
x=x_list, y=y_list,
77+
labels={
78+
"x": dict_choices[x_attribute],
79+
"y": dict_choices[y_attribute]
80+
},
81+
title=f'Среднее значение "{dict_choices[y_attribute]}" от "{dict_choices[x_attribute]}"'
82+
)
83+
fig.update_layout(title_x=0.5)
84+
85+
chart = fig.to_html()
86+
4987
context = dict(
5088
self.admin_site.each_context(request),
5189
cl=cl,
52-
title='Кастомизируемая статистика'
90+
title='Кастомизируемая статистика',
91+
form=form,
92+
chart=chart
5393
)
5494
return render(request, "admin/customisable_statistics.html", context)

application/pay_parking/pay_parking/change_list.py

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
from django.contrib.admin.views.main import ChangeList
2-
from django.core.exceptions import (
3-
ImproperlyConfigured,
4-
SuspiciousOperation,
5-
)
6-
from django.contrib.admin.utils import (
7-
build_q_object_from_lookup_parameters,
8-
)
9-
from django.contrib.admin.options import (
10-
IncorrectLookupParameters,
11-
)
2+
123

134
class CustomChangeList(ChangeList):
145
filter_form_class = None
@@ -35,24 +26,24 @@ def get_queryset(self, request):
3526
if new_qs is not None:
3627
qs = new_qs
3728

38-
try:
39-
# Finally, we apply the remaining lookup parameters from the query
40-
# string (i.e. those that haven't already been processed by the
41-
# filters).
42-
q_object = build_q_object_from_lookup_parameters(
43-
remaining_lookup_params)
44-
qs = qs.filter(q_object)
45-
except (SuspiciousOperation, ImproperlyConfigured):
46-
# Allow certain types of errors to be re-raised as-is so that the
47-
# caller can treat them in a special way.
48-
raise
49-
except Exception as e:
50-
# Every other error is caught with a naked except, because we don't
51-
# have any other way of validating lookup parameters. They might be
52-
# invalid if the keyword arguments are incorrect, or if the values
53-
# are not in the correct type, so we might get FieldError,
54-
# ValueError, ValidationError, or ?.
55-
raise IncorrectLookupParameters(e)
29+
# try:
30+
# # Finally, we apply the remaining lookup parameters from the query
31+
# # string (i.e. those that haven't already been processed by the
32+
# # filters).
33+
# q_object = build_q_object_from_lookup_parameters(
34+
# remaining_lookup_params)
35+
# qs = qs.filter(q_object)
36+
# except (SuspiciousOperation, ImproperlyConfigured):
37+
# # Allow certain types of errors to be re-raised as-is so that the
38+
# # caller can treat them in a special way.
39+
# raise
40+
# except Exception as e:
41+
# # Every other error is caught with a naked except, because we don't
42+
# # have any other way of validating lookup parameters. They might be
43+
# # invalid if the keyword arguments are incorrect, or if the values
44+
# # are not in the correct type, so we might get FieldError,
45+
# # ValueError, ValidationError, or ?.
46+
# raise IncorrectLookupParameters(e)
5647

5748
if not qs.query.select_related:
5849
qs = self.apply_select_related(qs)

application/pay_parking/pay_parking/filters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def choices(self, changelist):
1616

1717

1818
class FakeFilterWithForm(admin.SimpleListFilter):
19-
template = 'admin/filter_with_form.html'
19+
template = 'admin/empty.html'
2020
parameter_name = ''
2121
title = ''
2222

application/pay_parking/pay_parking/forms.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,18 @@ def fieldsets(self):
2222
yield Fieldset(
2323
title=name,
2424
fields=(self[f] for f in data['fields']),
25-
)
25+
)
26+
27+
28+
class StatisticsForm(forms.Form):
29+
choices = []
30+
x_attribute = forms.ChoiceField(choices=choices)
31+
y_attribute = forms.ChoiceField(choices=choices)
32+
33+
def clean(self):
34+
cleaned_data = super().clean()
35+
x_attribute = cleaned_data.get('x_attribute')
36+
y_attribute = cleaned_data.get('y_attribute')
37+
if x_attribute and y_attribute:
38+
if x_attribute == y_attribute:
39+
raise forms.ValidationError("Выберите разные атрибуты для X и Y.")

application/pay_parking/paying/admin.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib import admin
22
from .models import Payment
3-
from .forms import AdminPaymentForm, PaymentFilterForm
3+
from .forms import AdminPaymentForm, PaymentFilterForm, PaymentStatisticsForm
44
from pay_parking.change_list import CustomChangeList
55
from django.utils.safestring import mark_safe
66
from django.urls import reverse
@@ -16,8 +16,6 @@
1616
MaxPricePerHourFilter, MinPricePerHourFilter,
1717
MinAvailableLotsFilter, MaxAvailableLotsFilter
1818
)
19-
from pay_parking.filters import FakeFilterWithForm
20-
from django.shortcuts import get_object_or_404
2119
from .models import User, Parking
2220
from pay_parking.admin import CustomModelAdmin
2321

@@ -44,7 +42,6 @@ class PaymentAdmin(CustomModelAdmin):
4442
)
4543

4644
list_filter = (
47-
FakeFilterWithForm,
4845
MinCreatedAtFilter, MaxCreatedAtFilter, MinStartFilter, MaxStartFilter,
4946
MinEndFilter, MaxEndFilter, MinPriceFilter, MaxPriceFilter,
5047
MinDurationFilter, MaxDurationFilter,
@@ -104,7 +101,7 @@ def get_changelist(self, request, **kwargs):
104101

105102
def changelist_view(self, request, extra_context=None):
106103
user_id = request.GET.get('user_id')
107-
extra_context = {}
104+
extra_context = extra_context or {}
108105
if user_id:
109106
try:
110107
extra_context['title'] = f'Оплаты {User.objects.get(pk=user_id)}'
@@ -121,4 +118,7 @@ def changelist_view(self, request, extra_context=None):
121118
def add_view(self, request, form_url='', extra_context=None):
122119
return super().add_view(request, form_url, extra_context)
123120

121+
statistics_form_class = PaymentStatisticsForm
122+
123+
124124
admin.site.register(Payment, PaymentAdmin)

application/pay_parking/paying/forms.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django import forms
22
from .models import Payment, Parking, User
33
from django.core.validators import MinValueValidator, MaxValueValidator
4-
from pay_parking.forms import FormWithFormsets
4+
from pay_parking.forms import FormWithFormsets, StatisticsForm
55
from .models import now
66
from datetime import datetime, timedelta
77
from django.core.exceptions import ValidationError
@@ -257,3 +257,23 @@ class Meta:
257257
'fields': ('first_name', 'second_name', 'third_name', 'email', 'is_staff', 'user_id')
258258
}),
259259
)
260+
261+
262+
class PaymentStatisticsForm(StatisticsForm):
263+
y_choices = [
264+
("parking_detail__latitude", "Широта парковки"),
265+
("parking_detail__longitude", "Долгота парковки"),
266+
("parking_detail__total_lots", "Общее число мест парковки"),
267+
("parking_detail__price_per_hour", "Цена за час парковки"),
268+
("duration", "Длительность"),
269+
("price", "Цена"),
270+
]
271+
x_choices = y_choices + [
272+
("parking_detail__parking_zone", "Номер парковочной зоны"),
273+
("user_detail__is_staff", "Сотрудник"),
274+
("created_at", "Создание"),
275+
("start", "Начало"),
276+
("end", "Конец"),
277+
]
278+
x_attribute = forms.ChoiceField(choices=x_choices, label='Атрибут X', initial="parking_detail__parking_zone")
279+
y_attribute = forms.ChoiceField(choices=y_choices, label='Атрибут Y', initial="price")

application/pay_parking/paying/views.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
MaxPricePerHourFilter, MinPricePerHourFilter
1616
)
1717
from django.contrib.auth.decorators import login_required
18-
from pay_parking.filters import FakeFilterWithForm
1918
from pay_parking.admin import UserSite
2019
from .admin import PaymentAdmin
2120
from datetime import timedelta, datetime
@@ -34,7 +33,6 @@ class UserPaymentAdmin(PaymentAdmin):
3433
)
3534
list_display_links = None
3635
list_filter = (
37-
FakeFilterWithForm,
3836
MinCreatedAtFilter, MaxCreatedAtFilter, MinStartFilter, MaxStartFilter,
3937
MinEndFilter, MaxEndFilter, MinPriceFilter, MaxPriceFilter,
4038
MinDurationFilter, MaxDurationFilter,

0 commit comments

Comments
 (0)