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 from django.views.generic.base import ContextMixin from django.http import HttpResponseRedirect from django.core.exceptions import SuspiciousOperation from django.contrib import auth from markdown2 import markdown from datetime import datetime from urllib.parse import urlparse, urlencode from . import models, forms from base64 import b64decode import logging logger = logging.getLogger(__name__) 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 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'): 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 S3CompleteMixin(View): always_set = False def complete(self, key): self.object.key = key self.object.save() def get(self, request, *args, **kwargs): self.object = self.get_object() if self.always_set or not self.object.key: if 'location' in request.GET: uri = urlparse(request.GET['location']) self.complete(uri.path[1:]) elif 'key' in request.GET: self.complete(request.GET['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.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 == data['passphrase']: 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) current = models.Ensemble.objects.filter(pk__in=registered.values()) return render(request, 'interface/register.html', {'form': form, 'current': current}) def on_login(sender, **kwargs): request = kwargs['request'] registered = request.session.get('registered', {}) for e in kwargs['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 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 SubmissionCreateView(ProjectMixin, CreateView): model = models.Submission fields = ['name', 'instrument', 'notes'] 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 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 SubmissionDetailView(ProjectMixin, S3CompleteMixin, DetailView): model = models.Submission def complete(self, key): self.object.complete = True super().complete(key) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.is_admin: context['download'] = self.object.presigned_url() return context 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_detail', **self.kwargs) def get_cancel_url(self): return resolve_url('submission_cancel', **self.kwargs) 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 ResourceCreateView(ProjectMixin, CreateView): model = models.Resource fields = ['name', 'media_type', 'description'] template_name = 'interface/project_form.html' title = "Add a new resource" def form_valid(self, form): if not self.request.is_admin: raise SuspiciousOperation("Must be logged in to create resources") 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(S3CompleteMixin, SingleObjectMixin, RedirectView): model = models.Resource always_set = True 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): return super().get_queryset().filter(visible=True) 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