0002-statistics-allow-filtering-by-users-OU-49670.patch
src/authentic2/api_views.py | ||
---|---|---|
1125 | 1125 | |
1126 | 1126 |
time_interval = serializers.ChoiceField(choices=TIME_INTERVAL_CHOICES, default='month') |
1127 | 1127 |
service = ServiceOUField(child=serializers.SlugField(max_length=256), required=False) |
1128 |
ou = serializers.SlugField(required=False, allow_blank=False, max_length=256) |
|
1128 |
services_ou = serializers.SlugField(required=False, allow_blank=False, max_length=256) |
|
1129 |
users_ou = serializers.SlugField(required=False, allow_blank=False, max_length=256) |
|
1130 |
ou = serializers.SlugField(required=False, allow_blank=False, max_length=256) # legacy |
|
1129 | 1131 |
start = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d']) |
1130 | 1132 |
end = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d']) |
1131 | 1133 | |
... | ... | |
1171 | 1173 |
for action in self.get_extra_actions(): |
1172 | 1174 |
url = self.reverse_action(action.url_name) |
1173 | 1175 |
filters = common_filters.copy() |
1174 |
if 'ou' in action.filters: |
|
1175 |
filters.append({'id': 'ou', 'label': _('Organizational Unit'), 'options': ous}) |
|
1176 |
if 'services_ou' in action.filters: |
|
1177 |
filters.append( |
|
1178 |
{'id': 'services_ou', 'label': _('Services organizational unit'), 'options': ous} |
|
1179 |
) |
|
1180 |
if 'users_ou' in action.filters: |
|
1181 |
filters.append( |
|
1182 |
{'id': 'users_ou', 'label': _('Users organizational unit'), 'options': ous} |
|
1183 |
) |
|
1176 | 1184 |
if 'service' in action.filters: |
1177 | 1185 |
filters.append({'id': 'service', 'label': _('Service'), 'options': services}) |
1178 | 1186 |
data = { |
... | ... | |
1206 | 1214 |
} |
1207 | 1215 | |
1208 | 1216 |
allowed_filters = getattr(self, self.action).filters |
1209 |
service, ou = data.get('service'), data.get('ou') |
|
1217 |
service = data.get('service') |
|
1218 |
services_ou = data.get('services_ou') or data.get('ou') # legacy 'ou' filter |
|
1219 |
users_ou = data.get('users_ou') |
|
1220 | ||
1210 | 1221 |
if service and 'service' in allowed_filters: |
1211 | 1222 |
service_slug, ou_slug = service |
1212 | 1223 |
kwargs['service'] = get_object_or_404(Service, slug=service_slug, ou__slug=ou_slug) |
1213 |
elif ou and 'ou' in allowed_filters: |
|
1214 |
kwargs['ou'] = get_object_or_404(get_ou_model(), slug=ou) |
|
1224 |
if services_ou and 'services_ou' in allowed_filters: |
|
1225 |
kwargs['services_ou'] = get_object_or_404(get_ou_model(), slug=services_ou) |
|
1226 |
if users_ou and 'users_ou' in allowed_filters: |
|
1227 |
kwargs['users_ou'] = get_object_or_404(get_ou_model(), slug=users_ou) |
|
1215 | 1228 | |
1216 | 1229 |
return Response({ |
1217 | 1230 |
'data': getattr(klass, method)(**kwargs), |
1218 | 1231 |
'err': 0, |
1219 | 1232 |
}) |
1220 | 1233 | |
1221 |
@stat(name=_('Login count by authentication type'), filters=('ou', 'service')) |
|
1234 |
@stat(name=_('Login count by authentication type'), filters=('services_ou', 'users_ou', 'service'))
|
|
1222 | 1235 |
def login(self, request): |
1223 | 1236 |
return self.get_statistics(request, UserLogin, 'get_method_statistics') |
1224 | 1237 | |
... | ... | |
1230 | 1243 |
def service_ou_login(self, request): |
1231 | 1244 |
return self.get_statistics(request, UserLogin, 'get_service_ou_statistics') |
1232 | 1245 | |
1233 |
@stat(name=_('Registration count by type'), filters=('ou', 'service')) |
|
1246 |
@stat(name=_('Registration count by type'), filters=('services_ou', 'users_ou', 'service'))
|
|
1234 | 1247 |
def registration(self, request): |
1235 | 1248 |
return self.get_statistics(request, UserRegistration, 'get_method_statistics') |
1236 | 1249 |
src/authentic2/apps/journal/models.py | ||
---|---|---|
143 | 143 |
values.append(group_by_field) |
144 | 144 | |
145 | 145 |
if which_references: |
146 |
qs = qs.which_references(which_references)
|
|
146 |
qs = qs.filter(which_references)
|
|
147 | 147 | |
148 | 148 |
if group_by_references: |
149 | 149 |
values.append('reference_ids') |
src/authentic2/journal_event_types.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django.contrib.contenttypes.models import ContentType |
18 |
from django.db.models import Q |
|
18 | 19 |
from django.utils.translation import ugettext_lazy as _ |
19 | 20 | |
20 | 21 |
from authentic2.custom_user.models import get_attributes_map |
21 |
from authentic2.apps.journal.models import EventTypeDefinition, n_2_pairing_rev |
|
22 |
from authentic2.apps.journal.models import EventTypeDefinition, n_2_pairing_rev, EventQuerySet
|
|
22 | 23 |
from authentic2.apps.journal.utils import form_to_old_new, Statistics |
23 | 24 |
from authentic2.custom_user.models import User |
24 | 25 | |
... | ... | |
53 | 54 |
super().record(user=user, session=session, service=service, data={'how': how}) |
54 | 55 | |
55 | 56 |
@classmethod |
56 |
def get_method_statistics(cls, group_by_time, service=None, ou=None, start=None, end=None): |
|
57 |
if ou: |
|
58 |
service = Service.objects.filter(ou=ou) |
|
57 |
def get_method_statistics(cls, group_by_time, service=None, services_ou=None, users_ou=None, start=None, end=None): |
|
58 |
which_references = Q() |
|
59 |
if service: |
|
60 |
which_references &= EventQuerySet._which_references_query(service) |
|
61 |
if services_ou: |
|
62 |
which_references &= EventQuerySet._which_references_query(Service.objects.filter(ou=services_ou)) |
|
63 |
if users_ou: |
|
64 |
which_references &= EventQuerySet._which_references_query(User.objects.filter(ou=users_ou)) |
|
59 | 65 | |
60 | 66 |
qs = cls.get_statistics( |
61 |
group_by_time=group_by_time, group_by_field='how', which_references=service, start=start, end=end
|
|
67 |
group_by_time=group_by_time, group_by_field='how', which_references=which_references, start=start, end=end
|
|
62 | 68 |
) |
63 | 69 |
stats = Statistics(qs, time_interval=group_by_time) |
64 | 70 |
tests/test_api.py | ||
---|---|---|
2052 | 2052 |
"default": "month", |
2053 | 2053 |
}, |
2054 | 2054 |
{ |
2055 |
'id': 'ou', |
|
2056 |
'label': 'Organizational Unit', |
|
2055 |
'id': 'services_ou', |
|
2056 |
'label': 'Services organizational unit', |
|
2057 |
'options': [{'id': 'default', 'label': 'Default organizational unit'}], |
|
2058 |
}, |
|
2059 |
{ |
|
2060 |
'id': 'users_ou', |
|
2061 |
'label': 'Users organizational unit', |
|
2057 | 2062 |
'options': [{'id': 'default', 'label': 'Default organizational unit'}], |
2058 | 2063 |
}, |
2059 | 2064 |
{'id': 'service', 'label': 'Service', 'options': []}, |
... | ... | |
2081 | 2086 | |
2082 | 2087 |
service = Service.objects.create(name='Service1', slug='service1', ou=get_default_ou()) |
2083 | 2088 |
service = Service.objects.create(name='Service2', slug='service2', ou=get_default_ou()) |
2084 |
login_stats['filters'][2]['options'].append({'id': 'service1 default', 'label': 'Service1'})
|
|
2085 |
login_stats['filters'][2]['options'].append({'id': 'service2 default', 'label': 'Service2'})
|
|
2089 |
login_stats['filters'][3]['options'].append({'id': 'service1 default', 'label': 'Service1'})
|
|
2090 |
login_stats['filters'][3]['options'].append({'id': 'service2 default', 'label': 'Service2'})
|
|
2086 | 2091 | |
2087 | 2092 |
resp = app.get('/api/statistics/', headers=headers) |
2088 | 2093 |
assert login_stats in resp.json['data'] |
... | ... | |
2093 | 2098 |
'event_type_name,event_name', [('user.login', 'login'), ('user.registration', 'registration')] |
2094 | 2099 |
) |
2095 | 2100 |
def test_api_statistics(app, admin, freezer, event_type_name, event_name): |
2101 |
OU = get_ou_model() |
|
2096 | 2102 |
headers = basic_authorization_header(admin) |
2097 | 2103 | |
2098 | 2104 |
resp = app.get('/api/statistics/login/?time_interval=month', headers=headers) |
2099 | 2105 |
assert resp.json == {"data": {"series": [], "x_labels": []}, "err": 0} |
2100 | 2106 | |
2101 |
user = User.objects.create(username='john.doe', email='john.doe@example.com') |
|
2102 |
portal = Service.objects.create(name='portal', slug='portal', ou=get_default_ou()) |
|
2107 |
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou()) |
|
2108 |
ou = OU.objects.create(name='Second OU', slug='second') |
|
2109 |
portal = Service.objects.create(name='portal', slug='portal', ou=ou) |
|
2103 | 2110 |
agendas = Service.objects.create(name='agendas', slug='agendas', ou=get_default_ou()) |
2104 | 2111 | |
2105 | 2112 |
method = {'how': 'password-on-https'} |
... | ... | |
2110 | 2117 | |
2111 | 2118 |
freezer.move_to('2020-02-03 12:00') |
2112 | 2119 |
event = Event.objects.create(type=event_type, references=[portal], data=method) |
2113 |
event = Event.objects.create(type=event_type, references=[agendas], data=method) |
|
2120 |
event = Event.objects.create(type=event_type, references=[agendas, user], data=method)
|
|
2114 | 2121 | |
2115 | 2122 |
freezer.move_to('2020-03-04 13:00') |
2116 | 2123 |
event = Event.objects.create(type=event_type, references=[agendas], data=method) |
... | ... | |
2131 | 2138 |
data['series'].sort(key=lambda x: x['label']) |
2132 | 2139 |
assert month_data == data |
2133 | 2140 | |
2134 |
resp = app.get('/api/statistics/%s/?time_interval=month&ou=default' % event_name, headers=headers) |
|
2141 |
resp = app.get( |
|
2142 |
'/api/statistics/%s/?time_interval=month&services_ou=default' % event_name, headers=headers |
|
2143 |
) |
|
2135 | 2144 |
data = resp.json['data'] |
2136 | 2145 |
data['series'].sort(key=lambda x: x['label']) |
2137 | 2146 |
assert data == { |
2138 | 2147 |
'x_labels': ['2020-02', '2020-03'], |
2139 |
'series': [{'label': 'FranceConnect', 'data': [None, 1]}, {'label': 'password', 'data': [2, 1]}],
|
|
2148 |
'series': [{'label': 'password', 'data': [1, 1]}],
|
|
2140 | 2149 |
} |
2141 | 2150 | |
2151 |
# legacy way to filter by service OU |
|
2152 |
services_ou_data = data |
|
2153 |
resp = app.get('/api/statistics/%s/?time_interval=month&ou=default' % event_name, headers=headers) |
|
2154 |
data = resp.json['data'] |
|
2155 |
data['series'].sort(key=lambda x: x['label']) |
|
2156 |
assert services_ou_data == data |
|
2157 | ||
2158 |
resp = app.get( |
|
2159 |
'/api/statistics/%s/?time_interval=month&users_ou=default&service=agendas default' % event_name, headers=headers |
|
2160 |
) |
|
2161 |
data = resp.json['data'] |
|
2162 |
assert data == { |
|
2163 |
'x_labels': ['2020-02'], |
|
2164 |
'series': [{'label': 'password', 'data': [1]}], |
|
2165 |
} |
|
2166 | ||
2167 |
resp = app.get('/api/statistics/%s/?time_interval=month&users_ou=default' % event_name, headers=headers) |
|
2168 |
data = resp.json['data'] |
|
2169 |
assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [1]}]} |
|
2170 | ||
2142 | 2171 |
resp = app.get( |
2143 | 2172 |
'/api/statistics/%s/?time_interval=month&service=agendas default' % event_name, headers=headers |
2144 | 2173 |
) |
... | ... | |
2168 | 2197 |
assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]} |
2169 | 2198 | |
2170 | 2199 |
resp = app.get( |
2171 |
'/api/statistics/%s/?time_interval=year&service=portal default' % event_name, headers=headers
|
|
2200 |
'/api/statistics/%s/?time_interval=year&service=portal second' % event_name, headers=headers
|
|
2172 | 2201 |
) |
2173 | 2202 |
data = resp.json['data'] |
2174 | 2203 |
data['series'].sort(key=lambda x: x['label']) |
... | ... | |
2187 | 2216 | |
2188 | 2217 |
resp = app.get('/api/statistics/service_ou_%s/?time_interval=month' % event_name, headers=headers) |
2189 | 2218 |
data = resp.json['data'] |
2219 |
data['series'].sort(key=lambda x: x['label']) |
|
2190 | 2220 |
assert data == { |
2191 | 2221 |
'x_labels': ['2020-02', '2020-03'], |
2192 |
'series': [{'label': 'Default organizational unit', 'data': [2, 2]}], |
|
2222 |
'series': [ |
|
2223 |
{'label': 'Default organizational unit', 'data': [1, 1]}, |
|
2224 |
{'label': 'Second OU', 'data': [1, 1]}, |
|
2225 |
], |
|
2193 | 2226 |
} |
2194 | 2227 | |
2195 | 2228 |
tests/test_journal.py | ||
---|---|---|
450 | 450 | |
451 | 451 |
@pytest.mark.parametrize('event_type_name', ['user.login', 'user.registration']) |
452 | 452 |
def test_statistics(db, event_type_name, freezer): |
453 |
user = User.objects.create(username='john.doe', email='john.doe@example.com') |
|
454 |
user2 = User.objects.create(username='jane.doe', email='jane.doe@example.com') |
|
453 |
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou()) |
|
455 | 454 |
ou = OU.objects.create(name='Second OU') |
455 |
user2 = User.objects.create(username='jane.doe', email='jane.doe@example.com', ou=ou) |
|
456 | 456 | |
457 | 457 |
portal = Service.objects.create(name='portal', slug='portal', ou=ou) |
458 | 458 |
agendas = Service.objects.create(name='agendas', slug='agendas', ou=get_default_ou()) |
... | ... | |
514 | 514 |
], |
515 | 515 |
} |
516 | 516 | |
517 |
stats = event_type_definition.get_method_statistics('month', ou=get_default_ou()) |
|
517 |
stats = event_type_definition.get_method_statistics('month', services_ou=get_default_ou())
|
|
518 | 518 |
assert stats == { |
519 | 519 |
'x_labels': ['2020-03'], |
520 | 520 |
'series': [{'label': 'password', 'data': [2]},], |
521 | 521 |
} |
522 | 522 | |
523 |
stats = event_type_definition.get_method_statistics('month', ou=ou) |
|
523 |
stats = event_type_definition.get_method_statistics('month', services_ou=ou)
|
|
524 | 524 |
stats['series'].sort(key=lambda x: x['label']) |
525 | 525 |
assert stats == { |
526 | 526 |
'x_labels': ['2020-02', '2020-03'], |
527 | 527 |
'series': [{'label': 'FranceConnect', 'data': [2, None]}, {'label': 'password', 'data': [2, 1]}], |
528 | 528 |
} |
529 | 529 | |
530 |
stats = event_type_definition.get_method_statistics('month', users_ou=ou) |
|
531 |
stats['series'].sort(key=lambda x: x['label']) |
|
532 |
assert stats == { |
|
533 |
'x_labels': ['2020-02'], |
|
534 |
'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}], |
|
535 |
} |
|
536 | ||
530 | 537 |
stats = event_type_definition.get_method_statistics('month', service=portal) |
531 | 538 |
stats['series'].sort(key=lambda x: x['label']) |
532 | 539 |
assert stats == { |
... | ... | |
534 | 541 |
'series': [{'label': 'FranceConnect', 'data': [2, None]}, {'label': 'password', 'data': [2, 1]}], |
535 | 542 |
} |
536 | 543 | |
544 |
stats = event_type_definition.get_method_statistics('month', service=agendas, users_ou=get_default_ou()) |
|
545 |
stats['series'].sort(key=lambda x: x['label']) |
|
546 |
assert stats == { |
|
547 |
'x_labels': ['2020-03'], |
|
548 |
'series': [{'label': 'password', 'data': [1]}], |
|
549 |
} |
|
550 | ||
537 | 551 |
stats = event_type_definition.get_method_statistics('year') |
538 | 552 |
stats['series'].sort(key=lambda x: x['label']) |
539 | 553 |
assert stats == { |
540 |
- |