Skip to content

Commit

Permalink
Merge pull request #1 from rwblair/issue156
Browse files Browse the repository at this point in the history
Issue156
  • Loading branch information
rwblair authored Jul 11, 2016
2 parents da3e94f + d54bcf7 commit 564c31b
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 78 deletions.
4 changes: 2 additions & 2 deletions expdj/apps/experiments/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
62 changes: 44 additions & 18 deletions expdj/apps/experiments/models.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{% extends "main/base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block head %}
<link rel="stylesheet" href="{% static "css/select2.min.css" %}" />
{% endblock %}
{% block content %}

Expand All @@ -22,3 +24,10 @@ <h2>{{ header_text }}</h2>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{% static "js/select2.min.js"%}" type="text/javascript"></script>
<script>
$("#id_required_batteries").select2();
$("#id_restricted_batteries").select2();
</script>
{% endblock %}
91 changes: 57 additions & 34 deletions expdj/apps/experiments/views.py
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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(
"experiments/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)
Expand Down
75 changes: 69 additions & 6 deletions expdj/apps/turk/tasks.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -283,3 +291,58 @@ 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
49 changes: 49 additions & 0 deletions expdj/apps/turk/templates/turk/battery_requirements_not_met.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% load staticfiles %}
<html>
<head>
<title>The Experiment Factory: Thank you</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
</head>
<body>
<style>
div {
width: 400px;
height: 400px;
font-family: 'Roboto', sans-serif;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;

margin: auto;
}
</style>
<body>
<div>
<h3>Some Requirements Have Not Been Met.</h3>
{% if missing_batteries %}
The following batteries need to be completed before the current one may be attempted: <br>
<ul>
{% for battery in missing_batteries %}
<li>
{{ battery.name }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if blocking_batteries %}
The following batteries conflict with the current battery. Their completion is preventing the current battery from being attempted: <br>
<ul>
{% for battery in blocking_batteries %}
<li>
{{ battery.name }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion expdj/apps/turk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,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
Expand Down
Loading

0 comments on commit 564c31b

Please sign in to comment.