345 lines
11 KiB
Python
345 lines
11 KiB
Python
"""
|
|
|
|
"""
|
|
|
|
|
|
from django.shortcuts import render, get_object_or_404, redirect, resolve_url
|
|
from django.views.generic import TemplateView, RedirectView
|
|
from django.views.generic.detail import DetailView
|
|
from django.views.generic.list import ListView
|
|
from django.views.generic.edit import CreateView, UpdateView
|
|
from django.core.exceptions import SuspiciousOperation, PermissionDenied
|
|
from django.http import Http404, HttpResponseForbidden
|
|
from django.contrib import auth
|
|
from django.db.models import Q
|
|
from django.utils import timezone
|
|
|
|
from markdown2 import markdown
|
|
from functools import cached_property
|
|
|
|
from . import models, forms
|
|
from interface.utils import signed_url, check_signed_url
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class AuthorizedResourceMixin(object):
|
|
"""
|
|
Handles two parts of the permission system, signed urls and persistent authenticated resources
|
|
"""
|
|
|
|
SESSION_KEY = 'authorized'
|
|
admin_required = False
|
|
|
|
def is_authorized(self):
|
|
"By default check if superuser or a signed request"
|
|
if self.request.is_admin:
|
|
return True
|
|
|
|
if 'auth' in self.request.GET:
|
|
check_signed_url(self.request.path, self.request.GET['auth'])
|
|
self.on_signed_request()
|
|
return True
|
|
|
|
return False
|
|
|
|
def on_signed_request(self):
|
|
pass
|
|
|
|
def get_authorized_keys(self, resource):
|
|
'Returns a set of authorized keys for this resource'
|
|
return set(self._authorized.get(resource, []))
|
|
|
|
def add_authorized_key(self, resource, key):
|
|
'Adds a key to the authorized list for this resource'
|
|
current = self.get_authorized_keys(resource)
|
|
current.add(key)
|
|
self._authorized[resource] = list(current)
|
|
self.request.session[self.SESSION_KEY] = self._authorized
|
|
|
|
def del_authorized_key(self, resource, key):
|
|
current = self.get_authorized_keys(resource)
|
|
current.discard(key)
|
|
if current:
|
|
self._authorized[resource] = list(current)
|
|
else:
|
|
self._authorized.pop(current)
|
|
self.request.session[self.SESSION_KEY] = self._authorized
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
self._authorized = request.session.get('authorized', {})
|
|
request.is_admin = request.user.is_superuser
|
|
|
|
if not self.is_authorized():
|
|
raise Http404("Either the given resource doesn't exist or you dont have access to it.")
|
|
|
|
if self.admin_required and not request.is_admin:
|
|
raise PermissionDenied("You must be an ensemble admin.")
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
class EnsembleMixin(AuthorizedResourceMixin):
|
|
|
|
def is_authorized(self):
|
|
if 'forget' in self.request.GET:
|
|
self.del_authorized_key('ensemble', self.kwargs['ensemble'])
|
|
raise Http404("Access removed")
|
|
super().is_authorized()
|
|
try:
|
|
self.ensemble = self.get_ensemble()
|
|
return True
|
|
except models.Ensemble.DoesNotExist:
|
|
return False
|
|
|
|
def get_ensemble(self):
|
|
ensemble = self.get_queryset().get(slug=self.kwargs['ensemble'])
|
|
|
|
self.request.is_admin = ensemble.has_admin(self.request.user)
|
|
|
|
return ensemble
|
|
|
|
def get_object(self):
|
|
return self.ensemble
|
|
|
|
def get_queryset(self):
|
|
ensembles = models.Ensemble.objects.all()
|
|
|
|
if self.request.is_admin:
|
|
return ensembles
|
|
|
|
# limit to registered ensembles
|
|
f = Q(slug__in=self.get_authorized_keys('ensemble'))
|
|
|
|
# or ensembles where the user is admin
|
|
if self.request.user.is_authenticated:
|
|
f |= Q(admins=self.request.user.pk)
|
|
|
|
return ensembles.filter(f)
|
|
|
|
class ProjectMixin(AuthorizedResourceMixin):
|
|
|
|
def is_authorized(self):
|
|
super().is_authorized()
|
|
try:
|
|
self.project = self.get_project()
|
|
return True
|
|
except models.Project.DoesNotExist:
|
|
return False
|
|
|
|
def get_project(self):
|
|
project = self.get_project_queryset().get(pk=self.kwargs['project'])
|
|
self.request.is_admin = project.ensemble.has_admin(self.request.user)
|
|
return project
|
|
|
|
def get_project_queryset(self):
|
|
projects = models.Project.objects.select_related('ensemble')
|
|
if self.request.is_admin:
|
|
return projects
|
|
|
|
f = Q(pk__in=self.get_authorized_keys('project')) | Q(ensemble__slug__in=self.get_authorized_keys('ensemble'))
|
|
|
|
if self.request.user.is_authenticated:
|
|
f |= Q(ensemble__admins=self.request.user.pk)
|
|
else:
|
|
f &= Q(active=True)
|
|
|
|
return projects.filter(f)
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(project=self.project)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
if 'project' in self.kwargs:
|
|
context['project'] = self.project
|
|
context['modules'] = self.project.active_modules
|
|
return context
|
|
|
|
class CrispyFormMixin(object):
|
|
|
|
cancel_url = None
|
|
|
|
def get_cancel_url(self):
|
|
return self.cancel_url
|
|
|
|
""" ENSEMBLE VIEWS """
|
|
|
|
class EnsembleListView(EnsembleMixin, ListView):
|
|
model = models.Ensemble
|
|
|
|
def is_authorized(self):
|
|
return True
|
|
|
|
class EnsembleDetailView(EnsembleMixin, DetailView):
|
|
|
|
def on_signed_request(self):
|
|
self.add_authorized_key('ensemble', self.kwargs['ensemble'])
|
|
|
|
def get_context_data(self, **kwargs):
|
|
data = super().get_context_data(**kwargs)
|
|
data['inactive'] = 'inactive' in self.request.GET
|
|
if data['inactive']:
|
|
data['object_list'] = self.object.projects.all().order_by('-pk')
|
|
else:
|
|
data['object_list'] = self.object.active_projects()
|
|
if self.request.is_admin:
|
|
data['ensemble_link'] = signed_url(self.request.path)
|
|
return data
|
|
|
|
""" PROJECT VIEWS """
|
|
|
|
class ProjectListView(ProjectMixin, ListView):
|
|
|
|
def is_authorized(self):
|
|
return True
|
|
|
|
def get_queryset(self):
|
|
return self.get_project_queryset().filter(active=True, event_date__gte=timezone.now()-timezone.timedelta(7))
|
|
|
|
class ProjectCreateView(EnsembleMixin, CreateView):
|
|
admin_required = True
|
|
model = models.Project
|
|
template_name = "interface/default_form.html"
|
|
title = "Add a new project"
|
|
form_class = forms.ProjectForm
|
|
|
|
def form_valid(self, form):
|
|
self.object = form.save(commit=False)
|
|
self.object.ensemble_id = self.kwargs['pk']
|
|
self.object.owner = self.request.user
|
|
self.object.save()
|
|
return redirect('project_detail', project=self.object.pk)
|
|
|
|
class ProjectDetailView(ProjectMixin, DetailView):
|
|
|
|
def on_signed_request(self):
|
|
self.add_authorized_key('project', self.kwargs['project'])
|
|
|
|
def get_object(self, queryset=None):
|
|
return self.project
|
|
|
|
def get_context_data(self, **kwargs):
|
|
data = super().get_context_data(**kwargs)
|
|
data['project_link'] = signed_url(self.request.path)
|
|
return data
|
|
|
|
class ProjectUpdateView(ProjectMixin, UpdateView):
|
|
admin_required = True
|
|
template_name = "interface/default_form.html"
|
|
pk_url_kwarg = 'project'
|
|
form_class = forms.ProjectForm
|
|
|
|
def get_object(self):
|
|
return self.project
|
|
|
|
@property
|
|
def cancel_url(self):
|
|
return self.get_success_url()
|
|
|
|
def get_success_url(self):
|
|
return resolve_url('project_detail', project=self.kwargs['project'])
|
|
|
|
# Old Makefile from submission module
|
|
#class ProjectMakefileView(EnsembleMixin, DetailView):
|
|
# template_name = 'interface/project_submissions.mk'
|
|
# content_type = 'text/plain'
|
|
#
|
|
# def get_queryset(self):
|
|
# if self.request.is_admin:
|
|
# return models.Project.objects.all()
|
|
#
|
|
# return models.Project.objects.filter(ensemble=self.request.ensemble_id)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# data = super().get_context_data(**kwargs)
|
|
#
|
|
# data['submissions'] = []
|
|
# data['targets'] = []
|
|
# for s in self.object.submissions:
|
|
# name = s.short_name
|
|
# data['targets'].append(name)
|
|
# data['submissions'].append({
|
|
# 'url': self.request.build_absolute_uri(signed_url('submission_download', project=self.kwargs['pk'], pk=s.pk)),
|
|
# 'name': name,
|
|
# })
|
|
#
|
|
# return data
|
|
|
|
""" WIKI VIEWS """
|
|
|
|
class WikiView(ProjectMixin, DetailView):
|
|
template_name = 'interface/wiki.html'
|
|
model = models.WikiPage
|
|
|
|
def get_context_data(self, **kwargs):
|
|
data = super().get_context_data(**kwargs)
|
|
data['wiki_html'] = markdown(self.object.markdown)
|
|
return data
|
|
|
|
class WikiCreateView(ProjectMixin, CreateView):
|
|
admin_required = True
|
|
model = models.WikiPage
|
|
template_name = 'interface/default_form.html'
|
|
form_class = forms.WikiForm
|
|
|
|
def cancel_url(self):
|
|
return resolve_url('project_detail', self.kwargs['project'])
|
|
|
|
def form_valid(self, form):
|
|
self.object = form.save(commit=False)
|
|
self.object.project = self.project
|
|
self.object.save()
|
|
return redirect('wiki', project=self.object.project_id, pk=self.object.pk)
|
|
|
|
class WikiEditView(ProjectMixin, UpdateView):
|
|
admin_required = True
|
|
model = models.WikiPage
|
|
form_class = forms.WikiForm
|
|
|
|
def cancel_url(self):
|
|
return resolve_url('wiki', self.kwargs['project'], self.kwargs['pk'])
|
|
|
|
""" RESOURCE VIEWS """
|
|
|
|
class ResourceCreateView(ProjectMixin, CreateView):
|
|
admin_required = True
|
|
model = models.Resource
|
|
form_class = forms.ResourceForm
|
|
template_name = 'interface/default_form.html'
|
|
title = "Add a new resource"
|
|
|
|
def form_valid(self, form):
|
|
self.object = form.save(commit=False)
|
|
self.object.project = self.project
|
|
self.object.save()
|
|
return redirect('resource_upload', project=self.object.project_id, pk=self.object.pk)
|
|
|
|
class ResourceUploadView(ProjectMixin, UpdateView):
|
|
admin_required = True
|
|
model = models.Resource
|
|
fields = ['file']
|
|
template_name = 'interface/default_form.html'
|
|
|
|
def get_success_url(self):
|
|
return resolve_url('resource_list', project=self.kwargs['project'])
|
|
|
|
class ResourceListView(ProjectMixin, ListView):
|
|
model = models.Resource
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
if not self.request.is_admin:
|
|
qs = qs.filter(visible=True)
|
|
return qs
|
|
|
|
class ResourceEditView(ProjectMixin, UpdateView):
|
|
admin_required = True
|
|
model = models.Resource
|
|
fields = ['name', 'description', 'visible']
|
|
template_name = 'interface/default_form.html'
|
|
|
|
def get_success_url(self):
|
|
return resolve_url('resource_list', project=self.kwargs['project'])
|