diff --git a/expdj/apps/experiments/forms.py b/expdj/apps/experiments/forms.py
index f72180a..fea9378 100644
--- a/expdj/apps/experiments/forms.py
+++ b/expdj/apps/experiments/forms.py
@@ -57,8 +57,8 @@ def __init__(self, *args, **kwargs):
class BatteryForm(ModelForm):
class Meta:
- exclude = ('owner','contributors','experiments','bonus_active'
- 'blacklist_active','blacklist_threshold')
+ exclude = ('owner', 'contributors', 'experiments',' bonus_active'
+ 'blacklist_active', 'blacklist_threshold')
model = Battery
def clean(self):
diff --git a/expdj/apps/experiments/models.py b/expdj/apps/experiments/models.py
index cae337e..83a951f 100644
--- a/expdj/apps/experiments/models.py
+++ b/expdj/apps/experiments/models.py
@@ -1,14 +1,21 @@
+import collections
+import operator
+
from guardian.shortcuts import assign_perm, get_users_with_perms, remove_perm
-from django.core.validators import MaxValueValidator, MinValueValidator
-from django.db.models.signals import m2m_changed
+from jsonfield import JSONField
from polymorphic.models import PolymorphicModel
-from django.core.urlresolvers import reverse
+
from django.contrib.auth.models import User
-from django.db.models import Q, DO_NOTHING
-from jsonfield import JSONField
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.urlresolvers import reverse
+from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
-import collections
-import operator
+from django.db.models import Q, DO_NOTHING
+from django.db.models.signals import m2m_changed
+
+# trying to import Result object directly from models was giving an import
+# error here, even though the import matched views.py exactly.
+from expdj.apps import turk
class CognitiveAtlasConcept(models.Model):
name = models.CharField(max_length=1000, null=False, blank=False)
@@ -133,6 +140,12 @@ def __str__(self):
class Battery(models.Model):
'''A battery is a collection of experiment templates'''
+
+ ORDER_CHOICES = (
+ ("random", "random"),
+ ("specified", "specified"),
+ )
+
# Name must be unique because anonymous link is generated from hash
name = models.CharField(max_length=200, unique = True, null=False, verbose_name="Name of battery")
description = models.TextField(blank=True, null=True)
@@ -150,19 +163,32 @@ class Battery(models.Model):
active = models.BooleanField(choices=((False, 'Inactive'),
(True, 'Active')),
default=True,verbose_name="Active")
- ORDER_CHOICES = (
- ("random", "random"),
- ("specified", "specified"),
- )
-
presentation_order = models.CharField("order function for presentation of experiments",max_length=200,choices=ORDER_CHOICES,default="random",help_text="Select experiments randomly, or in a custom specified order.")
- blacklist_active = models.BooleanField(choices=((False, 'Off'),
- (True, 'On')),
- default=False,verbose_name="Blacklist based on rejection criteria")
+ blacklist_active = models.BooleanField(
+ choices=((False, 'Off'), (True, 'On')),
+ default=False,
+ verbose_name="Blacklist based on rejection criteria"
+ )
blacklist_threshold = models.PositiveIntegerField(null=True,blank=True,default=10,help_text="Number of experiments to fail reject condition to add participant to blacklist",validators = [MinValueValidator(0.0)])
- bonus_active = models.BooleanField(choices=((False, 'Off'),
- (True, 'On')),
- default=False,verbose_name="Bonus based on reward criteria")
+ bonus_active = models.BooleanField(
+ choices=((False, 'Off'), (True, 'On')),
+ default=False,
+ verbose_name="Bonus based on reward criteria"
+ )
+ required_batteries = models.ManyToManyField(
+ "Battery",
+ blank=True,
+ related_name='required_batteries_mtm',
+ help_text=("Batteries which must be completed for this battery to be "
+ "attempted")
+ )
+ restricted_batteries = models.ManyToManyField(
+ "Battery",
+ blank=True,
+ related_name='restricted_batteries_mtm',
+ help_text=("Batteries that must not be completed in order for "
+ "this battery to be attempted")
+ )
def get_absolute_url(self):
return_cid = self.id
diff --git a/expdj/apps/experiments/templates/experiments/add_experiment.html b/expdj/apps/experiments/templates/experiments/add_experiment.html
index 06882f9..20dab52 100644
--- a/expdj/apps/experiments/templates/experiments/add_experiment.html
+++ b/expdj/apps/experiments/templates/experiments/add_experiment.html
@@ -125,7 +125,7 @@
{{ change_type }} Experiment Amazon Mechanical Turk HITS
title
+ creation time
keywords
description
reward
-
+
{% for hit in hits %}
+
+
{% if hit.sandbox = True %}
- {{ hit.title }}
+ {{ hit.title }}
{% else %}
- {{ hit.title }}
+ {{ hit.title }}
{% endif %}
+ (Details )
+
+ {{ hit.creation_time|date:"m/d/y G:H" }}
{{ hit.keywords }}
{{ hit.description }}
{{ hit.reward }}
{% if edit_permission %}
Manage Hit
+ Clone Hit
Expire Hit
{% if hit.status = "D" %}
Delete Hit
@@ -185,7 +192,7 @@ Amazon Mechanical Turk HITS
{% endif %}
- {% endfor %}
+ {% endfor %}
{% else %}
@@ -261,7 +268,8 @@ Email Authenticated Battery Link
]
});
$('#hits_table').dataTable({
- responsive: true
+ responsive: true,
+ "order": [[ 1, "desc" ]]
});
$('#delete_experiment').click(function(e) {
return confirm("This will remove the experiment from the battery, and not delete it from the application. Are you sure you want to do this?");
diff --git a/expdj/apps/experiments/templates/experiments/edit_battery.html b/expdj/apps/experiments/templates/experiments/edit_battery.html
index 9acb059..b17f028 100644
--- a/expdj/apps/experiments/templates/experiments/edit_battery.html
+++ b/expdj/apps/experiments/templates/experiments/edit_battery.html
@@ -1,6 +1,8 @@
{% extends "main/base.html" %}
+{% load static %}
{% load crispy_forms_tags %}
{% block head %}
+
{% endblock %}
{% block content %}
@@ -22,3 +24,10 @@ {{ header_text }}
{% endblock %}
+{% block scripts %}
+
+
+{% endblock %}
diff --git a/expdj/apps/experiments/urls.py b/expdj/apps/experiments/urls.py
index 44d58c5..18c8312 100644
--- a/expdj/apps/experiments/urls.py
+++ b/expdj/apps/experiments/urls.py
@@ -1,16 +1,20 @@
-from expdj.apps.experiments.views import experiments_view, edit_experiment_template, \
-delete_experiment_template, add_experiment_template, save_experiment_template, \
-view_experiment, preview_experiment, batteries_view, add_battery, \
-edit_battery, view_battery, delete_battery, remove_experiment, \
-add_experiment, edit_experiment, save_experiment, update_experiment_template, \
-remove_condition, preview_battery, serve_battery, serve_battery_anon, \
-generate_battery_user, sync, experiment_results_dashboard, \
-battery_results_dashboard, dummy_battery ,modify_experiment, intro_battery, \
-save_survey_template, add_survey_template, add_game_template, save_game_template, \
-enable_cookie_view, change_experiment_order, serve_battery_gmail, subject_management
-from expdj import settings
-from django.views.generic.base import TemplateView
from django.conf.urls import patterns, url
+from django.views.generic.base import TemplateView
+
+from expdj import settings
+from expdj.apps.experiments.views import (
+ experiments_view, edit_experiment_template, delete_experiment_template,
+ add_experiment_template, save_experiment_template, view_experiment,
+ preview_experiment, batteries_view, add_battery, edit_battery,
+ view_battery, delete_battery, remove_experiment, add_experiment,
+ edit_experiment, save_experiment, update_experiment_template,
+ remove_condition, preview_battery, serve_battery, serve_battery_anon,
+ generate_battery_user, sync, experiment_results_dashboard,
+ battery_results_dashboard, dummy_battery ,modify_experiment, intro_battery,
+ save_survey_template, add_survey_template, add_game_template,
+ save_game_template, enable_cookie_view, change_experiment_order,
+ serve_battery_gmail, subject_management
+)
urlpatterns = patterns('',
@@ -66,11 +70,12 @@
url(r'^batteries/(?P\d+|[A-Z]{8})/serve/gmail$',serve_battery_gmail,name='serve_battery_gmail'),
url(r'^local/(?P\d+|[A-Z]{8})/$',sync,name='local'),
url(r'^local/$',sync,name='local'), # local sync of data
- url(r'^cookie/$',enable_cookie_view,name='enable_cookie_view'))
+ url(r'^cookie/$',enable_cookie_view,name='enable_cookie_view')
+)
if settings.DEBUG:
urlpatterns += patterns('',
url(r'^static/(?P.*)$', 'django.views.static.serve', {
'document_root': settings.MEDIA_ROOT,
}),
- )
+)
diff --git a/expdj/apps/experiments/views.py b/expdj/apps/experiments/views.py
index ce3f2a0..9e61044 100644
--- a/expdj/apps/experiments/views.py
+++ b/expdj/apps/experiments/views.py
@@ -1,43 +1,57 @@
-from django.shortcuts import get_object_or_404, render_to_response, render, redirect
-from expdj.apps.experiments.models import ExperimentTemplate, Experiment, Battery, \
- ExperimentVariable, CreditCondition
-from expdj.apps.experiments.forms import ExperimentForm, ExperimentTemplateForm, BatteryForm, \
- BlacklistForm
-from expdj.apps.turk.utils import get_worker_experiments
-from expdj.apps.turk.tasks import assign_experiment_credit, update_assignments, check_blacklist, \
- experiment_reward
-from expdj.apps.experiments.utils import get_experiment_selection, install_experiments, \
- update_credits, make_results_df, get_battery_results, get_experiment_type, remove_keys, \
- complete_survey_result, select_experiments
-from expdj.settings import BASE_DIR,STATIC_ROOT,MEDIA_ROOT,DOMAIN_NAME
-from django.http.response import HttpResponseRedirect, HttpResponseForbidden, Http404
-from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
-from django.core.exceptions import PermissionDenied, ValidationError
+import datetime
+import csv
+import hashlib
+import json
+import numpy
+import os
+import pandas
+import re
+import shutil
+import uuid
+
from expfactory.battery import get_load_static, get_experiment_run
from expfactory.survey import generate_survey
+from expfactory.experiment import load_experiment
+from expfactory.views import embed_experiment
+
from django.contrib.auth.decorators import login_required
-from expdj.apps.turk.models import HIT, Result, Assignment
-from expdj.apps.turk.models import get_worker, Blacklist, Bonus
+from django.core.exceptions import PermissionDenied, ValidationError
+from django.forms.models import model_to_dict
from django.http import HttpResponse, JsonResponse
-from expfactory.experiment import load_experiment
+from django.http.response import (
+ HttpResponseRedirect, HttpResponseForbidden, Http404
+)
+from django.shortcuts import (
+ get_object_or_404, render_to_response, render, redirect
+)
+from django.utils import timezone
+from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
+
from expdj.apps.main.views import google_auth_view
-from django.forms.models import model_to_dict
-from expfactory.views import embed_experiment
-from expdj.apps.users.models import User
-from django.shortcuts import render
+from expdj.apps.experiments.forms import (
+ ExperimentForm, ExperimentTemplateForm, BatteryForm, BlacklistForm
+)
+from expdj.apps.experiments.models import (
+ ExperimentTemplate, Experiment, Battery, ExperimentVariable,
+ CreditCondition
+)
+from expdj.apps.experiments.utils import (
+ get_experiment_selection, install_experiments, update_credits,
+ make_results_df, get_battery_results, get_experiment_type, remove_keys,
+ complete_survey_result, select_experiments
+)
+from expdj.settings import BASE_DIR,STATIC_ROOT,MEDIA_ROOT,DOMAIN_NAME
import expdj.settings as settings
-from django.utils import timezone
-import datetime
-import uuid
-import shutil
-import hashlib
-import numpy
-import pandas
-import uuid
-import json
-import csv
-import re
-import os
+from expdj.apps.turk.models import (
+ HIT, Result, Assignment, get_worker, Blacklist, Bonus
+)
+from expdj.apps.turk.tasks import (
+ assign_experiment_credit, update_assignments, check_blacklist,
+ experiment_reward, check_battery_dependencies
+)
+from expdj.apps.turk.utils import get_worker_experiments
+from expdj.apps.users.models import User
+
media_dir = os.path.join(BASE_DIR,MEDIA_ROOT)
@@ -392,6 +406,15 @@ def serve_battery(request,bid,userid=None):
if isinstance(worker,list): # no id means returning []
return render_to_response("turk/invalid_id_sorry.html")
+ missing_batteries, blocking_batteries = check_battery_dependencies(battery, userid)
+ if missing_batteries or blocking_batteries:
+ return render_to_response(
+ "turk/battery_requirements_not_met.html",
+ context={'missing_batteries': missing_batteries,
+ 'blocking_batteries': blocking_batteries}
+ )
+
+
# Try to get some info about browser, language, etc.
browser = "%s,%s" %(request.user_agent.browser.family,request.user_agent.browser.version_string)
platform = "%s,%s" %(request.user_agent.os.family,request.user_agent.os.version_string)
@@ -399,7 +422,8 @@ def serve_battery(request,bid,userid=None):
# Does the worker have experiments remaining?
uncompleted_experiments = get_worker_experiments(worker,battery)
- if len(uncompleted_experiments) == 0:
+ experiments_left = len(uncompleted_experiments)
+ if experiments_left == 0:
# Thank you for your participation - no more experiments!
return render_to_response("turk/worker_sorry.html")
@@ -419,22 +443,27 @@ def serve_battery(request,bid,userid=None):
"uniqueId":result.id}
# If this is the last experiment, the finish button will link to a thank you page.
- if len(uncompleted_experiments) == 1:
+ if experiments_left == 1:
next_page = "/finished"
# Determine template name based on template_type
template = "%s/serve_battery.html" %(experiment_type)
- return deploy_battery(deployment="docker-local",
- battery=battery,
- experiment_type=experiment_type,
- context=context,
- task_list=task_list,
- template=template,
- next_page=next_page,
- result=result)
-
-def deploy_battery(deployment,battery,experiment_type,context,task_list,template,result,next_page=None,last_experiment=False):
+ return deploy_battery(
+ deployment="docker-local",
+ battery=battery,
+ experiment_type=experiment_type,
+ context=context,
+ task_list=task_list,
+ template=template,
+ next_page=next_page,
+ result=result,
+ experiments_left=experiments_left-1
+ )
+
+def deploy_battery(deployment, battery, experiment_type, context, task_list,
+ template, result, next_page=None, last_experiment=False,
+ experiments_left=None):
'''deploy_battery is a general function for returning the final view to deploy a battery, either local or MTurk
:param deployment: either "docker-mturk" or "docker-local"
:param battery: models.Battery object
@@ -445,6 +474,7 @@ def deploy_battery(deployment,battery,experiment_type,context,task_list,template
:param template: html template to render
:param result: the result object, turk.models.Result
:param last_experiment: boolean if true will redirect the user to a page to submit the result (for surveys)
+ :param experiments_left: integer indicating how many experiments are left in battery.
'''
if next_page == None:
next_page = "javascript:window.location.reload();"
@@ -471,6 +501,16 @@ def deploy_battery(deployment,battery,experiment_type,context,task_list,template
if result != None:
runcode = runcode.replace("{{result.id}}",str(result.id))
runcode = runcode.replace("{{next_page}}",next_page)
+ if experiments_left is not None:
+ total_experiments = battery.experiments.count()
+ expleft_msg = "Experiments left in battery {0:d} out of {1:d}
"
+ expleft_msg = expleft_msg.format(experiments_left, total_experiments)
+ runcode = runcode.replace("", expleft_msg)
+ if experiments_left == 0:
+ runcode = runcode.replace("Experiment Complete ", "All Experiments Complete ")
+ runcode = runcode.replace("You have completed the experiment", "You have completed all experiments")
+ runcode = runcode.replace("Click \"Next Experiment\" to keep your result, and progress to the next task", "Click \"Finised\" to keep your result.")
+ runcode = runcode.replace(">Next Experiment", ">Finished")
elif experiment_type in ["games"]:
experiment = load_experiment(experiment_folders[0])
runcode = experiment[0]["deployment_variables"]["run"]
diff --git a/expdj/apps/turk/forms.py b/expdj/apps/turk/forms.py
index eb35dad..c40dfb6 100644
--- a/expdj/apps/turk/forms.py
+++ b/expdj/apps/turk/forms.py
@@ -31,3 +31,13 @@ def __init__(self, *args, **kwargs):
self.helper.layout = Layout()
tab_holder = TabHolder()
self.helper.add_input(Submit("submit", "Save"))
+
+class WorkerContactForm(forms.Form):
+ subject = forms.CharField(label="Subject")
+ message = forms.CharField(widget=forms.Textarea, label="Message")
+
+ def __init__(self, *args, **kwargs):
+ super(WorkerContactForm, self).__init__(*args, **kwargs)
+ self.helper = FormHelper(self)
+ self.helper.layout = Layout()
+ self.helper.add_input(Submit("submit", "Send"))
diff --git a/expdj/apps/turk/models.py b/expdj/apps/turk/models.py
index 5319836..940da73 100644
--- a/expdj/apps/turk/models.py
+++ b/expdj/apps/turk/models.py
@@ -407,7 +407,6 @@ class Assignment(models.Model):
(True, 'Completed')),
default=False,verbose_name="participant completed the entire assignment")
-
def create(self):
init_connection_callback(sender=self.hit)
diff --git a/expdj/apps/turk/tasks.py b/expdj/apps/turk/tasks.py
index 7a1d5e4..a44b7a2 100644
--- a/expdj/apps/turk/tasks.py
+++ b/expdj/apps/turk/tasks.py
@@ -1,14 +1,22 @@
from __future__ import absolute_import
-from expdj.apps.turk.models import Result, Assignment, get_worker, HIT, Blacklist, Bonus
-from expdj.apps.experiments.utils import get_experiment_type
-from expdj.apps.experiments.models import ExperimentTemplate, Battery
+
+import numpy
+import os
+
from boto.mturk.price import Price
from celery import shared_task, Celery
-from django.utils import timezone
+
from django.conf import settings
+from django.utils import timezone
+
+from expdj.apps.experiments.models import ExperimentTemplate, Battery
+from expdj.apps.experiments.utils import get_experiment_type
+from expdj.apps.turk.models import Result, Assignment, get_worker, HIT, Blacklist, Bonus
from expdj.settings import TURK
-import numpy
-import os
+
+# trying to import Result object directly from models was giving an import
+# error here, even though the import matched views.py exactly.
+from expdj.apps import turk
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'expdj.settings')
app = Celery('expdj')
@@ -119,7 +127,7 @@ def add_blacklist(blacklist,experiment,description):
blacklist.flags[experiment.template.exp_id] = new_flag
# If the blacklist count is greater than acceptable count, user is blacklisted
- if len(blacklist.flags) > blacklist.battery.blacklist_threshold:
+ if len(blacklist.flags) >= blacklist.battery.blacklist_threshold:
blacklist.active = True
blacklist.blacklist_time = timezone.now()
blacklist.save()
@@ -283,3 +291,59 @@ def get_unique_variables(results):
new_variables = [x for x in trial["trialdata"].keys() if x not in variables]
variables = variables + new_variables
return numpy.unique(variables).tolist()
+
+
+def check_battery_dependencies(current_battery, worker_id):
+ '''
+ check_battery_dependencies looks up all of a workers completed
+ experiments in a result object and places them in a dictionary
+
+ organized by battery_id. Each of these buckets of results is
+ iterated through to check that every experiment in that battery has
+ been completed. In this way a list of batteries that a worker has
+ completed is built. This list is then compared to the lists of
+ required and restricted batteries to determine if the worker is
+ eligible to attempt the current battery.
+ '''
+ worker_results = turk.models.Result.objects.filter(
+ worker_id = worker_id,
+ completed=True
+ )
+
+ worker_result_batteries = {}
+ for result in worker_results:
+ if worker_result_batteries.get(result.battery.id):
+ worker_result_batteries[result.battery.id].append(result)
+ else:
+ worker_result_batteries[result.battery.id] = []
+ worker_result_batteries[result.battery.id].append(result)
+
+ worker_completed_batteries = []
+ for battery_id in worker_result_batteries:
+ result = worker_result_batteries[battery_id]
+ all_experiments_complete = True
+ result_experiment_list = [x.experiment_id for x in result]
+ try:
+ battery_experiments = Battery.objects.get(id=battery_id).experiments.all()
+ except ObjectDoesNotExist:
+ # battery may have been removed.
+ continue
+ for experiment in battery_experiments:
+ if experiment.template_id not in result_experiment_list:
+ all_experiments_complete = False
+ break
+ if all_experiments_complete:
+ worker_completed_batteries.append(battery_id)
+ continue
+
+ missing_batteries = []
+ for required_battery in current_battery.required_batteries.all():
+ if required_battery.id not in worker_completed_batteries:
+ missing_batteries.append(required_battery)
+
+ blocking_batteries = []
+ for restricted_battery in current_battery.restricted_batteries.all():
+ if restricted_battery.id in worker_completed_batteries:
+ blocking_batteries.append(restricted_battery)
+
+ return missing_batteries, blocking_batteries
diff --git a/expdj/apps/turk/templates/turk/battery_requirements_not_met.html b/expdj/apps/turk/templates/turk/battery_requirements_not_met.html
new file mode 100644
index 0000000..4f5fde2
--- /dev/null
+++ b/expdj/apps/turk/templates/turk/battery_requirements_not_met.html
@@ -0,0 +1,49 @@
+{% load staticfiles %}
+
+
+ The Experiment Factory: Thank you
+
+
+
+
+
+
+
+
+
Some Requirements Have Not Been Met.
+ {% if missing_batteries %}
+ The following batteries need to be completed before the current one may be attempted:
+
+ {% for battery in missing_batteries %}
+
+ {{ battery.name }}
+
+ {% endfor %}
+
+ {% endif %}
+ {% if blocking_batteries %}
+ The following batteries conflict with the current battery. Their completion is preventing the current battery from being attempted:
+
+ {% for battery in blocking_batteries %}
+
+ {{ battery.name }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
diff --git a/expdj/apps/turk/templates/turk/contact_worker_modal.html b/expdj/apps/turk/templates/turk/contact_worker_modal.html
new file mode 100644
index 0000000..7fd4eae
--- /dev/null
+++ b/expdj/apps/turk/templates/turk/contact_worker_modal.html
@@ -0,0 +1,19 @@
+{% load crispy_forms_tags %}
+
diff --git a/expdj/apps/turk/templates/turk/hit_detail.html b/expdj/apps/turk/templates/turk/hit_detail.html
new file mode 100644
index 0000000..2bf4856
--- /dev/null
+++ b/expdj/apps/turk/templates/turk/hit_detail.html
@@ -0,0 +1,30 @@
+{% extends "main/base.html" %}
+{% block title %}
+ Details for {{ hit.title }}
+{% endblock %}
+{% block content %}
+Battery: {{ hit.battery }}
+Owner: {{ hit.owner }}
+Mturk ID: {{ hit.mturk_id }}
+HIT Type ID: {{ hit.hit_type_id }}
+Creation Time: {{ hit.creation_time }}
+Description: {{ hit.description }}
+Keywords: {{ hit.keywords }}
+Reward: {{ hit.reward }}
+Lifetime in Hours: {{ hit.lifetime_in_hours }}
+Assignment Duration in Hours: {{ hit.assignment_duration_in_hours }}
+Max Assignments: {{ hit.max_assignments }}
+Auto Approval Delay in Seconds: {{ hit.auto_approval_delay_in_seconds }}
+Requester Annotation: {{ hit.requester_annotation }}
+Number of Similar Hits: {{ hit.number_of_similar_hits }}
+Review Status: {{ hit.review_status }}
+Number of Pending Assignments: {{ hit.number_of_assignments_pending }}
+Number of Available Assignments: {{ hit.number_of_assignments_available }}
+Number of Completed Assignments: {{ hit.number_of_assignments_completed }}
+Qualification for Number of Approved HITs: {{ hit.qualification_number_hits_approved }}
+Qualification for Percent of Assignments Approved: {{ hit.qualification_percent_assignments_approved }}
+Qualification Adult: {{ hit.qualification_adult }}
+Qualification Locale: {{ hit.qualification_locale }}
+Qualification Custom: {{ hit.qualification_custom }}
+Sandbox Only: {{ hit.sandbox }}
+{% endblock %}
diff --git a/expdj/apps/turk/templates/turk/manage_hit.html b/expdj/apps/turk/templates/turk/manage_hit.html
index d6e7fde..9d1adba 100644
--- a/expdj/apps/turk/templates/turk/manage_hit.html
+++ b/expdj/apps/turk/templates/turk/manage_hit.html
@@ -45,6 +45,7 @@ Attention
Worker ID
Status
Accept Time
+ Contact
@@ -54,6 +55,7 @@ Attention
{{ assignment.worker }}
{{ assignment.status }}
{{ assignment.accept_time | localize }}
+ Contact Worker
{% endfor %}
@@ -73,6 +75,7 @@ In Progress
Worker ID
Status
Accept Time
+ Contact
@@ -82,6 +85,7 @@ In Progress
{{ assignment.worker }}
{{ assignment.status }}
{{ assignment.accept_time | localize }}
+ Contact Worker
{% endfor %}
@@ -100,7 +104,8 @@ In Rejected
Assignment ID
Worker ID
Status
- Accept Time
+ Accept Time
+ Contact
@@ -110,6 +115,7 @@ In Rejected
{{ assignment.worker }}
{{ assignment.status }}
{{ assignment.accept_time | localize }}
+ Contact Worker
{% endfor %}
@@ -128,7 +134,8 @@ Submit
Assignment ID
Worker ID
Status
- Accept Time
+ Accept Time
+ Contact
@@ -138,6 +145,7 @@ Submit
{{ assignment.worker }}
{{ assignment.status }}
{{ assignment.accept_time | localize }}
+ Contact Worker
{% endfor %}
@@ -158,6 +166,7 @@ Approved
Worker ID
Status
Accept Time
+ Contact
@@ -167,6 +176,7 @@ Approved
{{ assignment.worker }}
{{ assignment.status }}
{{ assignment.accept_time | localize }}
+ Contact Worker
{% endfor %}
@@ -175,6 +185,8 @@ Approved
{% endif %}
+
+
{% endblock %}
@@ -190,6 +202,11 @@ Approved
$('.collapse').collapse('hide');
})
+$('.contact_worker').on("click", function(e) {
+ e.preventDefault();
+ $('#contact_modal').modal("show").load(this.href);
+});
+
})
{% endblock %}
diff --git a/expdj/apps/turk/urls.py b/expdj/apps/turk/urls.py
index 1bdf124..1ceab64 100644
--- a/expdj/apps/turk/urls.py
+++ b/expdj/apps/turk/urls.py
@@ -1,16 +1,47 @@
-from expdj.apps.turk.views import edit_hit, delete_hit, expire_hit, preview_hit, \
-serve_hit, multiple_new_hit, end_assignment, finished_view, not_consent_view, \
-survey_submit, manage_hit
+from expdj.apps.turk.views import (edit_hit, delete_hit, expire_hit,
+ preview_hit, serve_hit, multiple_new_hit, end_assignment, finished_view,
+ not_consent_view, survey_submit, manage_hit, contact_worker)
from expdj.apps.experiments.views import sync
from django.views.generic.base import TemplateView
from django.conf.urls import patterns, url
+from django.views.generic.base import TemplateView
+
+from expdj.apps.experiments.views import sync
+from expdj.apps.turk.views import (
+ edit_hit, delete_hit, expire_hit, preview_hit, serve_hit, multiple_new_hit,
+ end_assignment, finished_view, not_consent_view, survey_submit, manage_hit,
+ clone_hit, hit_detail
+)
+
urlpatterns = patterns('',
# HITS
url(r'^hits/(?P\d+|[A-Z]{8})/new$',edit_hit,name='new_hit'),
- url(r'^hits/(?P\d+|[A-Z]{8})/(?P\d+|[A-Z]{8})/manage$',manage_hit,name='manage_hit'),
- url(r'^hits/(?P\d+|[A-Z]{8})/multiple$',multiple_new_hit,name='multiple_new_hit'),
- url(r'^hits/(?P\d+|[A-Z]{8})/(?P\d+|[A-Z]{8})/edit$',edit_hit,name='edit_hit'),
+ url(
+ r'^hits/(?P\d+|[A-Z]{8})/(?P\d+|[A-Z]{8})/manage$',
+ manage_hit,
+ name='manage_hit'
+ ),
+ url(
+ r'^hits/(?P\d+|[A-Z]{8})/multiple$',
+ multiple_new_hit,
+ name='multiple_new_hit'
+ ),
+ url(
+ r'^hits/(?P\d+|[A-Z]{8})/(?P\d+|[A-Z]{8})/edit$',
+ edit_hit,
+ name='edit_hit'
+ ),
+ url(
+ r'^hits/(?P\d+|[A-Z]{8})/(?P\d+|[A-Z]{8})/clone$',
+ clone_hit,
+ name='clone_hit'
+ ),
+ url(
+ r'hits/(?P\d+|[A-Z]{8})/detail$',
+ hit_detail,
+ name='hit_detail'
+ ),
url(r'^hits/(?P\d+|[A-Z]{8})/delete$',delete_hit,name='delete_hit'),
url(r'^hits/(?P\d+|[A-Z]{8})/expire$',expire_hit,name='expire_hit'),
@@ -18,9 +49,18 @@
url(r'^accept/(?P\d+|[A-Z]{8})',serve_hit,name='serve_hit'),
url(r'^turk/(?P\d+|[A-Z]{8})',preview_hit,name='preview_hit'),
url(r'^turk/preview',not_consent_view,name='not_consent_view'),
- url(r'^turk/end/(?P\d+|[A-Z]{8})',end_assignment,name='end_assignment'),
- url(r'^surveys/(?P\d+|[A-Z]{8})/(?P[A-Za-z0-9]{30})/submit$',survey_submit,name='survey_submit'),
+ url(
+ r'^turk/end/(?P\d+|[A-Z]{8})',
+ end_assignment,
+ name='end_assignment'
+ ),
+ url(
+ r'^surveys/(?P\d+|[A-Z]{8})/(?P[A-Za-z0-9]{30})/submit$',
+ survey_submit,
+ name='survey_submit'
+ ),
url(r'^sync/(?P\d+|[A-Z]{8})/$',sync,name='sync_data'),
url(r'^sync/$',sync,name='sync_data'),
- url(r'^finished$', finished_view, name="finished_view")
+ url(r'^finished$', finished_view, name="finished_view"),
+ url(r'^worker/contact/(?P\d+)',contact_worker,name='contact_worker')
)
diff --git a/expdj/apps/turk/utils.py b/expdj/apps/turk/utils.py
index ad525ee..b3abf03 100644
--- a/expdj/apps/turk/utils.py
+++ b/expdj/apps/turk/utils.py
@@ -1,16 +1,19 @@
-from expdj.apps.experiments.models import Experiment
-from boto.mturk.connection import MTurkConnection
-from expdj.settings import BASE_DIR, MTURK_ALLOW
-from boto.mturk.question import ExternalQuestion
-from boto.mturk.price import Price
import ConfigParser
import datetime
-import pandas
import json
import os
+from boto.mturk.connection import MTurkConnection
+from boto.mturk.price import Price
+from boto.mturk.question import ExternalQuestion
+import pandas
+
from django.conf import settings
+from expdj.apps.experiments.models import Experiment
+from expdj.settings import BASE_DIR, MTURK_ALLOW
+
+
# RESULTS UTILS
def to_dict(input_ordered_dict):
@@ -75,7 +78,7 @@ def is_sandbox():
def get_worker_url():
"""Get proper URL depending upon sandbox settings"""
- if is_sandbox():
+ if settings.MTURK_ALLOW == False:
return SANDBOX_WORKER_URL
else:
return PRODUCTION_WORKER_URL
@@ -122,7 +125,8 @@ def get_worker_experiments(worker,battery,completed=False):
experiment_selection = [e for e in battery_tags if e not in worker_tags]
else:
experiment_selection = [e for e in worker_tags if e in battery_tags]
- return Experiment.objects.filter(template__exp_id__in=experiment_selection)
+ return Experiment.objects.filter(template__exp_id__in=experiment_selection,
+ battery_experiments__id=battery.id)
def get_time_difference(d1,d2,format='%Y-%m-%d %H:%M:%S'):
@@ -132,3 +136,6 @@ def get_time_difference(d1,d2,format='%Y-%m-%d %H:%M:%S'):
if isinstance(d2,str):
d2 = datetime.datetime.strptime(d2, format)
return (d2 - d1).total_seconds() / 60
+
+
+
diff --git a/expdj/apps/turk/views.py b/expdj/apps/turk/views.py
index 6d12149..d546d06 100644
--- a/expdj/apps/turk/views.py
+++ b/expdj/apps/turk/views.py
@@ -1,25 +1,32 @@
-from expdj.apps.experiments.views import check_battery_edit_permission, check_mturk_access, \
-get_battery_intro, deploy_battery
-from expdj.apps.turk.utils import get_connection, get_worker_url, get_host, get_worker_experiments
-from django.http.response import HttpResponseRedirect, HttpResponseForbidden, HttpResponse, Http404
-from django.shortcuts import get_object_or_404, render_to_response, render, redirect
-from expdj.apps.turk.tasks import assign_experiment_credit, get_unique_experiments
-from expdj.apps.experiments.utils import get_experiment_type, select_experiments
-from expdj.apps.turk.models import Worker, HIT, Assignment, Result, get_worker
-from expdj.apps.experiments.models import Battery, ExperimentTemplate
+from datetime import timedelta, datetime
+import json
+import os
+import requests
+
from expfactory.battery import get_load_static, get_experiment_run
-from expdj.settings import BASE_DIR,STATIC_ROOT,MEDIA_ROOT
+from numpy.random import choice
+from optparse import make_option
+
from django.contrib.auth.decorators import login_required
from django.core.management.base import BaseCommand
-from django.views.decorators.csrf import ensure_csrf_cookie
-from expdj.apps.turk.forms import HITForm
-from datetime import timedelta, datetime
+from django.http.response import (HttpResponseRedirect, HttpResponseForbidden,
+ HttpResponse, Http404, HttpResponseNotAllowed)
+from django.core.urlresolvers import reverse
+from django.shortcuts import get_object_or_404, render_to_response, render, redirect
from django.utils import timezone
-from optparse import make_option
-from numpy.random import choice
-import requests
-import json
-import os
+from django.views.decorators.csrf import ensure_csrf_cookie
+
+from expdj.apps.experiments.models import (Battery, ExperimentTemplate)
+from expdj.apps.experiments.views import (check_battery_edit_permission,
+ check_mturk_access, get_battery_intro, deploy_battery)
+from expdj.apps.experiments.utils import get_experiment_type, select_experiments
+from expdj.apps.turk.forms import HITForm, WorkerContactForm
+from expdj.apps.turk.models import Worker, HIT, Assignment, Result, get_worker
+from expdj.apps.turk.tasks import (assign_experiment_credit,
+ get_unique_experiments, check_battery_dependencies)
+from expdj.apps.turk.utils import (get_connection, get_credentials, get_host,
+ get_worker_url, get_worker_experiments)
+from expdj.settings import BASE_DIR,STATIC_ROOT,MEDIA_ROOT
media_dir = os.path.join(BASE_DIR,MEDIA_ROOT)
@@ -127,6 +134,10 @@ def serve_hit(request,hid):
# Get Experiment Factory objects for each
worker = get_worker(aws["worker_id"])
+ check_battery_response = check_battery_view(battery, aws["worker_id"])
+ if (check_battery_response):
+ return check_battery_response
+
# This is the submit URL, either external or sandbox
host = get_host(hit)
@@ -152,13 +163,14 @@ def serve_hit(request,hid):
# Does the worker have experiments remaining for the hit?
uncompleted_experiments = get_worker_experiments(worker,hit.battery)
- if len(uncompleted_experiments) == 0:
+ experiments_left = len(uncompleted_experiments)
+ if experiments_left == 0:
# Thank you for your participation - no more experiments!
return render_to_response("turk/worker_sorry.html")
# if it's the last experiment, we will submit the result to amazon (only for surveys)
last_experiment = False
- if len(uncompleted_experiments) == 1:
+ if experiments_left == 1:
last_experiment = True
task_list = select_experiments(battery,uncompleted_experiments)
@@ -180,18 +192,21 @@ def serve_hit(request,hid):
aws["uniqueId"] = result.id
# If this is the last experiment, the finish button will link to a thank you page.
- if len(uncompleted_experiments) == 1:
+ if experiments_left == 1:
next_page = "/finished"
- return deploy_battery(deployment="docker-mturk",
- battery=battery,
- experiment_type=experiment_type,
- context=aws,
- task_list=task_list,
- template=template,
- next_page=None,
- result=result,
- last_experiment=last_experiment)
+ return deploy_battery(
+ deployment="docker-mturk",
+ battery=battery,
+ experiment_type=experiment_type,
+ context=aws,
+ task_list=task_list,
+ template=template,
+ next_page=None,
+ result=result,
+ last_experiment=last_experiment,
+ experiments_left=experiments_left-1
+ )
else:
return render_to_response("turk/error_sorry.html")
@@ -208,6 +223,7 @@ def preview_hit(request,hid):
hit = get_hit(hid,request)
battery = hit.battery
context = get_amazon_variables(request)
+
context["instruction_forms"] = get_battery_intro(battery)
context["hit_uid"] = hid
context["start_url"] = "/accept/%s/?assignmentId=%s&workerId=%s&turkSubmitTo=%s&hitId=%s" %(hid,
@@ -289,6 +305,26 @@ def multiple_new_hit(request, bid):
else:
return HttpResponseForbidden()
+@login_required
+def clone_hit(request, bid, hid):
+ mturk_permission = check_mturk_access(request)
+ if mturk_permission != True:
+ return HttpResponseForbidden()
+
+ new_hit = get_object_or_404(HIT, pk=hid)
+ new_hit.pk = None
+ form = HITForm(instance=new_hit)
+ form.helper.form_action = reverse('new_hit',args=[bid])
+
+ battery = Battery.objects.get(pk=bid)
+ header_text = "%s HIT" %(battery.name)
+
+ context = {"form": form,
+ "is_owner": True,
+ "header_text":header_text}
+
+ return render(request, "turk/new_hit.html", context)
+
@login_required
def edit_hit(request, bid, hid=None):
@@ -329,6 +365,42 @@ def edit_hit(request, bid, hid=None):
else:
return HttpResponseForbidden()
+@login_required
+def contact_worker(request, aid):
+ mturk_permission = check_mturk_access(request)
+
+ if mturk_permission == False:
+ return HttpResponseForbidden()
+
+ assignment = Assignment.objects.get(id=aid)
+ worker = assignment.worker
+ if request.method == "GET":
+ form = WorkerContactForm()
+ context = {
+ "form": form,
+ "worker": worker,
+ "assignment": assignment
+ }
+ return render(request, "turk/contact_worker_modal.html", context)
+ elif request.method == "POST":
+ form = WorkerContactForm(request.POST)
+ if form.is_valid():
+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY_ID = get_credentials(
+ battery=assignment.hit.battery
+ )
+ conn = get_connection(
+ AWS_ACCESS_KEY_ID,
+ AWS_SECRET_ACCESS_KEY_ID,
+ hit=assignment.hit
+ )
+ subject = form.cleaned_data['subject']
+ message = form.cleaned_data['message']
+ conn.notify_workers([worker.id], subject, message)
+ return redirect('manage_hit', bid=assignment.hit.battery.id,
+ hid=assignment.hit.id)
+ else:
+ return HttpResponseNotAllowed()
+
# Expire a hit
@login_required
def expire_hit(request, hid):
@@ -366,6 +438,11 @@ def delete_hit(request, hid):
else:
return HttpResponseForbidden()
+@login_required
+def hit_detail(request, hid):
+ hit = get_object_or_404(HIT, pk=hid)
+ return render(request, "turk/hit_detail.html", {'hit': hit})
+
def get_flagged_questions(number=None):
"""get_flagged_questions
return questions that are flagged for curation
@@ -378,3 +455,14 @@ def get_flagged_questions(number=None):
if number == None:
return questions
return choice(questions,int(number))
+
+def check_battery_view(battery, worker_id):
+ missing_batteries, blocking_batteries = check_battery_dependencies(battery, worker_id)
+ if missing_batteries or blocking_batteries:
+ return render_to_response(
+ "turk/battery_requirements_not_met.html",
+ context={'missing_batteries': missing_batteries,
+ 'blocking_batteries': blocking_batteries}
+ )
+ else:
+ return None