from django.shortcuts import render, get_object_or_404, redirect, resolve_url from django.views.generic import TemplateView, View, RedirectView from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.list import ListView from django.views.generic.edit import CreateView, UpdateView, FormView from django.views.generic.base import ContextMixin from django.http import HttpResponseRedirect from django.core.exceptions import SuspiciousOperation from django.core.signing import Signer from django.contrib import auth from markdown2 import markdown from datetime import datetime from urllib.parse import urlparse, urlencode import os.path from . import models, forms from base64 import b64decode import logging logger = logging.getLogger(__name__) signer = Signer() def signed_url(name, **kwargs): url = resolve_url(name, **kwargs) sig = signer.sign(url) return sig.replace(":", "?auth=") class EnsembleMixin(object): admin_required = False def dispatch(self, request, *args, **kwargs): request.ensemble_id = request.session.get('ensemble') request.is_admin = request.user.is_superuser if 'auth' in request.GET: sig = signer.sign(request.path) if sig[len(request.path)+1:] == request.GET['auth']: logger.info("Allowing auth key") request.is_admin = True return super().dispatch(request, *args, **kwargs) else: raise SuspiciousOperation("Bad auth code") if not request.ensemble_id: return redirect('register') if not request.is_admin and request.user.is_authenticated: try: request.user.ensembles.get(pk=request.ensemble_id) request.is_admin = True except models.Ensemble.DoesNotExist: pass if self.admin_required and not request.is_admin: return redirect('login') return super().dispatch(request, *args, **kwargs) class ProjectMixin(EnsembleMixin): def get_project(self): if not hasattr(self, '_project'): if self.request.is_admin: # can access any ensemble self._project = get_object_or_404(models.Project, pk=self.kwargs['project']) else: self._project = get_object_or_404(models.Project, pk=self.kwargs['project'], ensemble=self.request.ensemble_id) return self._project def get_queryset(self): return super().get_queryset().filter(project=self.get_project()) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['project'] = self.get_project() return context class S3UploadMixin(ProjectMixin): accept_files = '' def get_accept_files(self): return self.accept_files def get_cancel_url(self): return self.cancel_url def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) success_url = self.request.build_absolute_uri(self.get_success_url()) key_template = self.object.key_template() project = self.get_project() context['upload'] = project.presigned_post(key_template, fields={'success_action_redirect': success_url}, conditions=[["starts-with", "$success_action_redirect", ""]]) context['ajax_upload'] = project.presigned_post(key_template) context['success_url'] = success_url context['cancel_url'] = self.get_cancel_url() context['accept_files'] = self.accept_files return context class S3CompleteView(SingleObjectMixin, RedirectView): def complete(self, key): self.object.key = key self.object.save() def get(self, request, *args, **kwargs): self.object = self.get_object() if 'key' in request.GET: self.complete(request.GET['key']) elif 'location' in request.GET: uri = urlparse(request.GET['location']) bucket, key = uri.path[1:].split('/', 1) if bucket != models.BUCKET: key = uri.path[1:] self.complete(key) else: raise KeyError("No key or location found") return super().get(request, *args, **kwargs) def register(request): if 'clear' in request.GET: request.session.clear() request.ensemble_id = request.session.get('ensemble') registered = request.session.setdefault('registered', {}) code = request.GET.get('code', '').replace('-', '') # check if already joined if code in registered: request.session['ensemble'] = registered[code] return redirect('ensemble_detail') if request.user.is_superuser and code: request.session['ensemble'] = models.Ensemble.objects.get(code=code).pk return redirect('ensemble_detail') if request.method == "POST": form = forms.CodeForm(request.POST) if form.is_valid(): data = form.cleaned_data try: ensemble = models.Ensemble.objects.get(code=data['code'].replace('-', '')) if ensemble.passphrase.lower() == data['passphrase'].lower(): request.session['ensemble'] = ensemble.pk registered[ensemble.code] = ensemble.pk return redirect('ensemble_detail') except models.Ensemble.DoesNotExist: form.add_error(None, "Incorrect code or passphrase") else: form = forms.CodeForm(initial=request.GET) if request.user.is_superuser: current = models.Ensemble.objects.all() else: current = models.Ensemble.objects.filter(pk__in=registered.values()) return render(request, 'interface/register.html', {'form': form, 'current': current}) def on_login(sender, **kwargs): user = kwargs['user'] request = kwargs['request'] registered = request.session.get('registered', {}) for e in user.ensembles.all(): if not e.code in registered: registered[e.code] = e.pk request.session['registered'] = registered auth.signals.user_logged_in.connect(on_login) def logout(request): ensemble = request.session.get('ensemble') registered = request.session.get('registered', {}) auth.logout(request) request.session['ensemble'] = ensemble request.session['registered'] = registered return redirect('/') class EnsembleDetailView(EnsembleMixin, DetailView): def dispatch(self, request, *args, **kwargs): # capture provided urls if 'code' in request.GET: return redirect('/register?code={0}'.format(request.GET['code'])) return super().dispatch(request, *args, **kwargs) def get_object(self): return models.Ensemble.objects.get(pk=self.request.ensemble_id) class ProjectDetailView(EnsembleMixin, DetailView): def get_queryset(self): return models.Project.objects.filter(ensemble=self.request.ensemble_id) 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 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 fields = ['title', 'markdown'] class WikiEditView(ProjectMixin, UpdateView): admin_required = True model = models.WikiPage fields = ['title', 'markdown'] class SubmissionCreateView(ProjectMixin, FormView): #model = models.Submission #fields = ['name', 'instrument', 'url', 'notes'] form_class = forms.SubmissionForm template_name = "interface/submission_create.html" def form_valid(self, form): self.object = form.save(commit=False) self.object.project = self.get_project() self.object.save() self.request.session['name'] = self.object.name self.request.session['instrument'] = self.object.instrument if form.cleaned_data['method'] == 'link': return redirect('submission_link', project=self.object.project.pk, pk=self.object.pk) return redirect('submission_upload', project=self.object.project.pk, pk=self.object.pk) def get_initial(self): return { k: self.request.session.get(k) for k in ('name', 'instrument') } class SubmissionCompleteView(ProjectMixin, S3CompleteView): model = models.Submission def complete(self, key): self.object.url = key self.object.private = False self.object.complete = True self.object.save() def get_redirect_url(self, **kwargs): return resolve_url('submission_detail', **self.kwargs) class SubmissionDownloadView(ProjectMixin, SingleObjectMixin, RedirectView): model = models.Submission admin_required = True def get_redirect_url(self, **kwargs): return self.get_object().download_url class SubmissionDetailView(ProjectMixin, DetailView): model = models.Submission def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['can_download'] = self.request.is_admin return context class SubmissionPreview(ProjectMixin, DetailView): model = models.Submission template_name = 'interface/submission_preview.html' admin_required = True class SubmissionUploadView(S3UploadMixin, DetailView): template_name = 'interface/s3_upload.html' model = models.Submission accept_files = "video/*" def get_success_url(self): return resolve_url('submission_complete', **self.kwargs) def get_cancel_url(self): return resolve_url('submission_cancel', **self.kwargs) class SubmissionLinkView(ProjectMixin, UpdateView): model = models.Submission template_name = 'interface/submission_link.html' fields = ['url'] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['cancel_url'] = self.get_cancel_url() return context def get_success_url(self): return resolve_url('submission_detail', **self.kwargs) def get_cancel_url(self): return resolve_url('submission_cancel', **self.kwargs) def form_valid(self, form): self.object = form.save(commit=False) self.object.complete = True self.object.private = True self.object.save() return redirect(self.get_success_url()) class SubmissionCancelView(ProjectMixin, SingleObjectMixin, View): model = models.Submission def get(self, request, *args, **kwargs): self.object = self.get_object() self.object.delete() return redirect('project_detail', pk=kwargs['project']) class SubmissionListView(ProjectMixin, ListView): model = models.Submission admin_required = True def get_queryset(self): return super().get_queryset().filter(complete=True).order_by('-pk') def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) data['signed_url'] = self.request.build_absolute_uri(signed_url('project_makefile', pk=self.kwargs['project'])) return data class ResourceCreateView(ProjectMixin, CreateView): model = models.Resource fields = ['name', 'media_type', 'description'] template_name = 'interface/project_form.html' title = "Add a new resource" admin_required = True def form_valid(self, form): self.object = form.save(commit=False) self.object.project = self.get_project() self.object.save() return redirect('resource_upload', project=self.object.project_id, pk=self.object.pk) class ResourceUploadView(S3UploadMixin, DetailView): model = models.Resource template_name = 'interface/s3_upload.html' def get_accept_files(self): return self.object.accept() def get_success_url(self): return resolve_url('resource_complete', **self.kwargs) def get_cancel_url(self): return resolve_url('resource_list', project=self.kwargs['project']) class ResourceCompleteView(ProjectMixin, S3CompleteView): model = models.Resource def get_redirect_url(self, **kwargs): 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']) class ManageView(EnsembleMixin, TemplateView): template_name = 'interface/manage.html' admin_required = True def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['ensemble'] = models.Ensemble.objects.get(pk=self.request.ensemble_id) context['ensemble_url'] = self.request.build_absolute_uri('/?code={0}'.format(context['ensemble'].ensemble_code())) return context