diff --git a/Campaign/admin.py b/Campaign/admin.py index 5e39b963..fce8c9db 100644 --- a/Campaign/admin.py +++ b/Campaign/admin.py @@ -11,14 +11,18 @@ from Campaign.models import CampaignTeam from Campaign.models import TrustedUser from EvalData.admin import BaseMetadataAdmin - +from django.http import HttpResponse +import csv +import zipfile +from io import StringIO +import importlib class DropdownFilter(AllValuesFieldListFilter): """ Experimental dropdown filter. """ - template = 'Campaign/filter_select.html' + template = "Campaign/filter_select.html" class CampaignTeamAdmin(BaseMetadataAdmin): @@ -27,33 +31,33 @@ class CampaignTeamAdmin(BaseMetadataAdmin): """ list_display = [ - 'teamName', - 'owner', - 'teamMembers', - 'requiredAnnotations', - 'requiredHours', - 'completionStatus', + "teamName", + "owner", + "teamMembers", + "requiredAnnotations", + "requiredHours", + "completionStatus", ] + BaseMetadataAdmin.list_display # type: ignore - list_filter = ['owner'] + BaseMetadataAdmin.list_filter # type: ignore + list_filter = ["owner"] + BaseMetadataAdmin.list_filter # type: ignore search_fields = [ - 'teamName', - 'owner__username', - 'owner__first_name', - 'owner__last_name', + "teamName", + "owner__username", + "owner__first_name", + "owner__last_name", ] + BaseMetadataAdmin.search_fields # type: ignore - filter_horizontal = ['members'] + filter_horizontal = ["members"] fieldsets = ( ( None, { - 'fields': ( - 'teamName', - 'owner', - 'members', - 'requiredAnnotations', - 'requiredHours', + "fields": ( + "teamName", + "owner", + "members", + "requiredAnnotations", + "requiredHours", ) }, ), @@ -66,22 +70,22 @@ class CampaignDataAdmin(BaseMetadataAdmin): """ list_display = [ - 'dataName', - 'market', - 'metadata', - 'dataValid', - 'dataReady', + "dataName", + "market", + "metadata", + "dataValid", + "dataReady", ] + BaseMetadataAdmin.list_display # type: ignore list_filter = [ - 'dataValid', - 'dataReady', + "dataValid", + "dataReady", ] + BaseMetadataAdmin.list_filter # type: ignore search_fields = [ # nothing model specific ] + BaseMetadataAdmin.search_fields # type: ignore fieldsets = ( - (None, {'fields': ('dataFile', 'market', 'metadata')}), + (None, {"fields": ("dataFile", "market", "metadata")}), ) + BaseMetadataAdmin.fieldsets # type: ignore @@ -90,7 +94,8 @@ class CampaignAdmin(BaseMetadataAdmin): Model admin for Campaign instances. """ - list_display = ['campaignName'] + BaseMetadataAdmin.list_display + ['id'] # type: ignore + list_display = ["campaignName"] + \ + BaseMetadataAdmin.list_display + ["id"] # type: ignore list_filter = [ # nothing model specific ] + BaseMetadataAdmin.list_filter # type: ignore @@ -98,39 +103,89 @@ class CampaignAdmin(BaseMetadataAdmin): # nothing model specific ] + BaseMetadataAdmin.search_fields # type: ignore - filter_horizontal = ['batches'] + filter_horizontal = ["batches"] fieldsets = ( ( None, { - 'fields': ( - 'campaignName', - 'packageFile', - 'teams', - 'batches', - 'campaignOptions', + "fields": ( + "campaignName", + "packageFile", + "teams", + "batches", + "campaignOptions", ) }, ), ) + BaseMetadataAdmin.fieldsets # type: ignore + actions = ["export_results"] + + def _retrieve_csv(self, current_campaign): + # Get the task type corresponding to the campaign + qs_name = current_campaign.get_campaign_type().lower() + qs_attr = "evaldata_{0}_campaign".format(qs_name) + qs_obj = getattr(current_campaign, qs_attr, None) + cls = type(qs_obj.all()[0]) + cls_name = cls.__name__ + cls_name = cls_name.replace("Task", "Result") + module = importlib.import_module(cls.__module__) + cls = getattr(module, cls_name) + + # Now get the content + f = StringIO() + writer = csv.writer(f) + csv_content = cls.get_system_data(current_campaign.id, extended_csv=True) + for r in csv_content: + writer.writerow(r) + + f.seek(0) + return f + + + def export_results(self, request, queryset): + if len(queryset) == 1: + current_campaign = queryset[0] + csv_content = self._retrieve_csv(current_campaign) + filename = f"results_{current_campaign.campaignName}.csv" + response = HttpResponse(csv_content, content_type="text/csv") + response["Content-Disposition"] = f"attachment; filename={filename}" + else: + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename="campaign_results.zip"' + + # Create a zip file with selected objects + with zipfile.ZipFile(response, 'w') as zipf: + for current_campaign in queryset: + + csv_content = self._retrieve_csv(current_campaign) + # Add objects to the zip file, customize as per your model's data + # For example, you can add an object's name and description to a text file in the zip + filename = f"results_{current_campaign.campaignName}.csv" + zipf.writestr(filename, csv_content.getvalue()) + return response + + export_results.short_description = "Download results" + + + class TrustedUserAdmin(admin.ModelAdmin): """ Model admin for Campaign instances. """ - list_display = ['user', 'campaign'] + list_display = ["user", "campaign"] list_filter = [ - ('campaign__campaignName', DropdownFilter), - # 'campaign' + ("campaign__campaignName", DropdownFilter), + # "campaign" ] search_fields = [ # type: ignore # nothing model specific ] - fieldsets = ((None, {'fields': ('user', 'campaign')}),) + fieldsets = ((None, {"fields": ("user", "campaign")}),) admin.site.register(CampaignTeam, CampaignTeamAdmin) diff --git a/Campaign/views.py b/Campaign/views.py index 2f326f5c..a5770b0a 100644 --- a/Campaign/views.py +++ b/Campaign/views.py @@ -264,10 +264,15 @@ def campaign_status_esa(campaign) -> str: """ out_str += f"

{campaign.campaignName}

\n" out_str += "\n" - out_str += "" + "".join( - f"" for x in ["Username", "Progress", "First Modified", "Last Modified", "Time (Last-First)", "Time (Real)"] - ) + "\n" - + out_str += """ + + + + + + +\n +""" for team in campaign.teams.all(): for user in team.members.all(): if user.is_staff: @@ -298,6 +303,7 @@ def campaign_status_esa(campaign) -> str: _data = DirectAssessmentDocumentResult.objects.filter( createdBy=user, completed=True, task__campaign=campaign.id ) + _data_uniq_len = len({item.id for item in _data}) # If no data, show 0 progress or show that no task is assigned if not _data: @@ -331,11 +337,11 @@ def campaign_status_esa(campaign) -> str: continue total_count = task.items.count() - if total_count == len(_data): + if total_count == _data_uniq_len: out_str += f"" else: out_str += f"" - out_str += f"" + out_str += f"" first_modified = min([x.start_time for x in _data]) last_modified = max([x.end_time for x in _data]) @@ -351,15 +357,9 @@ def campaign_status_esa(campaign) -> str: annotation_time_upper = f'{int(floor(annotation_time_upper / 3600)):0>2d}h {int(floor((annotation_time_upper % 3600) / 60)):0>2d}m' out_str += f"" - times = collections.defaultdict(list) - for item in _data: - times[(item.item.documentID, item.item.targetID)].append((item.start_time, item.end_time)) - times = [ - (min([x[0] for x in doc_v]), max([x[1] for x in doc_v])) - for doc, doc_v in times.items() - ] - - annotation_time = sum([b-a for a, b in times]) + # consider time that's in any action within 10 minutes + times = sorted([item.start_time for item in _data] + [item.end_time for item in _data]) + annotation_time = sum([b-a for a, b in zip(times, times[1:]) if (b-a) < 10*60]) annotation_time = f'{int(floor(annotation_time / 3600)):0>2d}h {int(floor((annotation_time % 3600) / 60)):0>2d}m' out_str += f"" diff --git a/EvalData/models/data_assessment.py b/EvalData/models/data_assessment.py index 3de5292b..c10df8eb 100644 --- a/EvalData/models/data_assessment.py +++ b/EvalData/models/data_assessment.py @@ -890,18 +890,15 @@ def get_system_data( for result in qs.values_list(*attributes_to_extract): user_id = result[0] - _fixed_ids = result[1].replace('Transformer+R2L', 'Transformer_R2L') - _fixed_ids = _fixed_ids.replace('R2L+Back', 'R2L_Back') - if expand_multi_sys: - system_ids = _fixed_ids.split('+') + system_ids = result[1].split('+') for system_id in system_ids: data = (user_id,) + (system_id,) + result[2:] system_data.append(data) else: - system_id = _fixed_ids + system_id = result[1] data = (user_id,) + (system_id,) + result[2:] system_data.append(data) diff --git a/EvalData/models/direct_assessment.py b/EvalData/models/direct_assessment.py index 801d54d7..c52e4b34 100644 --- a/EvalData/models/direct_assessment.py +++ b/EvalData/models/direct_assessment.py @@ -751,18 +751,15 @@ def get_system_data( for result in qs.values_list(*attributes_to_extract): user_id = result[0] - _fixed_ids = result[1].replace('Transformer+R2L', 'Transformer_R2L') - _fixed_ids = _fixed_ids.replace('R2L+Back', 'R2L_Back') - if expand_multi_sys: - system_ids = _fixed_ids.split('+') + system_ids = result[1].split('+') for system_id in system_ids: data = (user_id,) + (system_id,) + result[2:] system_data.append(data) else: - system_id = _fixed_ids + system_id = result[1] data = (user_id,) + (system_id,) + result[2:] system_data.append(data) diff --git a/EvalData/models/direct_assessment_context.py b/EvalData/models/direct_assessment_context.py index 37666068..aa76c6d2 100644 --- a/EvalData/models/direct_assessment_context.py +++ b/EvalData/models/direct_assessment_context.py @@ -836,11 +836,8 @@ def get_system_data( for result in qs.values_list(*attributes_to_extract): user_id = result[0] - _fixed_ids = result[1].replace('Transformer+R2L', 'Transformer_R2L') - _fixed_ids = _fixed_ids.replace('R2L+Back', 'R2L_Back') - if expand_multi_sys: - system_ids = _fixed_ids.split('+') + system_ids = result[1].split('+') for system_id in system_ids: data = (user_id,) + (system_id,) + result[2:] diff --git a/EvalData/models/direct_assessment_document.py b/EvalData/models/direct_assessment_document.py index 2a3a56dd..14d06d88 100644 --- a/EvalData/models/direct_assessment_document.py +++ b/EvalData/models/direct_assessment_document.py @@ -599,35 +599,27 @@ def get_hit_status_for_user(cls, user): @classmethod def get_time_for_user(cls, user): results = cls.objects.filter(createdBy=user, activated=False, completed=True) + if not results: + return seconds_to_timedelta(0) + campaign_opts = result.task.campaign.campaignOptions.lower().split(";") is_esa_or_mqm = any( [ - "esa" in result.task.campaign.campaignOptions.lower().split(";") - or "mqm" in result.task.campaign.campaignOptions.lower().split(";") + "esa" in campaign_opts or "mqm" in campaign_opts for result in results ] ) if is_esa_or_mqm: - # for ESA or MQM, do minimum and maximum from each doc - import collections - - timestamps = collections.defaultdict(list) - for result in results: - timestamps[ - result.item.documentID + " ||| " + result.item.targetID - ].append((result.start_time, result.end_time)) - - # timestamps are document-level now, but that does not change anything later on - timestamps = [ - (min([x[0] for x in doc_v]), max([x[1] for x in doc_v])) - for doc, doc_v in timestamps.items() - ] + # consider time that's in any action within 10 minutes + times = sorted([item.start_time for item in results] + [item.end_time for item in results]) + annotation_time = sum([b-a for a, b in zip(times, times[1:]) if (b-a) < 10*60]) + return seconds_to_timedelta(annotation_time) else: timestamps = [] for result in results: timestamps.append((result.start_time, result.end_time)) - return seconds_to_timedelta(_compute_user_total_annotation_time(timestamps)) + return seconds_to_timedelta(_compute_user_total_annotation_time(timestamps)) @classmethod def get_system_annotations(cls): @@ -941,9 +933,10 @@ def get_system_data( qs = cls.objects.filter(completed=True, item__itemType__in=item_types) # If campaign ID is given, only return results for this campaign. - campaign_name = None if campaign_id: qs = qs.filter(task__campaign__id=campaign_id) + if not qs: + return [] campaign_opts = str(qs.first().task.campaign.campaignOptions) if not include_inactive: @@ -988,18 +981,15 @@ def get_system_data( for result in qs.values_list(*attributes_to_extract): user_id = result[0] - _fixed_ids = result[1].replace('Transformer+R2L', 'Transformer_R2L') - _fixed_ids = _fixed_ids.replace('R2L+Back', 'R2L_Back') - if expand_multi_sys: - system_ids = _fixed_ids.split('+') + system_ids = result[1].split('+') for system_id in system_ids: data = (user_id,) + (system_id,) + result[2:] system_data.append(data) else: - system_id = _fixed_ids + system_id = result[1] data = (user_id,) + (system_id,) + result[2:] system_data.append(data) diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 index dfd799b6..84cdf5d4 --- a/manage.py +++ b/manage.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys
{x}
UsernameProgressFirst ModifiedLast ModifiedTime (Coarse ❔)Time (Real ❔)
{user.username} ✅{user.username} 🛠️{len(_data)}/{total_count} ({len(_data) / total_count:.0%}){_data_uniq_len}/{total_count} ({_data_uniq_len / total_count:.0%}){annotation_time_upper}{annotation_time}