# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at http://mozilla.org/MPL/2.0/.
from django import http
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Group
from django.shortcuts import redirect
from django.template import TemplateDoesNotExist, loader
from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import ERROR_500_TEMPLATE_NAME, ERROR_403_TEMPLATE_NAME
from django.views.generic.base import TemplateView
from guardian.shortcuts import get_objects_for_group, get_objects_for_user
from .clusters.models import Cluster
from .decorators import modified_date
from .jobs.models import SparkJob
[docs]@method_decorator(login_required, name='dispatch')
@method_decorator(modified_date, name='dispatch')
class DashboardView(TemplateView):
"""
The dashboard view that allows filtering clusters and jobs shown.
"""
#: Template name
template_name = 'atmo/dashboard.html'
#: No need to accept POST or DELETE requests
http_method_names = ['get', 'head']
#: Active filter for clusters
active_cluster_filter = 'active'
#: Default cluster filter
default_cluster_filter = active_cluster_filter
#: Allowed filters for clusters
clusters_filters = [active_cluster_filter, 'terminated', 'failed', 'all']
# Filters for jobs.
all_job_filter = 'all'
mine_job_filter = 'mine'
default_job_filter = mine_job_filter
jobs_filters = [mine_job_filter, all_job_filter]
#: Name of auth group that is checked to display Spark jobs
maintainer_group_name = 'Spark job maintainers'
def dispatch(self, request, *args, **kwargs):
self.clusters_shown = self.request.GET.get('clusters', self.default_cluster_filter)
if self.clusters_shown not in self.clusters_filters:
self.clusters_shown = self.default_cluster_filter
self.jobs_maintainer_group = Group.objects.filter(name=self.maintainer_group_name).first()
self.is_sparkjob_maintainer = (
self.jobs_maintainer_group and
self.jobs_maintainer_group in self.request.user.groups.all()
)
self.jobs_shown = self.request.GET.get('jobs', self.default_job_filter)
if self.jobs_shown not in self.jobs_filters:
self.jobs_shown = self.default_job_filter
# Redirect if user isn't in the right group.
if self.jobs_shown == self.all_job_filter and not self.is_sparkjob_maintainer:
return redirect('dashboard')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# get the model manager method depending on the cluster filter
# and call it to get the base queryset
clusters = get_objects_for_user(
self.request.user,
'clusters.view_cluster',
getattr(Cluster.objects, self.clusters_shown)().order_by('-created_at'),
use_groups=False,
with_superuser=False,
)
sparkjob_qs = SparkJob.objects.all().order_by('-start_date')
if self.jobs_shown == self.mine_job_filter:
spark_jobs = get_objects_for_user(
self.request.user,
'jobs.view_sparkjob',
sparkjob_qs,
use_groups=False,
with_superuser=False,
)
elif self.jobs_shown == self.all_job_filter:
spark_jobs = get_objects_for_group(
self.jobs_maintainer_group,
'jobs.view_sparkjob',
sparkjob_qs,
any_perm=False,
accept_global_perms=False,
)
else:
spark_jobs = sparkjob_qs.none()
context.update({
'clusters': clusters,
'spark_jobs': spark_jobs,
})
# a list of modification datetimes of the clusters and Spark jobs to use
# for getting the last changes on the dashboard
cluster_mod_datetimes = list(clusters.values_list('modified_at', flat=True))
spark_job_mod_datetimes = [
spark_job.latest_run.modified_at
for spark_job in spark_jobs.with_runs().order_by('-runs__modified_at')
]
modified_datetimes = sorted(cluster_mod_datetimes + spark_job_mod_datetimes, reverse=True)
if modified_datetimes:
context['modified_date'] = modified_datetimes[0]
return context
[docs]@requires_csrf_token
def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
"""
500 error handler.
:template: :file:`500.html`
"""
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
if template_name != ERROR_500_TEMPLATE_NAME:
# Reraise if it's a missing custom template.
raise
return http.HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
return http.HttpResponseServerError(template.render(request=request))
# This can be called when CsrfViewMiddleware.process_view has not run,
# therefore need @requires_csrf_token in case the template needs
# {% csrf_token %}.
[docs]@requires_csrf_token
def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME):
"""
Permission denied (403) handler.
:template: :file:`403.html`
If the template does not exist, an Http403 response containing the text
"403 Forbidden" (as per RFC 7231) will be returned.
"""
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
if template_name != ERROR_403_TEMPLATE_NAME:
# Reraise if it's a missing custom template.
raise
return http.HttpResponseForbidden('<h1>403 Forbidden</h1>', content_type='text/html')
return http.HttpResponseForbidden(
template.render(request=request, context={'exception': force_text(exception)})
)