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'