def create_annotation_set(request,cid): '''create_annotation_set will allow the user to create a custom annotation set (stored as ReportSet) for making annotations. :param cid: the collection id to use ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): users = get_collection_annotators(collection) allowed_annotations = get_allowed_annotations(collection,return_objects=False) # Only allow generation of a set if minimum of 25 reports (for gold standard) count = get_annotation_counts(collection)['total'] if count < GOLD_STANDARD_MIN_ANNOTS: messages.info(request,'''You must have a minimum of 25 annotations to create an annotation set. A collection owner or contributor can annotate random reports to generate this gold standard.''') return HttpResponseRedirect(collection.get_absolute_url()) context = {"users": users, "collection": collection, "allowed_annotations": allowed_annotations} return render(request, "reports/create_annotation_set.html", context) return HttpResponseRedirect(collection.get_absolute_url())
def save_annotation_session(request,cid): '''save_annotation_session will save the annotation set, meaning creation of a session with the queryset. (not in use) :param cid: the collection id to use ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): if request.method == "POST": # What user does the request want to see annotations by? user_id = request.POST.get('user') user = get_user(request,user_id) # What annotation (name and label) do we filter to? selection_keys = [x for x in request.POST.keys() if re.search("^whatisit[||]", x)] selections = [] seen_annotations = [] for selection_key in selection_keys: name,label = selection_key.replace("whatisit||","").split("||") annotation_object = AllowedAnnotation.objects.get(name=name, label=label) # Query to select the reports of interest selection = Annotation.objects.filter(annotator=user, annotation=annotation_object, reports__collection=collection) for annotation in selection: if annotation not in seen_annotations: selections = list(chain(selections,annotation.reports.all())) seen_annotations.append(annotation) request.session['reports'] = selections return annotate_session(request,cid) return HttpResponseRedirect(collection.get_absolute_url())
def annotate_session(request,cid): '''annotate_custom will allow the user to annotate a custom selected set (saved in a session) if not defined, the user is redirected to create one, with a message ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): report_set = request.session.get('reports', None) # Undefined session means the user hasn't created a set yet if report_set == None: messages.info(request, 'You need to create an annotation set first.') return create_annotation_set(request,cid) # The user has finished annotating all reports, time to move on. elif len(report_set) == 0: return create_annotation_set(request,cid) # The user has reports left to annotate else: next_report = report_set.pop(0) request.session['reports'] = report_set return annotate_report(request, rid=next_report.id, report=next_report, next="set") return HttpResponseRedirect(collection.get_absolute_url())
def approve_annotate_permission(request,cid,uid): '''a user must first get approved for annotate permission before being added to an annotation set ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): requester = get_user(request,uid) permission_request = RequestMembership.objects.get(collection=collection, requester=requester) if permission_request.status not in ["APPROVED","DENIED"]: # Update the collection collection.annotators.add(requester) collection.save() # Update the permission request permission_request.status = "APPROVED" permission_request.save() # Send a notification to the user message = """Your request to annotate sets in the collection %s has been approved!""" %(collection.name) notify.send(collection.owner, recipient=requester, verb=message) messages.success(request, 'Annotators approved.') return view_report_collection(request,cid)
def bulk_annotate(request,cid,sid=None): '''bulk annotate will allow a user to apply annotations, in masse, to a collection or set :param cid: the collection id to use :param sid: the report set id, if a subset of reports is being used ''' if sid !=None: report_set = get_report_set(sid) collection = report_set.collection reports = report_set.reports.all() else: collection = get_report_collection(cid) reports = collection.report_set.all() if has_collection_edit_permission(request,collection): # POST indicates creating a bulk annotation if request.method == "POST": # Does the user want to bulk annotate only unlabeled sets? unlabeled_only = request.POST.get('unlabeled_only',None) # What annotations does the user want to do in bulk? selection_keys = [x for x in request.POST.keys() if re.search("^whatisit[||]", x)] selections = [] seen_annotations = [] for selection_key in selection_keys: name = selection_key.replace("whatisit||","") label = request.POST.get(selection_key,"") if label != "": allowed_annotation = AllowedAnnotation.objects.get(name=name, label=label) # If the user doesn't want to include already labeled reports, exclude if unlabeled_only != None: reports = reports.exclude(reports_annotated__annotator=request.user).distinct() for report in reports: # Update user annotation sent to celery to run async update_user_annotation.apply_async(kwargs={'user':request.user.id, 'allowed_annotation':allowed_annotation.id, 'report':report.id}) messages.info(request,"Annotation %s:%s task running for %s reports" %(name,label,reports.count())) return view_report_collection(request,cid) # Otherwise just render the form else: allowed_annotations = get_allowed_annotations(collection,return_objects=False) context = {"collection": collection, "allowed_annotations": allowed_annotations} if sid != None: context['sid'] = sid return render(request, "annotate/bulk_annotate.html", context) return HttpResponseRedirect(collection.get_absolute_url())
def save_annotation_set(request,cid): '''save_annotation_set will save the annotation set, meaning creation of a ReportSet with the queryset. :param cid: the collection id to use ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): if request.method == "POST": # What does the user want to name the set? set_name = request.POST.get('setname').lower().replace(" ","_") # What user should be used as gold standard? gold_standard = request.POST.get('gold_standard') # How many reports in the set? request,N = parse_numeric_input(request=request, value=request.POST.get('N'), default=100, description="the number of reports") # How many tests should be given and passing? request,testing_set = parse_numeric_input(request=request, value=request.POST.get('testing_set'), default=25, description="the number of testing reports") request,testing_set_correct = parse_numeric_input(request=request, value=request.POST.get('testing_set_correct'), default=20, description="the number reports correct to pass") # Required number correct must be less than total if testing_set_correct > testing_set: messages.info(request,"The required number of passing questions must be less than or equal to the number of testing questions.") return create_annotation_set(request,cid) # What users does the request want to see annotations by? user_id = request.POST.get('user') if user_id == 'all': user = [u.id for u in get_collection_users(collection)] else: user = [get_user(request,user_id).id] # What annotation (name and label) do we filter to? selection_keys = [x for x in request.POST.keys() if re.search("^whatisit[||]", x)] generate_annotation_set.apply_async(kwargs={'uid':request.user.id, 'user_ids':user, 'selection_keys':selection_keys, 'cid':collection.id, 'N':N, 'testing_set':testing_set, 'testing_set_correct':testing_set_correct, 'gid':gold_standard, 'set_name':set_name}) messages.info(request,"""Your set is being created. It's status will be sent via notification. Once it is created, you should add annotators to it.""") return redirect('report_collection_details',cid=collection.id)
def summarize_reports(request,cid): collection = get_report_collection(cid) # Get a count of annotations by label in the collection annotation_counts = get_annotation_counts(collection) report_count = Report.objects.filter(collection=collection).count() context = {"collection":collection, "annotation_counts":annotation_counts, "report_count":report_count} return render(request, 'reports/report_collection_summary.html', context)
def create_label(request,cid,lid=None): '''create_label will allow a user to create a new label to be associated with a collection. The user will be able to select from other collection labels :param cid: the collection id to associate the label with (not required, but no url accessible for it) :param lid: if provided on a post, the label will be added to the collection as an allowed annotation ''' collection = get_report_collection(cid) if request.user == collection.owner: if request.method == "POST": # If lid is defined, we are adding an already existing annotation label to collection if lid != None: try: allowed_annotation = AllowedAnnotation.objects.get(id=lid) collection.allowed_annotations.add(allowed_annotation) collection.save() label_name = "%s:%s" %(allowed_annotation.name, allowed_annotation.label) response_text = {"result": "New annotation label %s added successfully." %(label_name)} except BaseException as e: response_text = {"error": "Error retrieving allowed annotation %s, %s" %(lid,e.message)} # Otherwise, the user wants a new one else: name = request.POST.get('annotation_name', None) if name != None: for key in request.POST.keys(): if re.search('annotation_label',key): new_label = request.POST.get(key) allowed_annot,created = AllowedAnnotation.objects.get_or_create(name=name, label=new_label) if created == True: allowed_annot.save() collection.allowed_annotations.add(allowed_annot) messages.info(request,"Label generation successful.") else: messages.info(request,"An annotation name is required.") return view_label(request,cid) else: return view_label(request,cid) return JsonResponse(response_text) else: # Does not have permission, return to collection messages.info(request, "You do not have permission to perform this action.") return HttpResponseRedirect(collection.get_absolute_url())
def save_collection_markup(request,cid): collection = get_report_collection(cid) edit_permission = has_collection_edit_permission(request,collection) if request.method == "POST": if edit_permission: markup = request.POST.get('markup',None) if markup: collection.markup = markup collection.save() messages.success(request, 'Collection markup saved.') return view_report_collection(request,cid)
def request_annotate_permission(request,cid): '''request_annotate_permission will allow a user to request addition to a collection (as an annotator) via a RequestMembership object. This does not give full access to annotate, they must then be added to specific annotation sets ''' collection = get_report_collection(cid) previous_request,created = RequestMembership.objects.get_or_create(requester=request.user, collection=collection) if created == True: previous_request.save() # redirect back to collection with message messages.success(request, 'Request sent.') return HttpResponseRedirect(collection.get_absolute_url())
def remove_contributor(request,cid,uid): '''remove a contributor from a collection :param cid: the collection id :param uid: the contributor (user) id ''' collection = get_report_collection(cid) user = get_user(request,uid) contributors = collection.contributors.all() if request.user == collection.owner: if user in contributors: collection.contributors = [x for x in contributors if x != user] collection.save() messages.success(request, 'User %s is no longer a contributor to the collection.' %(contributor)) return edit_contributors(request,cid)
def view_label(request,cid,template=None): '''view_label is a general template to return a view of labels ''' collection = get_report_collection(cid) if template==None: template = 'labels/new_collection_label.html' # Labels associated with collection collection_labels = collection.allowed_annotations.all() labels = [x for x in AllowedAnnotation.objects.all() if x not in collection_labels] context = {'labels':labels, 'collection':collection, 'collection_labels':collection_labels} # send back json response success return render(request, template, context)
def add_contributor(request,cid): '''add a new contributor to a collection :param cid: the collection id ''' collection = get_report_collection(cid) if request.user == collection.owner: if request.method == "POST": user_id = request.POST.get('user',None) if user_id: user = get_user(request,user_id) collection.contributors.add(user) collection.save() # Alert the user of the status change message = """You have been added as a contributor to the %s.""" %(collection.name) notify.send(collection.owner, recipient=user, verb=message) messages.success(request, 'User %s added as contributor to collection.' %(user)) return edit_contributors(request,cid)
def annotate_testing(request,cid,rid=None,sid=None,reports=None): '''annotate_testing will select a random record from a collection, and render a page for the user to annotate :param cid: the collection id to select from :param rid: a report id, if provided, to annotate :param sid: the set id of a report, if provided, will be sent in with the next url param :param reports: a pre selected subset to select randomly from ''' collection = get_report_collection(cid) if reports == None: reports = Report.objects.filter(collection=collection) # Get a random report (if gets slow, change to get_random_report report = select_random_reports(reports)[0] # Ensure url returned is for report if sid != None: return HttpResponseRedirect(reverse('annotate_report_testing', kwargs={'rid': report.id, 'sid': sid})) return HttpResponseRedirect(reverse('annotate_report_testing', kwargs={'rid': report.id}))
def create_annotation_session(request,cid): '''create_annotation_session will allow the user to create a custom annotation set (stored in a session) for making annotations. (not currently in use) :param cid: the collection id to use ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): report_set = request.session.get('reports', None) if report_set != None: if len(report_set) != 0: messages.warning(request, 'You have an annotation session in progress. Creating a new set will override it.') # Get collection annotators users = get_collection_users(collection) allowed_annotations = get_allowed_annotations(collection,return_objects=False) context = {"users": users, "collection": collection, "allowed_annotations": allowed_annotations} return render(request, "reports/create_annotation_set_local.html", context) return HttpResponseRedirect(collection.get_absolute_url())
def deny_annotate_permission(request,cid,uid): '''a user can be denied annotate permission at the onset (being asked) or have it taken away, given asking --> pending --> does not pass test ''' collection = get_report_collection(cid) if has_collection_edit_permission(request,collection): requester = get_user(request,uid) permission_request = RequestMembership.objects.get(collection=collection, requester=requester) if permission_request.status != "DENIED": permission_request.status = "DENIED" permission_request.save() # Send a notification to the user message = """Your request to annotate sets in the collection %s has been denied""" %(collection.name) notify.send(collection.owner, recipient=requester, verb=message) messages.success(request, 'Annotators updated.') return view_report_collection(request,cid)
def download_data(request, cid): '''download data returns a general view for a collection to download data, meaning all reports, reports for a set, or annotations for a set :param cid: the collection id ''' collection = get_report_collection(cid) # Does the user have permission to edit? requester = request.user if requester == collection.owner or requester in collection.contributors.all( ): context = { "collection": collection, "annotators": get_collection_annotators(collection), "report_sets": ReportSet.objects.filter(collection=collection) } return render(request, 'export/download_data.html', context) messages.info(request, "You do not have permission to perform this action.") return redirect('report_collection_details', cid=collection.id)
def edit_contributors(request,cid): '''edit_contributors is the view to see, add, and delete contributors for a set. ''' collection = get_report_collection(cid) if request.user == collection.owner: # Who are current contributors? contributors = collection.contributors.all() # Any user that isn't the owner or already a contributor can be added invalid_users = [x.id for x in contributors] + [request.user.id] contenders = [x for x in User.objects.all() if x.username != 'AnonymousUser'] contenders = [x for x in contenders if x.id not in invalid_users] context = {'contributors':contributors, 'collection':collection, 'contenders':contenders} return render(request, 'reports/edit_collection_contributors.html', context) # Does not have permission, return to collection messages.info(request, "You do not have permission to perform this action.") return view_report_collection(request,cid)
def view_report_collection(request,cid): collection = get_report_collection(cid) report_count = collection.report_set.count() annotation_count = count_user_annotations(collection.annotators.all()) context = {"collection":collection, "report_count":report_count, "annotation_count":annotation_count} # Get all permissions, context must have collection as key context = get_permissions(request,context) # If the user has edit_permissions, we want to show him/her users that can be added if context["edit_permission"] == True: context["requesters"] = RequestMembership.objects.filter(collection=collection) context["requesters_pending"] = len([x for x in context["requesters"] if x.status == "PENDING"]) # Show report Sets allowed to annotate context["report_sets"] = ReportSet.objects.filter(annotators__in=[request.user], collection=collection) context['report_set_testers'] = get_user_report_sets(collection, user=request.user) # status="TESTING" return render(request, 'reports/report_collection_details.html', context)
def edit_report_collection(request, cid=None): if cid: collection = get_report_collection(cid) else: collection = ReportCollection(owner=request.user) if has_collection_edit_permission(request,collection): if request.method == "POST": form = ReportCollectionForm(request.POST,instance=collection) if form.is_valid(): # Update annotators and contributors previous_contribs = set() previous_annots = set() if form.instance.id is not None: previous_contribs = set(form.instance.contributors.all()) previous_annots = set(form.instance.annotators.all()) collection = form.save(commit=False) collection.save() form.save_m2m() # save contributors return HttpResponseRedirect(collection.get_absolute_url()) else: form = ReportCollectionForm(instance=collection) edit_permission = has_collection_edit_permission(request,collection) context = {"form": form, "edit_oermission": edit_permission} return render(request, "reports/edit_report_collection.html", context) # If user makes it down here, does not have permission messages.info(request, "You don't have permission to edit this collection.") return redirect("report_collections")
def download_reports(request, cid, sid=None, return_json=False): '''download reports returns a tsv download for an entire collection of reports (not recommended) or for reports within a collection (recommended) :param cid: the collection id :param sid: the report set if, if provided use that report set. :param return_json: return a Json response instead (default is False) ''' if sid != None: report_set = get_report_set(sid) collection = report_set.collection reports = report_set.reports.all() export_name = "%s_reports_set.tsv" % (report_set.id) else: collection = get_report_collection(cid) reports = collection.report_set.all() export_name = "%s_reports.tsv" % (collection.id) # Does the user have permission to edit? requester = request.user if requester == collection.owner or requester in collection.contributors.all( ): df = pandas.DataFrame(columns=["report_id", "report_text"]) df["report_id"] = [r.report_id for r in reports] df["report_text"] = [r.report_text for r in reports] if not return_json: response = HttpResponse(df.to_csv(sep="\t"), content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="%s"' % ( export_name) return response else: return JsonResponse(df.to_json(orient="records")) messages.info(request, "You do not have permission to perform this action.") return redirect('report_collection_details', cid=collection.id)
def generate_annotation_set(uid,user_ids,selection_keys,cid,N,testing_set,testing_set_correct,set_name,gid): '''generate annotation set is a task to select reports and generate an annotation set. If failed, will send a message to the user :param uid: the user id requesting, in case failed :param users: the (list, one or more) of user ids to include :param selection_keys: the list of selection keys :param cid: the id of the collection to generate a set for :param N: the number to include in the report set :param testing_set: the number to include in the testing set :param testing_set_correct: the total correct :param set_name: the name for the new report set :param gid: the unique id for the gold standard user ''' collection = get_report_collection(cid) # Try to get requester and gold standard annotator users try: requester = User.objects.get(id=uid) gold_standard = User.objects.get(id=gid) except: raise user = [] for uid in user_ids: try: add_user = User.objects.get(id=uid) user.append(add_user) except: pass if requester in collection.contributors.all() or requester == collection.owner: selections = [] seen_annotations = [] allowed_annotations = [] for selection_key in selection_keys: name,label = selection_key.replace("whatisit||","").split("||") annotation_object = AllowedAnnotation.objects.get(name=name, label=label) # We save the allowed annotation as a label to use for testing allowed_annotations.append(annotation_object) # Query to select the reports of interest selection = Annotation.objects.filter(annotator__in=user, annotation=annotation_object, reports__collection=collection) for annotation in selection: if annotation not in seen_annotations: selections = list(chain(selections,annotation.reports.filter(collection=collection))) seen_annotations.append(annotation) # Remove reports that are already in sets existing = [] existing_sets = ReportSet.objects.filter(collection=collection) for existing_set in existing_sets: existing = list(chain(existing,existing_set.reports.all())) selections = [report for report in selections if report not in existing] # If we have fewer selections left than options, no go if len(selections) < N: message = """You requested a new report set with %s reports for the %s collection. There are %s reports (not in a set) that match this criteria, and so we cannot create new set.""" %(N,collection.name,len(selections)) notify.send(requester, recipient=requester, verb=message) return False # Otherwise, save the new report set selections = selections[0:N] report_set = ReportSet.objects.create(collection=collection, number_tests=testing_set, number_reports=N, name=set_name, passing_tests=testing_set_correct, gold_standard=gold_standard) report_set.save() # Set creator should be allowed to see it report_set.annotators.add(requester) report_set.reports = selections report_set.testing_annotations = allowed_annotations report_set.save() # If we are successful, return a message to tell the admin to add annotators. add_annotators_link = "/collections/%s/sets/annotators" %(report_set.id) message = """Report set %s (N=%s reports) for the %s collection has been successfully created.""" %(set_name,N,collection.name) notify.send(requester, recipient=requester, verb=message)
def upload_reports(request,cid): '''upload_reports to a collection (currently not implemented) ''' collection = get_report_collection(cid) messages.info(request,"Upload of reports in the interface is not currently supported.") return view_report_collection(request,cid)