0001-add-a-new-page-for-resource-paginated-logs-14671.patch
passerelle/base/models.py | ||
---|---|---|
16 | 16 |
from django.utils.text import slugify |
17 | 17 |
from django.utils import timezone |
18 | 18 |
from django.utils.translation import ugettext_lazy as _ |
19 |
from django.utils.dateparse import parse_datetime |
|
20 | ||
19 | 21 |
from django.core.files.base import ContentFile |
22 |
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
|
20 | 23 | |
21 | 24 |
from django.contrib.contenttypes.models import ContentType |
22 | 25 |
from django.contrib.contenttypes import fields |
... | ... | |
428 | 431 |
return '%s (on %s <%s>) (for %s)' % (self.codename, self.resource_type, self.resource_pk, self.apiuser) |
429 | 432 | |
430 | 433 | |
434 |
class ResourceLogManager(models.Manager): |
|
435 |
def get_paginated_logs(self, appname, slug, page=None, lookup_dict=None, lookup_or=None, order_by=None, page_size=None): |
|
436 |
if page is None: |
|
437 |
page = 1 |
|
438 |
if order_by is None: |
|
439 |
order_by = '-timestamp' |
|
440 |
if page_size is None: |
|
441 |
page_size = 10 |
|
442 |
qs = None |
|
443 |
if lookup_dict: |
|
444 |
q_list = [] |
|
445 |
# remove forbidden lookup filter keys, transform timestamp |
|
446 |
for key, value in lookup_dict.items(): |
|
447 |
if key in ['appname', 'slug']: |
|
448 |
del lookup_dict[key] |
|
449 |
if key.startswith('timestamp'): |
|
450 |
lookup_dict[key] = parse_datetime(lookup_dict[key]) |
|
451 |
print lookup_dict[key] |
|
452 |
if lookup_dict[key] is None: |
|
453 |
del lookup_dict[key] |
|
454 |
q_list += [Q(**{key: value})] |
|
455 | ||
456 |
if len(q_list): |
|
457 |
lookups = q_list.pop() |
|
458 |
for q_item in q_list: |
|
459 |
if lookup_or is None: |
|
460 |
lookups &= q_item |
|
461 |
else: |
|
462 |
lookups |= q_item |
|
463 |
qs = self.filter(lookups) |
|
464 |
if qs and qs.count() > 0: |
|
465 |
qs = qs.filter(appname=appname, slug=slug) |
|
466 |
else: |
|
467 |
qs = self.none() |
|
468 |
else: |
|
469 |
qs = self.filter(appname=appname, slug=slug) |
|
470 | ||
471 |
qs = qs.order_by(order_by) |
|
472 |
paginator = Paginator(qs, int(page_size)) |
|
473 |
try: |
|
474 |
page_obj = paginator.page(page) |
|
475 |
except PageNotAnInteger: |
|
476 |
page_obj = paginator.page(1) |
|
477 |
except (EmptyPage,): |
|
478 |
page_obj = paginator.page(paginator.num_pages) |
|
479 | ||
480 |
return page_obj |
|
481 | ||
482 | ||
431 | 483 |
class ResourceLog(models.Model): |
432 | 484 |
timestamp = models.DateTimeField(auto_now_add=True) |
433 | 485 |
appname = models.CharField(max_length=128, verbose_name='appname', null=True) |
... | ... | |
437 | 489 |
message = models.TextField(max_length=2048, verbose_name='message') |
438 | 490 |
extra = jsonfield.JSONField(verbose_name='extras', default={}) |
439 | 491 | |
492 |
objects = ResourceLogManager() |
|
493 | ||
440 | 494 |
class Meta: |
441 | 495 |
permissions = ( |
442 | 496 |
('view_resourcelog', 'Can view resource logs'), |
passerelle/base/templatetags/passerelle.py | ||
---|---|---|
3 | 3 |
from django import template |
4 | 4 |
from django.contrib.contenttypes.models import ContentType |
5 | 5 |
from django.contrib.auth import get_permission_codename |
6 |
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
|
7 | 6 | |
8 | 7 |
from passerelle.utils import get_trusted_services |
9 | 8 |
from ..models import AccessRight, ResourceLog |
... | ... | |
23 | 22 |
return context |
24 | 23 | |
25 | 24 | |
26 |
@register.inclusion_tag('passerelle/includes/resource-logs-table.html', takes_context=True) |
|
27 |
def resource_logs_table(context, resource): |
|
28 |
request = context.get('request') |
|
29 |
page = request.GET.get('page', 1) |
|
30 | ||
31 |
connector = resource.get_connector_slug() |
|
32 |
context['connector'] = connector |
|
33 |
context['slug'] = resource.slug |
|
34 |
qs = ResourceLog.objects.filter(appname=connector, slug=resource.slug).order_by('-timestamp') |
|
35 | ||
36 |
paginator = Paginator(qs, 10) |
|
37 |
try: |
|
38 |
logrecords = paginator.page(page) |
|
39 |
except PageNotAnInteger: |
|
40 |
logrecords = paginator.page(1) |
|
41 |
except (EmptyPage,): |
|
42 |
logrecords = paginator.page(paginator.num_pages) |
|
43 | ||
44 |
context['logrecords'] = logrecords |
|
45 |
return context |
|
46 | ||
47 | ||
48 | 25 |
@register.filter |
49 | 26 |
def can_edit(obj, user): |
50 | 27 |
return user.has_perm(get_permission_codename('change', obj._meta), obj=obj) |
passerelle/templates/passerelle/includes/resource-logs-table.html | ||
---|---|---|
1 |
{% load i18n passerelle %} |
|
2 |
{% load tz %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
{% if logrecords %} |
|
6 |
<table class="main"> |
|
7 |
<thead> |
|
8 |
<th>{% trans 'Timestamp' %}</th> |
|
9 |
<th>{% trans 'Source IP' %}</th> |
|
10 |
<th>{% trans 'Message' %}</th> |
|
11 |
</thead> |
|
12 |
<tbody> |
|
13 |
{% for record in logrecords %} |
|
14 |
<tr class="level-{{record.level}}"> |
|
15 |
<td class="timestamp">{{ record.timestamp|localtime }}</td> |
|
16 |
<td>{{ record.sourceip|default:"-" }}</td> |
|
17 |
<td class="message">{{ record.message}}</td> |
|
18 |
</tr> |
|
19 |
{% endfor %} |
|
20 |
</tbody> |
|
21 |
</table> |
|
22 | ||
23 |
{% with page_obj=logrecords %} |
|
24 |
{% include "gadjo/pagination.html" with anchor="#logs" %} |
|
25 |
{% endwith %} |
|
26 | ||
27 |
{% else %} |
|
28 |
<p>{% trans 'No records found' %}</p> |
|
29 |
{% endif %} |
|
30 |
{% endblock %} |
passerelle/templates/passerelle/manage/service_log.html | ||
---|---|---|
1 |
{% extends "passerelle/manage/service_view.html" %} |
|
2 |
{% load i18n passerelle %} |
|
3 |
{% load tz %} |
|
4 | ||
5 |
{% block breadcrumb %} |
|
6 |
{{ block.super }} |
|
7 |
<a href="{{object.get_absolute_url}}">{{ object.get_verbose_name }}</a> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block content %} |
|
11 |
{% if perms.base.view_resourcelog %} |
|
12 |
<div id="logs" class="section"> |
|
13 |
<h3>{% trans "Logs" %}</h3> |
|
14 |
<div> |
|
15 |
{% block logs %} |
|
16 |
{% if page_obj.paginator.count %} |
|
17 |
<table class="main"> |
|
18 |
<thead> |
|
19 |
<th>{% trans 'Timestamp' %}</th> |
|
20 |
<th>{% trans 'Source IP' %}</th> |
|
21 |
<th>{% trans 'Message' %}</th> |
|
22 |
</thead> |
|
23 |
<tbody> |
|
24 |
{% for record in page_obj %} |
|
25 |
<tr class="level-{{record.level}}"> |
|
26 |
<td class="timestamp">{{ record.timestamp|localtime }}</td> |
|
27 |
<td>{{ record.sourceip|default:"-" }}</td> |
|
28 |
<td class="message">{{ record.message}}</td> |
|
29 |
</tr> |
|
30 |
{% endfor %} |
|
31 |
</tbody> |
|
32 |
</table> |
|
33 |
{% include "gadjo/pagination.html" with anchor="#logs" %} |
|
34 |
{% else %} |
|
35 |
<p>{% trans 'No records found' %}</p> |
|
36 |
{% endif %} |
|
37 |
{% endblock %} |
|
38 |
</div> |
|
39 |
</div> |
|
40 |
{% endif %} |
|
41 |
{% endblock %} |
passerelle/templates/passerelle/manage/service_view.html | ||
---|---|---|
87 | 87 | |
88 | 88 |
{% if perms.base.view_resourcelog %} |
89 | 89 |
<div id="logs" class="section"> |
90 |
<h3>{% trans "Logs" %}</h3> |
|
91 |
<div> |
|
92 |
{% block logs %} |
|
93 |
{% resource_logs_table resource=object %} |
|
94 |
{% endblock %} |
|
95 |
</div> |
|
90 |
<h3><a href="{% url 'view-connector-log' object.get_connector_slug object.slug %}">{% trans "Logs" %}</a></h3> |
|
96 | 91 |
</div> |
97 | 92 |
{% endif %} |
98 | 93 |
passerelle/urls.py | ||
---|---|---|
9 | 9 |
from .views import (HomePageView, ManageView, ManageAddView, |
10 | 10 |
GenericCreateConnectorView, GenericDeleteConnectorView, |
11 | 11 |
GenericEditConnectorView, GenericEndpointView, GenericConnectorView, |
12 |
GenericConnectorLogView, |
|
12 | 13 |
login, logout, menu_json) |
13 | 14 |
from .urls_utils import decorated_includes, required, app_enabled, manager_required |
14 | 15 |
from .base.urls import access_urlpatterns |
... | ... | |
78 | 79 |
urlpatterns += [ |
79 | 80 |
url(r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/$', |
80 | 81 |
GenericConnectorView.as_view(), name='view-connector'), |
82 |
url(r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/logwatch$', |
|
83 |
GenericConnectorLogView.as_view(), name='view-connector-log'), |
|
81 | 84 |
url(r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/(?P<endpoint>[\w,-]+)(?:/(?P<rest>.*))?$', |
82 | 85 |
GenericEndpointView.as_view(), name='generic-endpoint') |
83 | 86 |
] |
84 | 87 | |
85 |
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS: |
|
86 |
import debug_toolbar |
|
87 |
urlpatterns = [ |
|
88 |
url(r'^__debug__/', include(debug_toolbar.urls)), |
|
89 |
] + urlpatterns |
|
90 | ||
91 | 88 |
from django.contrib.staticfiles.urls import staticfiles_urlpatterns |
92 | 89 |
urlpatterns += staticfiles_urlpatterns() |
passerelle/views.py | ||
---|---|---|
6 | 6 |
from django.core.exceptions import PermissionDenied |
7 | 7 |
from django.contrib.auth import logout as auth_logout |
8 | 8 |
from django.contrib.auth import views as auth_views |
9 |
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, Http404
|
|
9 |
from django.http import HttpResponse, HttpResponseRedirect, Http404 |
|
10 | 10 |
from django.views.decorators.csrf import csrf_exempt |
11 | 11 |
from django.views.generic import (RedirectView, View, TemplateView, CreateView, |
12 | 12 |
DeleteView, UpdateView, DetailView) |
... | ... | |
18 | 18 |
from django.utils.encoding import force_text |
19 | 19 |
from django.forms.models import modelform_factory |
20 | 20 |
from django.forms.widgets import ClearableFileInput |
21 |
from .base.models import BaseResource, ResourceLog |
|
22 |
from .utils import to_json, is_authorized |
|
23 |
from .forms import GenericConnectorForm |
|
21 | 24 | |
22 | 25 |
if 'mellon' in settings.INSTALLED_APPS: |
23 | 26 |
from mellon.utils import get_idps |
24 | 27 |
else: |
25 | 28 |
get_idps = lambda: [] |
26 | 29 | |
27 |
from passerelle.base.models import BaseResource |
|
28 | ||
29 |
from .utils import to_json, response_for_json, is_authorized |
|
30 |
from .forms import GenericConnectorForm |
|
31 | ||
32 | 30 | |
33 | 31 |
def get_all_apps(): |
34 | 32 |
return [x for x in apps.get_models() if issubclass(x, BaseResource) and \ |
... | ... | |
137 | 135 |
template_names.append('passerelle/manage/service_view.html') |
138 | 136 |
return template_names |
139 | 137 | |
138 |
class GenericConnectorLogView(GenericConnectorMixin, DetailView): |
|
139 |
template_name = 'passerelle/manage/service_log.html' |
|
140 | ||
141 |
def get_context_data(self, object, **kwargs): |
|
142 |
'''All search parameters from GET query dict overridden with POST if present |
|
143 |
''' |
|
144 |
connector = object.get_connector_slug() |
|
145 |
kwargs = {} |
|
146 |
# Mutable version of WueryDict instance |
|
147 |
get = self.request.GET.copy() |
|
148 |
post = self.request.POST.copy() |
|
149 |
for param in ['page', 'lookup_or', 'order_by', 'page_size']: |
|
150 |
try: |
|
151 |
kwargs[param] = get.pop(param)[0] |
|
152 |
if param in post: |
|
153 |
kwargs[param] = post.pop(param)[0] |
|
154 |
except KeyError: |
|
155 |
continue |
|
156 | ||
157 |
lookup_dict = get.dict() |
|
158 |
lookup_dict.update(post.dict()) |
|
159 |
page_obj = ResourceLog.objects.get_paginated_logs(connector, object.slug, lookup_dict=lookup_dict, |
|
160 |
**kwargs) |
|
161 |
return { |
|
162 |
'view': self, |
|
163 |
'object': object, |
|
164 |
'connector': connector, |
|
165 |
'slug': object.slug, |
|
166 |
'page_obj': page_obj |
|
167 |
} |
|
168 | ||
140 | 169 | |
141 | 170 |
class GenericCreateConnectorView(GenericConnectorMixin, CreateView): |
142 | 171 |
template_name = 'passerelle/manage/service_form.html' |
143 |
- |