From 037521d53020e9185346841e0f74f8688728ecb5 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 22 Mar 2022 17:37:46 +0100 Subject: [PATCH 1/4] rename Domain to CodingList --- castellum/appointments/forms.py | 2 +- castellum/appointments/helpers.py | 2 +- castellum/appointments/mixins.py | 2 +- castellum/appointments/signals.py | 2 +- castellum/execution/api.py | 38 ++-- .../execution/participation_detail.html | 14 +- .../execution/participation_pseudonyms.html | 12 +- .../templates/execution/study_export.html | 2 +- .../templates/execution/study_resolve.html | 6 +- castellum/execution/urls.py | 17 +- castellum/execution/views.py | 48 ++--- castellum/pseudonyms/admin.py | 4 +- castellum/pseudonyms/forms.py | 16 +- castellum/pseudonyms/helpers.py | 25 +-- castellum/pseudonyms/models.py | 14 +- .../commands/create_demo_content.py | 6 +- castellum/recruitment/signals.py | 2 +- castellum/settings/default/__init__.py | 20 +- castellum/studies/apps.py | 6 +- castellum/studies/models.py | 20 +- castellum/studies/signals.py | 16 +- ...e.html => coding_list_confirm_delete.html} | 12 +- .../studies/templates/studies/study_base.html | 2 +- ...base.html => study_coding_lists_base.html} | 8 +- .../studies/study_coding_lists_general.html | 14 ++ ...udy.html => study_coding_lists_study.html} | 24 +-- .../studies/templates/studies/study_diff.html | 8 +- .../studies/study_domains_general.html | 14 -- castellum/studies/urls/__init__.py | 20 +- castellum/studies/views/studies.py | 62 +++--- castellum/subjects/api.py | 14 +- .../subjects/subject_confirm_delete.html | 14 +- .../templates/subjects/subject_export.html | 6 +- .../subjects/subject_pseudonyms.html | 12 +- castellum/subjects/urls.py | 7 +- castellum/subjects/views.py | 22 ++- docs/API.md | 12 +- docs/pseudonyms.md | 30 +-- .../test_fetch_scheduler_appointments.py | 2 +- .../views/test_api_attributes_view.py | 36 ++-- .../views/test_api_coding_lists_view.py | 41 ++++ .../execution/views/test_api_domains_view.py | 41 ---- .../views/test_api_pseudonyms_view.py | 20 +- .../execution/views/test_api_resolve_view.py | 184 +++++++++--------- .../execution/views/test_api_validate_view.py | 36 ++-- tests/execution/views/test_export_view.py | 46 ++--- tests/execution/views/test_pseudonyms_view.py | 4 +- tests/execution/views/test_views.py | 22 +-- tests/pseudonyms/test_general_attributes.py | 30 +-- tests/pseudonyms/test_helpers.py | 20 +- tests/recruitment/models/test_recruitment.py | 6 +- tests/studies/models/test_study.py | 12 +- tests/studies/views/test_coding_lists_view.py | 41 ++++ tests/studies/views/test_create_view.py | 10 +- tests/studies/views/test_diff_view.py | 4 +- .../views/test_pseudonym_domains_view.py | 41 ---- tests/subjects/views/test_api.py | 20 +- tests/subjects/views/test_subject_export.py | 2 +- tests/subjects/views/test_views.py | 8 +- 59 files changed, 608 insertions(+), 573 deletions(-) rename castellum/studies/templates/studies/{domain_confirm_delete.html => coding_list_confirm_delete.html} (62%) rename castellum/studies/templates/studies/{study_domains_base.html => study_coding_lists_base.html} (53%) create mode 100644 castellum/studies/templates/studies/study_coding_lists_general.html rename castellum/studies/templates/studies/{study_domains_study.html => study_coding_lists_study.html} (55%) delete mode 100644 castellum/studies/templates/studies/study_domains_general.html create mode 100644 tests/execution/views/test_api_coding_lists_view.py delete mode 100644 tests/execution/views/test_api_domains_view.py create mode 100644 tests/studies/views/test_coding_lists_view.py delete mode 100644 tests/studies/views/test_pseudonym_domains_view.py diff --git a/castellum/appointments/forms.py b/castellum/appointments/forms.py index 59a63cde6..385cfc636 100644 --- a/castellum/appointments/forms.py +++ b/castellum/appointments/forms.py @@ -63,7 +63,7 @@ class AppointmentsForm(forms.ModelForm): def get_invitation_url(self, session): if settings.SCHEDULER_URL and session.schedule_id: if self.instance.status == Participation.INVITED: - pseudonym = get_pseudonym(self.instance.subject, session.domain) + pseudonym = get_pseudonym(self.instance.subject, session.coding_list_key) try: return scheduler.create_invitation_url(session.schedule_id, pseudonym) except requests.RequestException as e: diff --git a/castellum/appointments/helpers.py b/castellum/appointments/helpers.py index 6bc088406..4a7d2d823 100644 --- a/castellum/appointments/helpers.py +++ b/castellum/appointments/helpers.py @@ -162,7 +162,7 @@ def fetch_scheduler_appointments(study, base_url, timeout=0): session.save() for participation in session.study.participation_set.filter(status=Participation.INVITED): - pseudonym = get_pseudonym(participation.subject, session.domain) + pseudonym = get_pseudonym(participation.subject, session.coding_list_key) start = data.get(pseudonym) change = Appointment.change_start(session, participation, start) diff --git a/castellum/appointments/mixins.py b/castellum/appointments/mixins.py index 921a0a6e3..fc63ab132 100644 --- a/castellum/appointments/mixins.py +++ b/castellum/appointments/mixins.py @@ -52,7 +52,7 @@ class SchedulerFetchParticipationMixin: def fetch_scheduler_appointments(self): changes = [] for session in self.study.studysession_set.exclude(schedule_id=None): - pseudonym = get_pseudonym(self.subject, session.domain) + pseudonym = get_pseudonym(self.subject, session.coding_list_key) try: start = scheduler.get(session.schedule_id, pseudonym) except requests.RequestException as e: diff --git a/castellum/appointments/signals.py b/castellum/appointments/signals.py index b13b784ff..13a220f1e 100644 --- a/castellum/appointments/signals.py +++ b/castellum/appointments/signals.py @@ -28,7 +28,7 @@ def delete_scheduler_appointments_on_delete(sender, instance, using, **kwargs): if sender is Participation and settings.SCHEDULER_URL: for session in instance.study.studysession_set.exclude(schedule_id=None): - pseudonym = get_pseudonym(instance.subject, session.domain) + pseudonym = get_pseudonym(instance.subject, session.coding_list_key) scheduler.delete(session.schedule_id, pseudonym) diff --git a/castellum/execution/api.py b/castellum/execution/api.py index c990d2c53..80b5f742d 100644 --- a/castellum/execution/api.py +++ b/castellum/execution/api.py @@ -32,7 +32,7 @@ from castellum.castellum_auth.mixins import APIAuthMixin from castellum.castellum_auth.mixins import PermissionRequiredMixin from castellum.pseudonyms.helpers import get_pseudonym from castellum.pseudonyms.helpers import get_subject -from castellum.pseudonyms.models import Domain +from castellum.pseudonyms.models import CodingList from castellum.recruitment.attribute_exporters import JSONExporter from castellum.recruitment.models import Participation from castellum.studies.mixins import StudyMixin @@ -47,10 +47,10 @@ class BaseAPIView(APIAuthMixin, StudyMixin, PermissionRequiredMixin, View): study_status = [Study.EXECUTION] -class APIDomainsView(BaseAPIView): +class APICodingListsView(BaseAPIView): def get(self, request, **kwargs): - data = [{'key': domain.key, 'name': domain.name} for domain in self.study.domains.all()] - return JsonResponse({'domains': data}) + data = [{'key': cl.key, 'name': cl.name} for cl in self.study.coding_lists.all()] + return JsonResponse({'coding_lists': data}) class APIValidateView(BaseAPIView): @@ -60,10 +60,10 @@ class APIValidateView(BaseAPIView): except ValueError: raise Http404 - domain = get_object_or_404(self.study.domains, key=self.kwargs['domain']) + coding_list = get_object_or_404(self.study.coding_lists, key=self.kwargs['coding_list']) try: - subject = get_subject(domain.key, pseudonym) + subject = get_subject(coding_list.key, pseudonym) except Subject.DoesNotExist: raise Http404 @@ -76,9 +76,9 @@ class APIValidateView(BaseAPIView): class APIPseudonymsView(BaseAPIView): def get(self, request, **kwargs): - domain = get_object_or_404(self.study.domains, key=kwargs['domain']) + coding_list = get_object_or_404(self.study.coding_lists, key=kwargs['coding_list']) - pseudonyms = [get_pseudonym(p.subject, domain.key) for p in ( + pseudonyms = [get_pseudonym(p.subject, coding_list.key) for p in ( self.study.participation_set .filter(status=Participation.INVITED) .select_related('subject') @@ -94,10 +94,10 @@ class APIResolveView(BaseAPIView): except ValueError: raise Http404 - domain = get_object_or_404(self.study.domains, key=self.kwargs['domain']) + coding_list = get_object_or_404(self.study.coding_lists, key=self.kwargs['coding_list']) try: - subject = get_subject(domain.key, pseudonym) + subject = get_subject(coding_list.key, pseudonym) except Subject.DoesNotExist: raise Http404 get_object_or_404( @@ -106,15 +106,15 @@ class APIResolveView(BaseAPIView): if not self.request.user.has_privacy_level(subject.privacy_level): raise PermissionDenied - domains = Domain.objects.filter( - models.Q(pk__in=self.study.domains.all()) - | models.Q(pk__in=self.study.general_domains.filter(managers=request.user)) + coding_lists = CodingList.objects.filter( + models.Q(pk__in=self.study.coding_lists.all()) + | models.Q(pk__in=self.study.general_coding_lists.filter(managers=request.user)) ) - target_domain = get_object_or_404(domains, key=self.kwargs['target_domain']) - pseudonym = get_pseudonym(subject, target_domain.key) + target_coding_list = get_object_or_404(coding_lists, key=self.kwargs['target_coding_list']) + pseudonym = get_pseudonym(subject, target_coding_list.key) - monitoring_logger.info('Pseudonym access: domain {} by {}'.format( - target_domain.key, self.request.user.pk + monitoring_logger.info('Pseudonym access: coding_list {} by {}'.format( + target_coding_list.key, self.request.user.pk )) return JsonResponse({'pseudonym': pseudonym}) @@ -127,10 +127,10 @@ class APIAttributesView(BaseAPIView): except ValueError: raise Http404 - domain = get_object_or_404(self.study.domains, key=self.kwargs['domain']) + coding_list = get_object_or_404(self.study.coding_lists, key=self.kwargs['coding_list']) try: - subject = get_subject(domain.key, pseudonym) + subject = get_subject(coding_list.key, pseudonym) except Subject.DoesNotExist: raise Http404 get_object_or_404( diff --git a/castellum/execution/templates/execution/participation_detail.html b/castellum/execution/templates/execution/participation_detail.html index a48b3ae97..d1d5406cd 100644 --- a/castellum/execution/templates/execution/participation_detail.html +++ b/castellum/execution/templates/execution/participation_detail.html @@ -2,24 +2,24 @@ {% load i18n utils auth %} {% block content %} - {% if domains|length > 1 %} + {% if coding_lists|length > 1 %}

{% translate 'Pseudonyms' %}

- {% for domain in domains %} -
{{ domain }}
+ {% for coding_list in coding_lists %} +
{{ coding_list }}
- + {% translate 'get pseudonym' %}
{% endfor %}
- {% elif domains|length > 0 %} + {% elif coding_lists|length > 0 %}
{% translate 'Pseudonym' %}
- {% for domain in domains %} + {% for coding_list in coding_lists %}
- + {% translate 'get pseudonym' %}
diff --git a/castellum/execution/templates/execution/participation_pseudonyms.html b/castellum/execution/templates/execution/participation_pseudonyms.html index 60a9c8997..efa277726 100644 --- a/castellum/execution/templates/execution/participation_pseudonyms.html +++ b/castellum/execution/templates/execution/participation_pseudonyms.html @@ -9,18 +9,18 @@ - - + + - {% for domain in domains %} + {% for coding_list in coding_lists %} -
{% translate 'Domain key' %}{% translate 'Domain name' %}{% translate 'Coding list key' %}{% translate 'Coding list name' %} {% translate 'Pseudonym' %}
{{ domain|display:'key' }} - {{ domain|display:'name' }} + {{ coding_list|display:'key' }} + {{ coding_list|display:'name' }} - + {% translate 'get pseudonym' %} diff --git a/castellum/execution/templates/execution/study_export.html b/castellum/execution/templates/execution/study_export.html index d1ff65109..afd8ea8ed 100644 --- a/castellum/execution/templates/execution/study_export.html +++ b/castellum/execution/templates/execution/study_export.html @@ -6,7 +6,7 @@ {% block content %}
{% include 'utils/form_errors.html' with form=form %} - {% bootstrap_field form.domain %} + {% bootstrap_field form.coding_list %}
{% endblock %} diff --git a/castellum/execution/templates/execution/study_resolve.html b/castellum/execution/templates/execution/study_resolve.html index 71b27716d..7f7da3f34 100644 --- a/castellum/execution/templates/execution/study_resolve.html +++ b/castellum/execution/templates/execution/study_resolve.html @@ -6,10 +6,10 @@ {% include 'utils/form_errors.html' with form=form %} {% csrf_token %} - {% if form.domain.field.choices|length != 1 %} - {% bootstrap_field form.domain %} + {% if form.coding_list.field.choices|length != 1 %} + {% bootstrap_field form.coding_list %} {% else %} - {{ form.domain.as_hidden }} + {{ form.coding_list.as_hidden }} {% endif %} {% bootstrap_field form.pseudonym %} diff --git a/castellum/execution/urls.py b/castellum/execution/urls.py index bbe9c6426..e210c6e2a 100644 --- a/castellum/execution/urls.py +++ b/castellum/execution/urls.py @@ -22,7 +22,7 @@ from django.conf import settings from django.urls import path from .api import APIAttributesView -from .api import APIDomainsView +from .api import APICodingListsView from .api import APIPseudonymsView from .api import APIResolveView from .api import APIValidateView @@ -74,7 +74,7 @@ urlpatterns = [ name='participation-pseudonyms', ), path( - '//pseudonyms//', + '//pseudonyms//', ParticipationPseudonymView.as_view(), name='participation-pseudonym', ), @@ -127,15 +127,18 @@ urlpatterns = [ if settings.CASTELLUM_API_ENABLED: urlpatterns += [ - path('api/studies//domains/', APIDomainsView.as_view()), - path('api/studies//domains//', APIPseudonymsView.as_view()), - path('api/studies//domains///', APIValidateView.as_view()), + path('api/studies//coding-lists/', APICodingListsView.as_view()), + path('api/studies//coding-lists//', APIPseudonymsView.as_view()), path( - 'api/studies//domains///attributes/', + 'api/studies//coding-lists///', + APIValidateView.as_view(), + ), + path( + 'api/studies//coding-lists///attributes/', APIAttributesView.as_view(), ), path( - 'api/studies//domains////', + 'api/studies//coding-lists////', # noqa APIResolveView.as_view(), ), ] diff --git a/castellum/execution/views.py b/castellum/execution/views.py index c2a6b3f07..96516344f 100644 --- a/castellum/execution/views.py +++ b/castellum/execution/views.py @@ -46,11 +46,11 @@ from castellum.appointments.mixins import BaseCalendarView from castellum.appointments.mixins import SchedulerFetchStudyMixin from castellum.castellum_auth.mixins import PermissionRequiredMixin from castellum.contacts.mixins import BaseContactUpdateView -from castellum.pseudonyms.forms import DomainForm +from castellum.pseudonyms.forms import CodingListForm from castellum.pseudonyms.forms import PseudonymForm from castellum.pseudonyms.helpers import get_pseudonym from castellum.pseudonyms.helpers import get_subject -from castellum.pseudonyms.models import Domain +from castellum.pseudonyms.models import CodingList from castellum.recruitment.attribute_exporters import get_exporter from castellum.recruitment.mixins import BaseAttributesUpdateView from castellum.recruitment.mixins import ParticipationMixin @@ -112,10 +112,10 @@ class ExportView(StudyMixin, PermissionRequiredMixin, DetailView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['form'] = DomainForm(context=self.study) + context['form'] = CodingListForm(context=self.study) return context - def export(self, domain): + def export(self, coding_list): exporter = get_exporter() descriptions = self.study.exportable_attributes.all() response = HttpResponse(content_type='application/zip') @@ -127,7 +127,7 @@ class ExportView(StudyMixin, PermissionRequiredMixin, DetailView): .filter(status=Participation.INVITED) .select_related('subject') ): - pseudonym = get_pseudonym(participation.subject, domain.key) + pseudonym = get_pseudonym(participation.subject, coding_list.key) subjects.append((pseudonym, participation.subject)) filename = exporter.get_schema_filename() @@ -152,12 +152,14 @@ class ExportView(StudyMixin, PermissionRequiredMixin, DetailView): self.study.get_exportable_attributes_max_privacy_level(), )): raise PermissionDenied - if 'domain' in self.request.GET: - domain = get_object_or_404(self.study.domains.all(), key=self.request.GET['domain']) - return self.export(domain) - elif self.study.domains.count() == 1: - domain = self.study.domains.get() - return self.export(domain) + if 'coding_list' in self.request.GET: + coding_list = get_object_or_404( + self.study.coding_lists.all(), key=self.request.GET['coding_list'] + ) + return self.export(coding_list) + elif self.study.coding_lists.count() == 1: + coding_list = self.study.coding_lists.get() + return self.export(coding_list) else: return super().get(request, **kwargs) @@ -178,9 +180,9 @@ class ParticipationDetailView(ParticipationDetailMixin, DetailView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['domains'] = [ - *self.study.domains.all(), - *self.study.general_domains.filter(managers=self.request.user), + context['coding_lists'] = [ + *self.study.coding_lists.all(), + *self.study.general_coding_lists.filter(managers=self.request.user), ] return context @@ -205,7 +207,7 @@ class ParticipationPseudonymsView(ParticipationDetailMixin, DetailView): context['subject'] = None context['participation'] = None - context['domains'] = self.study.domains.all() + context['coding_lists'] = self.study.coding_lists.all() return context @@ -221,15 +223,15 @@ class ParticipationPseudonymView(ParticipationDetailMixin, View): ) def get(self, request, *args, **kwargs): - qs = Domain.objects.filter( - models.Q(pk__in=self.study.domains.all()) - | models.Q(pk__in=self.study.general_domains.filter(managers=request.user)) + qs = CodingList.objects.filter( + models.Q(pk__in=self.study.coding_lists.all()) + | models.Q(pk__in=self.study.general_coding_lists.filter(managers=request.user)) ) - domain = get_object_or_404(qs, key=kwargs['domain']) - pseudonym = get_pseudonym(self.subject, domain.key) + coding_list = get_object_or_404(qs, key=kwargs['coding_list']) + pseudonym = get_pseudonym(self.subject, coding_list.key) - monitoring_logger.info('Pseudonym access: domain {} by {}'.format( - domain.key, self.request.user.pk + monitoring_logger.info('Pseudonym access: coding_list {} by {}'.format( + coding_list.key, self.request.user.pk )) return HttpResponse(pseudonym) @@ -326,7 +328,7 @@ class ResolveView(StudyMixin, PermissionRequiredMixin, FormView): def form_valid(self, form): try: - subject = get_subject(form.cleaned_data['domain'], form.cleaned_data['pseudonym']) + subject = get_subject(form.cleaned_data['coding_list'], form.cleaned_data['pseudonym']) participation = Participation.objects.filter( study=self.study, subject=subject, diff --git a/castellum/pseudonyms/admin.py b/castellum/pseudonyms/admin.py index 7da5b3139..3d4203ca7 100644 --- a/castellum/pseudonyms/admin.py +++ b/castellum/pseudonyms/admin.py @@ -23,10 +23,10 @@ from django.contrib import admin from . import models -class DomainAdmin(admin.ModelAdmin): +class CodingListAdmin(admin.ModelAdmin): readonly_fields = ['key'] search_fields = ['name', 'key'] -admin.site.register(models.Domain, DomainAdmin) +admin.site.register(models.CodingList, CodingListAdmin) admin.site.register(models.Pseudonym) diff --git a/castellum/pseudonyms/forms.py b/castellum/pseudonyms/forms.py index 800e24067..5f9c9441e 100644 --- a/castellum/pseudonyms/forms.py +++ b/castellum/pseudonyms/forms.py @@ -33,20 +33,20 @@ class PseudonymField(forms.CharField): raise ValidationError(_('Invalid pseudonym'), code='invalid') -class DomainForm(forms.Form): - domain = forms.ChoiceField(label=_('Domain')) +class CodingListForm(forms.Form): + coding_list = forms.ChoiceField(label=_('Coding list')) def __init__(self, *args, context, **kwargs): super().__init__(*args, **kwargs) - self.fields['domain'].choices = [(d.key, str(d)) for d in context.domains.all()] - if self.fields['domain'].choices: - self.fields['domain'].initial = self.fields['domain'].choices[0][0] + self.fields['coding_list'].choices = [(d.key, str(d)) for d in context.coding_lists.all()] + if self.fields['coding_list'].choices: + self.fields['coding_list'].initial = self.fields['coding_list'].choices[0][0] else: - self.fields['domain'].disabled = True + self.fields['coding_list'].disabled = True self.cleaned_data = {} - self.add_error(None, _('All pseudonym domains have been deleted.')) + self.add_error(None, _('All coding lists have been deleted.')) -class PseudonymForm(DomainForm): +class PseudonymForm(CodingListForm): pseudonym = PseudonymField(label=_('Pseudonym')) diff --git a/castellum/pseudonyms/helpers.py b/castellum/pseudonyms/helpers.py index e79527fe5..792a31628 100644 --- a/castellum/pseudonyms/helpers.py +++ b/castellum/pseudonyms/helpers.py @@ -22,7 +22,7 @@ from django.db.utils import IntegrityError from castellum.subjects.models import Subject -from .models import Domain +from .models import CodingList from .models import Pseudonym @@ -35,27 +35,30 @@ def attempt(fn, attempts=10): return attempt(fn, attempts=attempts - 1) -def get_subject(domain_key, pseudonym): - return Subject.objects.get(pseudonym__domain__key=domain_key, pseudonym__pseudonym=pseudonym) +def get_subject(coding_list_key, pseudonym): + return Subject.objects.get( + pseudonym__coding_list__key=coding_list_key, + pseudonym__pseudonym=pseudonym, + ) -def get_pseudonym(subject, target_domain_key): - target_domain = Domain.objects.get(key=target_domain_key) +def get_pseudonym(subject, target_coding_list_key): + target_coding_list = CodingList.objects.get(key=target_coding_list_key) target, __ = attempt( - lambda: Pseudonym.objects.get_or_create(subject=subject, domain=target_domain) + lambda: Pseudonym.objects.get_or_create(subject=subject, coding_list=target_coding_list) ) return target.pseudonym -def get_pseudonym_if_exists(subject, target_domain_key): +def get_pseudonym_if_exists(subject, target_coding_list_key): # You should use ``get_pseudonym()`` in most cases as it does not # leak whether a pseudony exists. - target_domain = Domain.objects.get(key=target_domain_key) - target = Pseudonym.objects.filter(subject=subject, domain=target_domain).first() + target_coding_list = CodingList.objects.get(key=target_coding_list_key) + target = Pseudonym.objects.filter(subject=subject, coding_list=target_coding_list).first() return target.pseudonym if target else None -def delete_pseudonym(domain_key, pseudonym): - p = Pseudonym.objects.get(domain__key=domain_key, pseudonym=pseudonym) +def delete_pseudonym(coding_list_key, pseudonym): + p = Pseudonym.objects.get(coding_list__key=coding_list_key, pseudonym=pseudonym) p.subject = None p.save() diff --git a/castellum/pseudonyms/models.py b/castellum/pseudonyms/models.py index 0880304b4..4423fcdd7 100644 --- a/castellum/pseudonyms/models.py +++ b/castellum/pseudonyms/models.py @@ -29,7 +29,7 @@ from castellum.subjects.models import Subject from castellum.utils import uuid_str -class Domain(models.Model): +class CodingList(models.Model): key = models.CharField(max_length=64, default=uuid_str, unique=True, editable=False) name = models.CharField(max_length=64, blank=True) bits = models.PositiveIntegerField(default=40) @@ -40,8 +40,8 @@ class Domain(models.Model): managers = models.ManyToManyField( User, - verbose_name=_('Users who can access this domain'), - related_name='general_domains', + verbose_name=_('Users who can access this coding list'), + related_name='general_coding_lists', blank=True, ) exportable_attributes = models.ManyToManyField( @@ -65,13 +65,13 @@ class Domain(models.Model): class Pseudonym(models.Model): subject = models.ForeignKey(Subject, on_delete=models.SET_NULL, blank=True, null=True) - domain = models.ForeignKey(Domain, on_delete=models.CASCADE) + coding_list = models.ForeignKey(CodingList, on_delete=models.CASCADE) pseudonym = models.CharField(max_length=64, default=None) class Meta: unique_together = [ - ['domain', 'pseudonym'], - ['domain', 'subject'], + ['coding_list', 'pseudonym'], + ['coding_list', 'subject'], ] def __str__(self): @@ -79,5 +79,5 @@ class Pseudonym(models.Model): def save(self, *args, **kwargs): if not self.pseudonym: - self.pseudonym = settings.CASTELLUM_PSEUDONYM_GENERATE(self.domain.bits) + self.pseudonym = settings.CASTELLUM_PSEUDONYM_GENERATE(self.coding_list.bits) super().save(*args, **kwargs) diff --git a/castellum/recruitment/management/commands/create_demo_content.py b/castellum/recruitment/management/commands/create_demo_content.py index 199b1055b..fbba8b637 100644 --- a/castellum/recruitment/management/commands/create_demo_content.py +++ b/castellum/recruitment/management/commands/create_demo_content.py @@ -33,7 +33,7 @@ from castellum.contacts.models import Address from castellum.contacts.models import Contact from castellum.contacts.models import Street from castellum.contacts.models import phonetic -from castellum.pseudonyms.models import Domain +from castellum.pseudonyms.models import CodingList from castellum.recruitment.models import AttributeChoice from castellum.studies.models import Study from castellum.studies.models import StudySession @@ -219,8 +219,8 @@ def generate_studies(count, stdout): continue i += 1 - Domain.objects.create( - bits=settings.CASTELLUM_STUDY_DOMAIN_BITS, + CodingList.objects.create( + bits=settings.CASTELLUM_STUDY_PSEUDONYM_BITS, context=study, ) diff --git a/castellum/recruitment/signals.py b/castellum/recruitment/signals.py index 88c9bc68a..76eaef1ba 100644 --- a/castellum/recruitment/signals.py +++ b/castellum/recruitment/signals.py @@ -28,7 +28,7 @@ def delete_participation_pseudonyms(sender, instance, using, **kwargs): if sender is Participation: Pseudonym.objects.filter( - domain__in=instance.study.domains.all(), subject=instance.subject + coding_list__in=instance.study.coding_lists.all(), subject=instance.subject ).update(subject=None) diff --git a/castellum/settings/default/__init__.py b/castellum/settings/default/__init__.py index 1b69830fa..1259494fc 100644 --- a/castellum/settings/default/__init__.py +++ b/castellum/settings/default/__init__.py @@ -157,18 +157,18 @@ CASTELLUM_ATTRIBUTE_EXPORTER = 'castellum.recruitment.attribute_exporters.JSONEx # /studies/api/studies/?status= # basic metadata of a study # /studies/api/studies// -# list of study domains: -# /execution/api/studies//domains/ -# list of pseudonyms in a domain: -# /execution/api/studies//domains// +# list of study coding lists: +# /execution/api/studies//coding-lists/ +# list of pseudonyms in a coding list: +# /execution/api/studies//coding-lists// # validate a single pseudonym -# /execution/api/studies//domains/// +# /execution/api/studies//coding-lists/// # get exportable attributes: -# /execution/api/studies//domains///attributes/ -# resolve pseudonym to a target domain (can also be a general domain): -# /execution/api/studies//domains//// -# get exportable attributes for general domains -# /subjects/api/subjects///attributes/ +# /execution/api/studies//coding-lists///attributes/ +# resolve pseudonym to a target coding list (can also be a general coding list): +# /execution/api/studies//coding-lists//// +# get exportable attributes for general coding lists +# /subjects/api/subjects///attributes/ CASTELLUM_API_ENABLED = False # If you want to allow external services to automatically add subjects diff --git a/castellum/studies/apps.py b/castellum/studies/apps.py index 4d9c19b52..118a11f2a 100644 --- a/castellum/studies/apps.py +++ b/castellum/studies/apps.py @@ -29,7 +29,7 @@ class StudiesConfig(AppConfig): name = 'castellum.studies' def ready(self): - pre_delete.connect(signals.delete_study_domain) + pre_delete.connect(signals.delete_study_coding_list) - post_save.connect(signals.create_session_domain) - pre_delete.connect(signals.delete_session_domain) + post_save.connect(signals.create_session_coding_list) + pre_delete.connect(signals.delete_session_coding_list) diff --git a/castellum/studies/models.py b/castellum/studies/models.py index a784afc61..193a71be9 100644 --- a/castellum/studies/models.py +++ b/castellum/studies/models.py @@ -35,7 +35,7 @@ from parler.models import TranslatableModel from parler.models import TranslatedFields from castellum.castellum_auth.models import User -from castellum.pseudonyms.models import Domain +from castellum.pseudonyms.models import CodingList from castellum.utils.fields import ColorField from castellum.utils.fields import DateField from castellum.utils.fields import PhoneNumberField @@ -116,10 +116,10 @@ class Study(models.Model): snapshot = models.TextField(_('Snapshot'), blank=True, editable=False) members = models.ManyToManyField(User, through='StudyMembership') - domains = GenericRelation(Domain) - general_domains = models.ManyToManyField( - Domain, - verbose_name=_('General pseudonym domains'), + coding_lists = GenericRelation(CodingList) + general_coding_lists = models.ManyToManyField( + CodingList, + verbose_name=_('General coding lists'), blank=True, limit_choices_to={'object_id': None}, ) @@ -413,7 +413,7 @@ class StudySession(models.Model): duration = models.PositiveIntegerField(_('Duration of a session in minutes')) type = models.ManyToManyField(StudyType, verbose_name=_('Type'), blank=True) resources = models.ManyToManyField(Resource, verbose_name=_('Resources'), blank=True) - domains = GenericRelation(Domain) + coding_lists = GenericRelation(CodingList) reminder_text = models.TextField(_('Additional text for reminder emails'), blank=True) schedule_id = models.CharField( _('External schedule ID'), max_length=64, blank=True, null=True, unique=True @@ -424,10 +424,10 @@ class StudySession(models.Model): return '{} - {}'.format(self.study, self.name) @property - def domain(self): - domain = self.domains.first() - if domain: - return domain.key + def coding_list_key(self): + coding_list = self.coding_lists.first() + if coding_list: + return coding_list.key class StudyTag(models.Model): diff --git a/castellum/studies/signals.py b/castellum/studies/signals.py index 108685d29..737ee2bf0 100644 --- a/castellum/studies/signals.py +++ b/castellum/studies/signals.py @@ -21,26 +21,26 @@ from django.conf import settings -def delete_study_domain(sender, instance, using, **kwargs): +def delete_study_coding_list(sender, instance, using, **kwargs): from castellum.studies.models import Study if sender is Study: - instance.domains.all().delete() + instance.coding_lists.all().delete() -def create_session_domain(sender, instance, using, **kwargs): - from castellum.pseudonyms.models import Domain +def create_session_coding_list(sender, instance, using, **kwargs): + from castellum.pseudonyms.models import CodingList from castellum.studies.models import StudySession - if sender is StudySession and not instance.domains.exists(): - Domain.objects.create( + if sender is StudySession and not instance.coding_lists.exists(): + CodingList.objects.create( bits=settings.CASTELLUM_SESSION_DOMAIN_BITS, context=instance, ) -def delete_session_domain(sender, instance, using, **kwargs): +def delete_session_coding_list(sender, instance, using, **kwargs): from castellum.studies.models import StudySession if sender is StudySession: - instance.domains.all().delete() + instance.coding_lists.all().delete() diff --git a/castellum/studies/templates/studies/domain_confirm_delete.html b/castellum/studies/templates/studies/coding_list_confirm_delete.html similarity index 62% rename from castellum/studies/templates/studies/domain_confirm_delete.html rename to castellum/studies/templates/studies/coding_list_confirm_delete.html index 6ac74f557..c74ccca1a 100644 --- a/castellum/studies/templates/studies/domain_confirm_delete.html +++ b/castellum/studies/templates/studies/coding_list_confirm_delete.html @@ -1,21 +1,21 @@ -{% extends "studies/study_domains_base.html" %} +{% extends "studies/study_coding_lists_base.html" %} {% load i18n auth django_bootstrap5 utils %} -{% block title %}{% translate "Delete" %} · {% translate "Study domains" %} · {{ block.super }}{% endblock %} +{% block title %}{% translate "Delete" %} · {% translate "Study coding lists" %} · {{ block.super }}{% endblock %} {% block content %}
{% csrf_token %} -

{% blocktranslate %}Are you sure you want to permanently delete the domain "{{ object }}" and all related pseudonyms?{% endblocktranslate %}

+

{% blocktranslate %}Are you sure you want to permanently delete the coding list "{{ object }}" and all related pseudonyms?{% endblocktranslate %}

{% translate "Once a pseudonym is deleted, it is no longer possible to find the corresponding contact information. Note, however, that additional steps might be necessary for full anonymization of scientific data (e.g. image data)." %}

-

{% translate "The date when a domain should be deleted is usually defined in the ethics application and the study consent form." %}

+

{% translate "The date when a coding list should be deleted is usually defined in the ethics application and the study consent form." %}

- {% translate 'Cancel' %} + {% translate 'Cancel' %}
diff --git a/castellum/studies/templates/studies/study_base.html b/castellum/studies/templates/studies/study_base.html index c04a10b25..f77d9d20c 100644 --- a/castellum/studies/templates/studies/study_base.html +++ b/castellum/studies/templates/studies/study_base.html @@ -128,7 +128,7 @@ {% translate 'Member management' %}