0001-manager-add-full-page-with-logs-and-basic-filtering-.patch
passerelle/templates/passerelle/includes/resource-logs-table.html | ||
---|---|---|
1 | 1 |
{% load i18n passerelle %} |
2 | 2 |
{% load tz %} |
3 | 3 | |
4 |
{% block content %} |
|
5 | 4 |
{% if logrecords %} |
6 | 5 |
<table class="main"> |
7 | 6 |
<thead> |
... | ... | |
27 | 26 |
{% else %} |
28 | 27 |
<p>{% trans 'No records found' %}</p> |
29 | 28 |
{% endif %} |
30 |
{% endblock %} |
passerelle/templates/passerelle/manage/service_logs.html | ||
---|---|---|
1 |
{% extends "passerelle/manage.html" %} |
|
2 |
{% load i18n passerelle %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{{object.get_absolute_url}}">{{ object.title }}</a> |
|
7 |
<a href="#">{% trans "Logs" %}</a> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block appbar %} |
|
11 |
<h2>{% trans "Logs" %}</h2> |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block content %} |
|
15 | ||
16 |
<div id="logs"> |
|
17 | ||
18 |
<form> |
|
19 |
<p><input name="q" type="search" value="{{query}}"> <button>{% trans 'Search' %}</button> |
|
20 |
<span class="help_text">{% trans "(supports text search in messages, or dates)" %}</span> |
|
21 |
</p> |
|
22 |
</form> |
|
23 | ||
24 |
{% include "passerelle/includes/resource-logs-table.html" with logrecords=page_obj %} |
|
25 |
</div> |
|
26 | ||
27 |
{% endblock %} |
passerelle/templates/passerelle/manage/service_view.html | ||
---|---|---|
90 | 90 | |
91 | 91 |
{% if perms.base.view_resourcelog %} |
92 | 92 |
<div id="logs" class="section"> |
93 |
<h3>{% trans "Logs" %}</h3> |
|
93 |
<h3>{% trans "Logs" %} |
|
94 |
<a href="{% url 'view-logs-connector' connector=object.get_connector_slug slug=object.slug %}">({% trans "full page & filter" %})</a> |
|
95 |
</h3> |
|
94 | 96 |
<div> |
95 | 97 |
{% block logs %} |
96 | 98 |
{% resource_logs_table resource=object %} |
passerelle/urls.py | ||
---|---|---|
9 | 9 |
from .views import (HomePageView, ManageView, ManageAddView, |
10 | 10 |
GenericCreateConnectorView, GenericDeleteConnectorView, |
11 | 11 |
GenericEditConnectorView, GenericEndpointView, GenericConnectorView, |
12 |
login, logout, menu_json) |
|
12 |
GenericViewLogsConnectorView, login, logout, menu_json)
|
|
13 | 13 |
from .urls_utils import decorated_includes, required, app_enabled, manager_required |
14 | 14 |
from .base.urls import access_urlpatterns |
15 | 15 |
from .plugins import register_apps_urls |
... | ... | |
72 | 72 |
GenericDeleteConnectorView.as_view(), name='delete-connector'), |
73 | 73 |
url(r'^(?P<slug>[\w,-]+)/edit$', |
74 | 74 |
GenericEditConnectorView.as_view(), name='edit-connector'), |
75 |
url(r'^(?P<slug>[\w,-]+)/logs$', |
|
76 |
GenericViewLogsConnectorView.as_view(), name='view-logs-connector'), |
|
75 | 77 |
]))) |
76 | 78 |
] |
77 | 79 |
passerelle/views.py | ||
---|---|---|
1 |
import datetime |
|
1 | 2 |
import inspect |
2 | 3 |
import json |
3 | 4 | |
... | ... | |
9 | 10 |
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, Http404 |
10 | 11 |
from django.views.decorators.csrf import csrf_exempt |
11 | 12 |
from django.views.generic import (RedirectView, View, TemplateView, CreateView, |
12 |
DeleteView, UpdateView, DetailView) |
|
13 |
DeleteView, UpdateView, DetailView, ListView)
|
|
13 | 14 |
from django.views.generic.detail import SingleObjectMixin |
14 | 15 |
from django.conf import settings |
15 | 16 |
from django.shortcuts import resolve_url |
16 | 17 |
from django.core.urlresolvers import reverse |
18 |
from django.utils.timezone import make_aware |
|
17 | 19 |
from django.utils.translation import ugettext_lazy as _ |
18 | 20 |
from django.utils.encoding import force_text |
19 | 21 |
from django.forms.models import modelform_factory |
20 | 22 |
from django.forms.widgets import ClearableFileInput |
21 | 23 | |
24 |
from dateutil import parser as date_parser |
|
25 | ||
22 | 26 |
if 'mellon' in settings.INSTALLED_APPS: |
23 | 27 |
from mellon.utils import get_idps |
24 | 28 |
else: |
25 | 29 |
get_idps = lambda: [] |
26 | 30 | |
27 |
from passerelle.base.models import BaseResource |
|
31 |
from passerelle.base.models import BaseResource, ResourceLog
|
|
28 | 32 | |
29 | 33 |
from .utils import to_json, response_for_json, is_authorized |
30 | 34 |
from .forms import GenericConnectorForm |
... | ... | |
163 | 167 |
return reverse('manage-home') |
164 | 168 | |
165 | 169 | |
170 |
class GenericViewLogsConnectorView(GenericConnectorMixin, ListView): |
|
171 |
template_name = 'passerelle/manage/service_logs.html' |
|
172 |
paginate_by = 25 |
|
173 | ||
174 |
def get_context_data(self, **kwargs): |
|
175 |
context = super(GenericViewLogsConnectorView, self).get_context_data(**kwargs) |
|
176 |
context['object'] = self.get_object() |
|
177 |
context['query'] = self.request.GET.get('q') or '' |
|
178 |
return context |
|
179 | ||
180 |
def get_object(self): |
|
181 |
return self.model.objects.get(slug=self.kwargs['slug']) |
|
182 | ||
183 |
def get_queryset(self): |
|
184 |
qs = ResourceLog.objects.filter( |
|
185 |
appname=self.kwargs['connector'], |
|
186 |
slug=self.kwargs['slug']).order_by('-timestamp') |
|
187 |
query = self.request.GET.get('q') |
|
188 |
if query: |
|
189 |
try: |
|
190 |
date = date_parser.parse(query, dayfirst=True) |
|
191 |
except: |
|
192 |
qs = qs.filter(message__icontains=query) |
|
193 |
else: |
|
194 |
date = make_aware(date) |
|
195 |
qs = qs.filter(timestamp__gte=date, timestamp__lt=date + datetime.timedelta(days=1)) |
|
196 |
return qs |
|
197 | ||
198 | ||
166 | 199 |
class WrongParameter(Exception): |
167 | 200 |
http_status = 400 |
168 | 201 |
log_error = False |
tests/test_manager.py | ||
---|---|---|
1 |
import datetime |
|
1 | 2 |
import re |
3 |
from StringIO import StringIO |
|
2 | 4 | |
3 | 5 |
from django.contrib.auth.models import User |
6 |
from django.contrib.contenttypes.models import ContentType |
|
7 |
from django.core.files import File |
|
4 | 8 |
import pytest |
5 | 9 | |
6 |
from passerelle.base.models import ApiUser |
|
10 |
from passerelle.base.models import ApiUser, AccessRight |
|
11 |
from passerelle.apps.csvdatasource.models import CsvDataSource, Query |
|
7 | 12 | |
8 | 13 |
pytestmark = pytest.mark.django_db |
9 | 14 | |
... | ... | |
109 | 114 |
resp = app.get('/manage/menu.json?callback=FooBar') |
110 | 115 |
assert resp.headers['content-type'] == 'application/javascript' |
111 | 116 |
assert resp.content.startswith('FooBar([{"') |
117 | ||
118 |
def test_logs(app, admin_user): |
|
119 |
data = StringIO('1;Foo\n2;Bar\n3;Baz') |
|
120 |
csv = CsvDataSource.objects.create(csv_file=File(data, 't.csv'), |
|
121 |
columns_keynames='id, text', slug='test', title='a title', description='a description') |
|
122 | ||
123 |
query = Query(slug='fooba', resource=csv, structure='array') |
|
124 |
query.projections = '\n'.join(['id:int(id)', 'text:text']) |
|
125 |
query.save() |
|
126 | ||
127 |
api = ApiUser.objects.create(username='public', |
|
128 |
fullname='public', |
|
129 |
description='access for all', |
|
130 |
keytype='', key='') |
|
131 |
obj_type = ContentType.objects.get_for_model(csv) |
|
132 |
AccessRight.objects.create(codename='can_access', |
|
133 |
apiuser=api, |
|
134 |
resource_type=obj_type, |
|
135 |
resource_pk=csv.pk, |
|
136 |
) |
|
137 | ||
138 |
app = login(app) |
|
139 |
resp = app.get(csv.get_absolute_url()) |
|
140 |
assert '<p>No records found</p>' in resp.body |
|
141 | ||
142 |
app.get('/csvdatasource/test/query/foobar/') |
|
143 |
resp = app.get(csv.get_absolute_url()) |
|
144 |
assert 'endpoint GET /csvdatasource/test/query/foobar/ ' in resp.text |
|
145 | ||
146 |
app.get('/csvdatasource/test/query/foobar/?q=toto') |
|
147 |
resp = app.get(csv.get_absolute_url()) |
|
148 |
assert 'endpoint GET /csvdatasource/test/query/foobar/?q=toto' in resp.text |
|
149 | ||
150 |
resp = resp.click('full page') |
|
151 |
assert resp.text.count('<td class="timestamp">') == 2 |
|
152 | ||
153 |
resp.form['q'] = 'toto' |
|
154 |
resp = resp.form.submit() |
|
155 |
assert resp.text.count('<td class="timestamp">') == 1 |
|
156 | ||
157 |
resp.form['q'] = datetime.date.today().strftime('%d/%m/%Y') |
|
158 |
resp = resp.form.submit() |
|
159 |
assert resp.text.count('<td class="timestamp">') == 2 |
|
160 | ||
161 |
resp.form['q'] = datetime.date.today().strftime('%d/%m/2010') |
|
162 |
resp = resp.form.submit() |
|
163 |
assert resp.text.count('<td class="timestamp">') == 0 |
|
112 |
- |