diff --git a/castellum/appointments/forms.py b/castellum/appointments/forms.py index 59a63cde6be6fa067d1b3a7ba0fb83d48c681322..385cfc6368be2d80c94ce640ecef41ca7e424064 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 6bc0884067b375e029ba2e0fb87733d5b1eb6c04..4a7d2d823ba56bdc050118c12518961d26fcda37 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 921a0a6e3bc138bf4c6551f496afbcf7a475bdbe..fc63ab1322a89842396e22a5cb4b01fb3447857a 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 b13b784ff745f8647472fa295801d834fa6c0409..13a220f1ef20ec2eb2182bf5653d17f967f9498a 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 c990d2c53dbc5849e67664fb482a10350f810083..a915625f14e95ceeebcaa911050d12903d9fc7bd 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,11 @@ 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()] + key = 'domains' if '/domains/' in request.path else 'coding_lists' + return JsonResponse({key: data}) class APIValidateView(BaseAPIView): @@ -60,10 +61,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 +77,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 +95,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 +107,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 +128,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 a48b3ae97becc86a0af571aebc7a7afb101dfe93..d1d5406cda20441534319b4ab0dd52f3f37dc363 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 60a9c8997f3bbfd8283ed822a6bea724bd49fe41..efa27772667ed192a811f71a9465665fb2363922 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 d1ff65109e4aa48032b8814381190935ab3b6ceb..afd8ea8ed0101a3c25ee31e947ab2ac613083993 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 71b27716d3b12e5ac67c41f02329e67df115ed85..7f7da3f346099071398d87cd97e96c1b7da2ba81 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 bbe9c6426427740c996103c93d80fae3728df600..d904b0eee3acf24525c93a69fa2029672d3d016a 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,34 @@ 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//coding-lists////', # noqa + APIResolveView.as_view(), + ), + + # deprecated aliases + path('api/studies//domains/', APICodingListsView.as_view()), + path('api/studies//domains//', APIPseudonymsView.as_view()), + path( + 'api/studies//domains///', + APIValidateView.as_view(), + ), + path( + 'api/studies//domains///attributes/', APIAttributesView.as_view(), ), path( - 'api/studies//domains////', + 'api/studies//domains////', # noqa APIResolveView.as_view(), ), ] diff --git a/castellum/execution/views.py b/castellum/execution/views.py index c2a6b3f07711956ed39c86149b680e8ea2989c2d..96516344fed68c8c8a9deca651443ca9d20db048 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 7da5b3139d87a3f937205ae42e7c5e90e0220ab9..3d4203ca7776765b508bb7520f41a043a03af949 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 800e2406782c72e3c104b745a608df54348c1303..5f9c9441e997008761a8c8fd9ff1a21ac83abee4 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 e79527fe54a0e632fbb2c6fccfe27698e26deed4..792a316288a5412d1bb4de5119597e48d57670b9 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/migrations/0009_rename_domain_coding_list.py b/castellum/pseudonyms/migrations/0009_rename_domain_coding_list.py new file mode 100644 index 0000000000000000000000000000000000000000..c8b736d86092b56dce12bd0f5c62a2b1f5046c8d --- /dev/null +++ b/castellum/pseudonyms/migrations/0009_rename_domain_coding_list.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.12 on 2022-03-22 16:22 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('subjects', '0037_alter_subject_privacy_level'), + ('pseudonyms', '0008_domain_managers'), + ('studies', '0047_rename_domain_conding_list'), + ] + + operations = [ + migrations.RenameField( + model_name='pseudonym', + old_name='domain', + new_name='coding_list', + ), + migrations.AlterUniqueTogether( + name='pseudonym', + unique_together={('coding_list', 'pseudonym'), ('coding_list', 'subject')}, + ), + migrations.RenameModel('Domain', 'CodingList'), + migrations.AlterField( + model_name='codinglist', + name='managers', + field=models.ManyToManyField(blank=True, related_name='general_coding_lists', to=settings.AUTH_USER_MODEL, verbose_name='Users who can access this coding list'), + ), + ] diff --git a/castellum/pseudonyms/models.py b/castellum/pseudonyms/models.py index 0880304b44ccb3fe899071b709c5588fdb910b1e..4423fcdd74fcdc9f82b303e7dba3dda6b28bb904 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 199b1055b9e32b499d136d02385b46e576199975..fbba8b637dac01ca4fee88b27c2ffb1580f29e70 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 88c9bc68aea4ff6a86a3fcda5b30d93a41d26884..76eaef1ba98d1b8ca12f3f65d1cf78c248536515 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 1b69830fab45906bdc4aae78e42cbeb27eb9ecae..9a74685cb24b6b54386efbda2e1a3c055dcae113 100644 --- a/castellum/settings/default/__init__.py +++ b/castellum/settings/default/__init__.py @@ -126,8 +126,8 @@ CASTELLUM_PSEUDONYM_GENERATE = castellum_pseudonyms.generate # See also https://docs.djangoproject.com/en/stable/ref/forms/fields/#django.forms.Field.clean CASTELLUM_PSEUDONYM_CLEAN = castellum_pseudonyms.clean -CASTELLUM_STUDY_DOMAIN_BITS = 20 -CASTELLUM_SESSION_DOMAIN_BITS = 20 +CASTELLUM_STUDY_PSEUDONYM_BITS = 20 +CASTELLUM_SESSION_PSEUDONYM_BITS = 20 # Path to a cover letter template # Must be in .docx format @@ -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 4d9c19b5218297414d4b2a5977699b7a4f73f930..118a11f2a8cf516f0b2ba89c4fbe3638e138fa2f 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/migrations/0047_rename_domain_conding_list.py b/castellum/studies/migrations/0047_rename_domain_conding_list.py new file mode 100644 index 0000000000000000000000000000000000000000..82f7e99635bee45d648365b7131553eb02085c1f --- /dev/null +++ b/castellum/studies/migrations/0047_rename_domain_conding_list.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2022-03-22 16:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('studies', '0046_rename_study_is_onetime_invitation'), + ] + + operations = [ + migrations.RenameField( + model_name='study', + old_name='general_domains', + new_name='general_coding_lists', + ), + migrations.AlterField( + model_name='study', + name='general_coding_lists', + field=models.ManyToManyField(blank=True, limit_choices_to={'object_id': None}, to='pseudonyms.Domain', verbose_name='General coding lists'), + ), + ] diff --git a/castellum/studies/models.py b/castellum/studies/models.py index a784afc613a73cf36c6a0d87803db77594cf093a..193a71be940cf9009c8a6f3bed64ecf2ce04ef1c 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 108685d2999710ebc7e8dfd2e0fcaa640a79918b..824daec5c71ddf9d3218777f8f24de8809934210 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( - bits=settings.CASTELLUM_SESSION_DOMAIN_BITS, + if sender is StudySession and not instance.coding_lists.exists(): + CodingList.objects.create( + bits=settings.CASTELLUM_SESSION_PSEUDONYM_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 6ac74f557dcc3103593053361a473b5000906bd7..c74ccca1a5551d6343f36a0c60f884e5d0f26dc9 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 c04a10b257ffc0345443ed3df1435faadb38c50c..f77d9d20c205a2d6dc815022eb125ede348a38b0 100644 --- a/castellum/studies/templates/studies/study_base.html +++ b/castellum/studies/templates/studies/study_base.html @@ -128,7 +128,7 @@ {% translate 'Member management' %}