diff --git a/castellum/execution/templates/execution/study_detail.html b/castellum/execution/templates/execution/study_detail.html
index 7d19f41134d10937bd5e335e32418234e0fba614..1f3ffd627a78ef866de344fe70ade7998e8c3256 100644
--- a/castellum/execution/templates/execution/study_detail.html
+++ b/castellum/execution/templates/execution/study_detail.html
@@ -46,5 +46,6 @@
{% translate 'Resolve pseudonym' %}
{% translate 'Calendar' %}
{% translate 'News mail' %}
+ {% trans 'Export attributes' %}
{% endblock %}
diff --git a/castellum/execution/views.py b/castellum/execution/views.py
index c23b13b5401d51edc468b712d41585670b4b57d3..1f29838335d60b42594449223761709f4f6cae29 100644
--- a/castellum/execution/views.py
+++ b/castellum/execution/views.py
@@ -20,12 +20,14 @@
# .
import datetime
+import zipfile
from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
+from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import redirect
from django.urls import reverse
@@ -43,6 +45,7 @@ from castellum.castellum_auth.mixins import PermissionRequiredMixin
from castellum.contacts.mixins import BaseContactUpdateView
from castellum.pseudonyms.forms import PseudonymForm
from castellum.pseudonyms.helpers import get_subject
+from castellum.recruitment.attribute_exporters import get_exporter
from castellum.recruitment.mixins import BaseAttributesUpdateView
from castellum.recruitment.mixins import ParticipationMixin
from castellum.recruitment.models import NewsMailBatch
@@ -84,6 +87,36 @@ class StudyDetailView(StudyMixin, PermissionRequiredMixin, DetailView):
return context
+ def export(self):
+ exporter = get_exporter()
+ descriptions = self.study.exportable_attributes.all()
+ response = HttpResponse(content_type='application/zip')
+ zresponse = zipfile.ZipFile(response, 'w')
+
+ subjects = []
+ for participation in self.study.participation_set.filter(status=Participation.INVITED):
+ has_access = self.request.user.has_privacy_level(participation.subject.privacy_level)
+ if has_access:
+ subjects.append((participation.pseudonym, participation.subject))
+
+ filename = exporter.get_schema_filename()
+ content = exporter.get_schema(descriptions)
+ with zresponse.open(filename, 'w') as fh:
+ fh.write(content.encode('UTF-8'))
+
+ filename = exporter.get_data_filename()
+ content = exporter.get_data(descriptions, subjects)
+ with zresponse.open(filename, 'w') as fh:
+ fh.write(content.encode('UTF-8'))
+
+ return response
+
+ def get(self, request, *args, **kwargs):
+ if 'export' in request.GET:
+ return self.export()
+ else:
+ return super().get(request, *args, **kwargs)
+
class ParticipationDetailMixin(ParticipationMixin, PermissionRequiredMixin):
model = Participation
diff --git a/castellum/studies/forms.py b/castellum/studies/forms.py
index c01c8f037d0c28b03e1c52109aa3ebf6515767c6..89d36b6de8d154e0537fdc422ec21d2c9b2f9db7 100644
--- a/castellum/studies/forms.py
+++ b/castellum/studies/forms.py
@@ -53,6 +53,7 @@ class StudyForm(ReadonlyModelForm):
'data_sensitivity',
'consent',
'min_subject_count',
+ 'exportable_attributes',
]
help_texts = {
'announce_status_changes': (
diff --git a/castellum/studies/migrations/0026_study_exportable_attributes.py b/castellum/studies/migrations/0026_study_exportable_attributes.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6523bb3360bc0b257f35a551be1ac4fb71051c5
--- /dev/null
+++ b/castellum/studies/migrations/0026_study_exportable_attributes.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1.3 on 2020-11-17 11:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0022_delete_attributeset'),
+ ('studies', '0025_studysession_schedule_id'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='study',
+ name='exportable_attributes',
+ field=models.ManyToManyField(blank=True, limit_choices_to={'privacy_level_read': 0}, related_name='_study_exportable_attributes_+', to='recruitment.AttributeDescription', verbose_name='Exportable attributes'),
+ ),
+ ]
diff --git a/castellum/studies/models.py b/castellum/studies/models.py
index 12f4eb67913801b48c6cf9f83aedf75b6d6a3ad1..d2fc472f0d3235c76dc5b25e8b4578e88d4381f6 100644
--- a/castellum/studies/models.py
+++ b/castellum/studies/models.py
@@ -191,6 +191,14 @@ class Study(models.Model):
max_upload_size=settings.CASTELLUM_FILE_UPLOAD_MAX_SIZE,
)
+ exportable_attributes = models.ManyToManyField(
+ 'recruitment.AttributeDescription',
+ verbose_name=_('Exportable attributes'),
+ related_name='+',
+ blank=True,
+ limit_choices_to={'privacy_level_read': 0},
+ )
+
mail_subject = models.CharField(_('E-mail subject'), max_length=254, blank=True)
mail_body = models.TextField(
_('E-mail body'),
diff --git a/castellum/studies/templates/studies/study_form.html b/castellum/studies/templates/studies/study_form.html
index 293c25dcc64918757de2ccfc6076a475268f4ecd..f66be0aa29a42a97a70011f5ce4e35aaf5772e15 100644
--- a/castellum/studies/templates/studies/study_form.html
+++ b/castellum/studies/templates/studies/study_form.html
@@ -32,6 +32,7 @@
{% bootstrap_field form.data_sensitivity %}
{% bootstrap_field form.consent %}
{% bootstrap_field form.min_subject_count %}
+ {% bootstrap_field form.exportable_attributes %}
{% if form.is_onetime_invitation %}
{% bootstrap_field form.is_onetime_invitation %}
{% endif %}
diff --git a/castellum/studies/views/studies.py b/castellum/studies/views/studies.py
index 2cd157dd17e3aaa3dd7cdd1d13ca4248392c8ae1..13514321928abe4b7558920c21983ae8a5aebc7e 100644
--- a/castellum/studies/views/studies.py
+++ b/castellum/studies/views/studies.py
@@ -192,6 +192,7 @@ class StudyCreateView(PermissionRequiredMixin, CreateView):
'data_sensitivity',
'consent',
'min_subject_count',
+ 'exportable_attributes',
'is_onetime_invitation',
]
permission_required = 'studies.change_study'