passerelle/passerelle/base/views.py

355 lines
13 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import json
from dateutil import parser as date_parser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Q
from django.forms import models as model_forms
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.timezone import make_aware
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, UpdateView, View
from ..utils import ImportSiteError, export_site, get_trusted_services, import_site
from ..views import GenericConnectorMixin
from .forms import AccessRightForm, ApiUserForm, AvailabilityParametersForm, ImportSiteForm
from .models import AccessRight, ApiUser, Job, LoggingParameters, ResourceStatus
class ResourceView(DetailView):
template_name = 'passerelle/base/view.html'
def get_object(self, queryset=None):
try:
obj = self.model.objects.get_subclass(slug=self.kwargs['slug'])
except ObjectDoesNotExist:
raise Http404
if obj.is_accessible_by(self.request):
return obj
raise PermissionDenied
def get_context_data(self, slug=None, **kwargs):
context = super().get_context_data(**kwargs)
context['site_base_uri'] = '%s://%s' % (
'https' if self.request.is_secure() else 'http',
self.request.get_host(),
)
context['absolute_uri'] = '%s%s' % (context['site_base_uri'], self.request.path)
return context
class ApiUserCreateView(CreateView):
model = ApiUser
form_class = ApiUserForm
template_name = 'passerelle/manage/apiuser_form.html'
def get_success_url(self):
return reverse('apiuser-list')
class ApiUserUpdateView(UpdateView):
model = ApiUser
form_class = ApiUserForm
template_name = 'passerelle/manage/apiuser_form.html'
def get_success_url(self):
return reverse('apiuser-list')
class ApiUserDeleteView(DeleteView):
model = ApiUser
template_name = 'passerelle/manage/apiuser_confirm_delete.html'
def get_success_url(self):
return reverse('apiuser-list')
class ApiUserListView(ListView):
model = ApiUser
template_name = 'passerelle/manage/apiuser_list.html'
paginate_by = 25
ordering = 'id'
def get_context_data(self, slug=None, **kwargs):
context = super().get_context_data(**kwargs)
context['trusted_services'] = get_trusted_services()
return context
class AccessRightDeleteView(DeleteView):
model = AccessRight
template_name = 'passerelle/manage/accessright_confirm_delete.html'
def get_object(self):
object = super().get_object()
self.resource = object.resource
return object
def get_success_url(self):
return self.resource.get_absolute_url()
class AccessRightCreateView(CreateView):
model = AccessRight
form_class = AccessRightForm
template_name = 'passerelle/manage/accessright_form.html'
def get_initial(self):
d = self.initial.copy()
d['codename'] = self.kwargs.get('codename')
d['resource_type'] = self.kwargs.get('resource_type')
d['resource_pk'] = self.kwargs.get('resource_pk')
return d
def form_valid(self, form):
if not form.cleaned_data['apiuser'].key and not form.allow_open_access:
form.add_confirmation_checkbox()
return self.form_invalid(form)
return super().form_valid(form)
def get_success_url(self):
return self.object.resource.get_absolute_url()
class LoggingParametersUpdateView(FormView):
template_name = 'passerelle/manage/logging_parameters_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['connector'] = self.get_resource()
return context
def get_form_class(self):
form_class = model_forms.modelform_factory(
LoggingParameters,
fields=[
'log_level',
'trace_emails',
'requests_max_size',
'responses_max_size',
'log_retention_days',
],
)
form_class.base_fields['trace_emails'].widget.attrs['rows'] = '3'
return form_class
def get_initial(self):
d = self.initial.copy()
d['resource_type'] = self.kwargs['resource_type']
d['resource_pk'] = self.kwargs['resource_pk']
parameters = self.get_resource().logging_parameters
d['log_level'] = parameters.log_level
d['trace_emails'] = parameters.trace_emails
d['requests_max_size'] = parameters.requests_max_size
d['responses_max_size'] = parameters.responses_max_size
d['log_retention_days'] = parameters.log_retention_days
return d
def get_resource(self):
content_type = ContentType.objects.get_for_id(self.kwargs['resource_type'])
return content_type.model_class().objects.get(pk=self.kwargs['resource_pk'])
def get_success_url(self):
return self.get_resource().get_absolute_url()
def form_valid(self, form):
parameters = self.get_resource().logging_parameters
parameters.log_level = form.cleaned_data['log_level']
parameters.trace_emails = form.cleaned_data['trace_emails']
parameters.requests_max_size = form.cleaned_data['requests_max_size']
parameters.responses_max_size = form.cleaned_data['responses_max_size']
parameters.log_retention_days = form.cleaned_data['log_retention_days']
parameters.save()
return super().form_valid(form)
class ManageAvailabilityView(UpdateView):
template_name = 'passerelle/manage/manage_availability_form.html'
form_class = AvailabilityParametersForm
def get_resource(self):
if not hasattr(self, '_resource'):
content_type = ContentType.objects.get_for_id(self.kwargs['resource_type'])
self._resource = content_type.model_class().objects.get(pk=self.kwargs['resource_pk'])
return self._resource
def get_object(self, queryset=None):
return self.get_resource().availability_parameters
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['connector'] = self.get_resource()
context['availability_status'] = self.get_resource().get_availability_status()
return context
def get_success_url(self):
return self.get_resource().get_absolute_url()
def form_valid(self, form):
resource = self.get_resource()
# if availability check is disabled resource is forced to up to activate logs
if not form.instance.run_check and resource.down():
resource_type = ContentType.objects.get_for_model(resource)
ResourceStatus(
resource_type=resource_type, resource_pk=self.kwargs['resource_pk'], status='up', message=''
).save()
# log changes to notification delays
if 'notification_delays' in form.changed_data:
resource.logger.info('availability checks delays set to %s', form.instance.notification_delays)
# log changes to run_check, if enabled immediately check for availability
if 'run_check' in form.changed_data:
resource.logger.info(
'availability checks %s', 'enabled' if form.instance.run_check else 'disabled'
)
if form.instance.run_check:
resource.availability()
return super().form_valid(form)
class GenericViewJobsConnectorView(GenericConnectorMixin, ListView):
template_name = 'passerelle/manage/service_jobs.html'
paginate_by = 25
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
connector = self.get_object()
context['object'] = connector
context['query'] = self.request.GET.get('q') or ''
if self.request.GET.get('job_id'):
try:
resource_type = ContentType.objects.get_for_model(connector)
context['job_target'] = Job.objects.get(
resource_type=resource_type, resource_pk=connector.pk, pk=self.request.GET['job_id']
)
except (ValueError, Job.DoesNotExist):
pass
return context
def get_object(self):
return self.model.objects.get(slug=self.kwargs['slug'])
def get_queryset(self):
connector = self.get_object()
qs = connector.jobs_set().order_by('-creation_timestamp')
query = self.request.GET.get('q')
if query:
try:
date = date_parser.parse(query, dayfirst=True)
except Exception:
qs = qs.filter(method_name__icontains=query)
else:
date = make_aware(date)
if date.hour == 0 and date.minute == 0 and date.second == 0:
# just a date: display all jobs for that date
max_date = date + datetime.timedelta(days=1)
qs = qs.filter(
Q(
creation_timestamp__gte=date,
creation_timestamp__lte=date + datetime.timedelta(days=1),
)
| Q(
update_timestamp__gte=date,
update_timestamp__lte=date + datetime.timedelta(days=1),
)
| Q(done_timestamp__gte=date, done_timestamp__lte=date + datetime.timedelta(days=1))
)
elif date.second == 0:
# without seconds: display all jobs in this minute
max_date = date + datetime.timedelta(seconds=60)
else:
# display all jobs in the same second
max_date = date + datetime.timedelta(seconds=1)
qs = qs.filter(
Q(creation_timestamp__gte=date, creation_timestamp__lte=max_date)
| Q(update_timestamp__gte=date, update_timestamp__lte=max_date)
| Q(update_timestamp__gte=date, update_timestamp__lte=max_date)
)
return qs
class GenericJobView(GenericConnectorMixin, DetailView):
template_name = 'passerelle/manage/job.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context['job'] = Job.objects.get(pk=self.kwargs['job_pk'])
except Job.DoesNotExist:
raise Http404()
return context
class GenericRestartJobView(GenericConnectorMixin, View):
def get(self, request, *args, **kwargs):
connector = get_object_or_404(self.model, slug=kwargs['slug'])
resource_type = ContentType.objects.get_for_model(connector)
job = get_object_or_404(
Job,
pk=self.kwargs['job_pk'],
resource_type=resource_type,
resource_pk=connector.pk,
status='failed',
)
job.restart()
return HttpResponseRedirect(
reverse('view-jobs-connector', kwargs={'connector': kwargs['connector'], 'slug': kwargs['slug']})
)
class ImportSiteView(FormView):
template_name = 'passerelle/manage/import_site.html'
form_class = ImportSiteForm
def get_success_url(self):
return reverse('manage-home')
def form_valid(self, form):
try:
site_json = json.loads(self.request.FILES['site_json'].read())
except ValueError:
form.add_error('site_json', _('File is not in the expected JSON format.'))
return self.form_invalid(form)
try:
import_site(site_json, overwrite=True, import_users=form.cleaned_data['import_users'])
except ImportSiteError as e:
form.add_error('site_json', e)
return self.form_invalid(form)
return super().form_valid(form)
class ExportSiteView(View):
def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='application/json')
today = datetime.date.today()
response['Content-Disposition'] = 'attachment; filename="export_connectors_{}.json"'.format(
today.strftime('%Y%m%d')
)
json.dump(export_site(), response, indent=2)
return response