""" """ 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'])