diff --git a/backend/django/core/forms.py b/backend/django/core/forms.py index 415854b4..b87148c3 100644 --- a/backend/django/core/forms.py +++ b/backend/django/core/forms.py @@ -136,6 +136,17 @@ class Meta: description = forms.CharField(required=False) +class ProjectUpdateAdvancedForm(forms.ModelForm): + class Meta: + model = Project + fields = ["allow_coders_view_labels"] + + def __init__(self, *args, **kwargs): + percentage_irr = kwargs.pop('percentage_irr') + super(ProjectUpdateAdvancedForm, self).__init__(*args, **kwargs) + if percentage_irr > 0: + self.fields['allow_coders_view_labels'].widget.attrs['disabled'] = 'disabled' + class LabelForm(forms.ModelForm): class Meta: model = Label @@ -263,6 +274,9 @@ class Meta: required=False, ) + allow_coders_view_labels = forms.BooleanField(initial=False, required=False) + + def clean(self): use_active_learning = self.cleaned_data.get("use_active_learning") use_default_batch_size = self.cleaned_data.get("use_default_batch_size") diff --git a/backend/django/core/migrations/0077_project_allow_coders_view_labels.py b/backend/django/core/migrations/0077_project_allow_coders_view_labels.py new file mode 100644 index 00000000..d4052300 --- /dev/null +++ b/backend/django/core/migrations/0077_project_allow_coders_view_labels.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.9 on 2024-06-12 14:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0076_auto_20230710_1905"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="allow_coders_view_labels", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/django/core/models.py b/backend/django/core/models.py index cf158854..95b01552 100644 --- a/backend/django/core/models.py +++ b/backend/django/core/models.py @@ -44,6 +44,7 @@ class Meta: num_users_irr = models.IntegerField(default=2, validators=[MinValueValidator(2)]) codebook_file = models.TextField(default="") batch_size = models.IntegerField(default=30) + allow_coders_view_labels = models.BooleanField(default=False) umbrella_string = models.TextField(blank=True) """ Advanced options """ # the current options are 'random', 'least confident', 'entropy', and 'margin sampling' diff --git a/backend/django/core/templates/projects/create/create_wizard_advanced.html b/backend/django/core/templates/projects/create/create_wizard_advanced.html index 6139d6d7..65d3547a 100644 --- a/backend/django/core/templates/projects/create/create_wizard_advanced.html +++ b/backend/django/core/templates/projects/create/create_wizard_advanced.html @@ -34,7 +34,7 @@
-

Train and display results for a model: {{ wizard.form.use_model }}

+

{{ wizard.form.use_model }} Train and display results for a model

NOTE: This option must be checked to use Active Learning


@@ -85,16 +85,24 @@
+

Under IRR, a certain percentage of the data is labeled by + multiple coders. The project admin can then examine the + consistency of the labels across different coders. The options + below allow you to set what percentage of the data is coded + multiple times, and how many coders must code data designated + for IRR before it is analyzed. +

-

Under IRR, a certain percentage of the data is labeled by - multiple coders. The project admin can then examine the - consistency of the labels across different coders. The options - below allow you to set what percentage of the data is coded - multiple times, and how many coders must code data designated - for IRR before it is analyzed. -

{{ wizard.form.use_irr }} Use Inter-rater Reliability
+
Percent of batch marked IRR (must be between 0 and 100):{{ wizard.form.percentage_irr }}%
@@ -118,6 +126,30 @@
+ +
+
+ +
+
+
+ {{ wizard.form.allow_coders_view_labels }} Allow coders to view other users' labels in history table +
+ + +
+
@@ -145,10 +177,14 @@
*/ var methods_box = $('div#choose_method_box'); var irr_box = $('div#IRR_options'); +var irr_or_not_box = $('div#choose_irr_or_not'); +var irr_or_not_box_disabled = $('div#choose_irr_or_not_disabled'); var batch_field = $('#choose_batch_size'); var use_model = $('#use_model_div'); var class_choice = $('#classifier_radios'); var al_tab = $('#al_tab'); +var allow_coders_box = $('#allow_coders_view_labels'); +var allow_coders_box_disabled = $('#allow_coders_view_labels_disabled'); if ($('input#id_advanced-use_irr').prop('checked') == true) { irr_box.show(); @@ -208,8 +244,22 @@
$('input#id_advanced-use_irr').change(function() { if ($(this).prop('checked') == true) { irr_box.show(); + allow_coders_box.hide(); + allow_coders_box_disabled.show(); } else { irr_box.hide(); + allow_coders_box.show() + allow_coders_box_disabled.hide(); + } +}); + +$('input#id_advanced-allow_coders_view_labels').change(function() { + if ($(this).prop('checked') == true) { + irr_or_not_box.hide(); + irr_or_not_box_disabled.show(); + } else { + irr_or_not_box.show(); + irr_or_not_box_disabled.hide(); } }); diff --git a/backend/django/core/templates/projects/update/advanced.html b/backend/django/core/templates/projects/update/advanced.html new file mode 100644 index 00000000..99affa88 --- /dev/null +++ b/backend/django/core/templates/projects/update/advanced.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} +{% block page_title %}Update Project Overview{% endblock %} +{% load static %} +{% load render_bundle from webpack_loader %} + +{% block content %} +
+
+
+
+ {{ form.media.css }} +
+ {% csrf_token %} +
+
+ +
+
+
+ {{ form.allow_coders_view_labels }} Allow coders to view other users' labels in history table +
+ + *This option is only enabled if project does not use IRR + +
+
+
+
+ + + +
+
+
+
+
+{% endblock %} diff --git a/backend/django/core/templates/projects/update_landing.html b/backend/django/core/templates/projects/update_landing.html index 6ac47327..727b2732 100644 --- a/backend/django/core/templates/projects/update_landing.html +++ b/backend/django/core/templates/projects/update_landing.html @@ -13,6 +13,7 @@

Update Project

  • {% if project.codebook_file == '' %}Upload Codebook{% else %}Change Codebook{% endif%}
  • Update Project Permissions
  • Update Label Descriptions
  • +
  • Update Advanced Settings
  • Add Project To Group
  • diff --git a/backend/django/core/urls/projects.py b/backend/django/core/urls/projects.py index 34200f92..0f3b895a 100644 --- a/backend/django/core/urls/projects.py +++ b/backend/django/core/urls/projects.py @@ -31,6 +31,11 @@ frontend.ProjectUpdateOverview.as_view(), name="project_update_overview", ), + re_path( + r"^projects/(?P\d+)/update/advanced/$", + frontend.ProjectUpdateAdvanced.as_view(), + name="project_update_advanced", + ), re_path( r"^projects/(?P\d+)/update/data/$", frontend.ProjectUpdateData.as_view(), diff --git a/backend/django/core/views/api_annotate.py b/backend/django/core/views/api_annotate.py index abec22a9..897075af 100644 --- a/backend/django/core/views/api_annotate.py +++ b/backend/django/core/views/api_annotate.py @@ -996,7 +996,10 @@ def get_label_history(request, project_pk): finalized_irr_data = IRRLog.objects.filter(data__irr_ind=False).values_list( "data__pk", flat=True ) - if project_extras.proj_permission_level(project, profile) >= 2: + if ( + project_extras.proj_permission_level(project, profile) >= 2 + or project.allow_coders_view_labels + ): labeled_data = DataLabel.objects.filter( data__project=project_pk, label__in=labels ).exclude(data__in=finalized_irr_data) diff --git a/backend/django/core/views/frontend.py b/backend/django/core/views/frontend.py index 58379198..c79e26ed 100644 --- a/backend/django/core/views/frontend.py +++ b/backend/django/core/views/frontend.py @@ -21,6 +21,7 @@ LabelFormSet, PermissionsFormSet, ProjectUpdateOverviewForm, + ProjectUpdateAdvancedForm, ProjectWizardForm, ) from core.models import ( @@ -324,6 +325,7 @@ def done(self, form_list, form_dict, **kwargs): proj_obj.percentage_irr = advanced_data["percentage_irr"] proj_obj.num_users_irr = advanced_data["num_users_irr"] proj_obj.classifier = advanced_data["classifier"] + proj_obj.allow_coders_view_labels = advanced_data["allow_coders_view_labels"] # use the data dedup choice to set dedup property of metadata fields proj_obj.dedup_on = data.cleaned_data["dedup_on"] @@ -442,6 +444,40 @@ def form_valid(self, form): else: return self.render_to_response(context) +class ProjectUpdateAdvanced(LoginRequiredMixin, UserPassesTestMixin, UpdateView): + model = Project + form_class = ProjectUpdateAdvancedForm + template_name = "projects/update/advanced.html" + permission_denied_message = ( + "You must be an Admin or Project Creator to access the Advanced Project Settings Update page." + ) + raise_exception = True + + def test_func(self): + project = Project.objects.get(pk=self.kwargs["pk"]) + + return ( + project_extras.proj_permission_level(project, self.request.user.profile) + >= 2 + ) + + def get_form_kwargs(self): + # pass the percentage_irr to the form + kwargs = super().get_form_kwargs() + project = self.get_object() + kwargs['percentage_irr'] = project.percentage_irr + return kwargs + + def form_valid(self, form): + context = self.get_context_data() + if form.is_valid(): + with transaction.atomic(): + self.object = form.save() + return redirect(self.get_success_url()) + else: + return self.render_to_response(context) + + class ProjectUpdateData(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model = Project