355 lines
13 KiB
Python
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
|