diff --git a/interface/models.py b/interface/models.py index 6eef732..ff72a86 100644 --- a/interface/models.py +++ b/interface/models.py @@ -131,5 +131,16 @@ class Submission(models.Model): slugify(self.instrument) ) + @property + def short_name(self): + _, ext = os.path.splitext(self.download_name) + return "{}_{}_{}{}".format( + #timezone.localtime(self.date).strftime("%Y%m%d%H%M%S"), + slugify(self.name), + slugify(self.instrument), + self.pk, + ext + ) + def __str__(self): return f"{self.name}: {self.date}" diff --git a/interface/templates/interface/project_submissions.mk b/interface/templates/interface/project_submissions.mk new file mode 100644 index 0000000..7bf95c4 --- /dev/null +++ b/interface/templates/interface/project_submissions.mk @@ -0,0 +1,11 @@ + +ALL = {{ targets|join:" " }} + +-include "local.mk" + +all: ${ALL} + +{% for s in submissions %} +{{ s.name }}: + curl -o $@ -L {{ s.url }} +{% endfor %} \ No newline at end of file diff --git a/interface/templates/interface/submission_list.html b/interface/templates/interface/submission_list.html index c4aa730..8af3f4e 100644 --- a/interface/templates/interface/submission_list.html +++ b/interface/templates/interface/submission_list.html @@ -1,6 +1,12 @@ {% extends "interface/project_base.html" %} {% block page %} + +
+ + + +
diff --git a/interface/urls.py b/interface/urls.py index 88bc0ba..324572c 100644 --- a/interface/urls.py +++ b/interface/urls.py @@ -12,6 +12,7 @@ urlpatterns = [ path('', views.EnsembleDetailView.as_view(), name='ensemble_detail'), path('projects/', views.ProjectDetailView.as_view(), name="project_detail"), + path('projects//submissions.mk', views.ProjectMakefileView.as_view(), name="project_makefile"), path('projects//page/', views.WikiView.as_view(), name="wiki"), path('projects//page//edit', views.WikiEditView.as_view(), name="wiki_edit"), @@ -23,6 +24,7 @@ urlpatterns = [ path('projects//submission//upload', views.SubmissionUploadView.as_view(), name="submission_upload"), path('projects//submission//cancel', views.SubmissionCancelView.as_view(), name="submission_cancel"), path('projects//submission//complete', views.SubmissionCompleteView.as_view(), name="submission_complete"), + path('projects//submission//download', views.SubmissionDownloadView.as_view(), name="submission_download"), path('projects//submissions', views.SubmissionListView.as_view(), name="submission_list"), path('projects//resources', views.ResourceListView.as_view(), name="resource_list"), diff --git a/interface/views.py b/interface/views.py index e13a015..b829322 100644 --- a/interface/views.py +++ b/interface/views.py @@ -6,6 +6,7 @@ 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 @@ -20,6 +21,13 @@ 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 @@ -28,6 +36,15 @@ class EnsembleMixin(object): 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') @@ -47,8 +64,11 @@ 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) + 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): @@ -192,6 +212,31 @@ 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 @@ -245,6 +290,13 @@ class SubmissionCompleteView(ProjectMixin, S3CompleteView): 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 @@ -307,6 +359,11 @@ class SubmissionListView(ProjectMixin, ListView): 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']