virtualcommons-svn Mailing List for Virtual Commons Experiment Software (Page 4)
Status: Beta
Brought to you by:
alllee
You can subscribe to this list here.
2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
(21) |
Aug
(31) |
Sep
(6) |
Oct
(15) |
Nov
(2) |
Dec
(9) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2009 |
Jan
(4) |
Feb
(6) |
Mar
(12) |
Apr
(52) |
May
(14) |
Jun
(19) |
Jul
(81) |
Aug
(115) |
Sep
(36) |
Oct
(88) |
Nov
(46) |
Dec
(58) |
2010 |
Jan
(52) |
Feb
(55) |
Mar
(48) |
Apr
(15) |
May
(5) |
Jun
(38) |
Jul
(27) |
Aug
(24) |
Sep
(28) |
Oct
(1) |
Nov
(2) |
Dec
(29) |
2011 |
Jan
(87) |
Feb
(39) |
Mar
(63) |
Apr
(42) |
May
(26) |
Jun
(53) |
Jul
(23) |
Aug
(43) |
Sep
(37) |
Oct
(25) |
Nov
(4) |
Dec
(7) |
2012 |
Jan
(73) |
Feb
(79) |
Mar
(62) |
Apr
(28) |
May
(12) |
Jun
(2) |
Jul
(9) |
Aug
(1) |
Sep
(8) |
Oct
|
Nov
(3) |
Dec
(3) |
2013 |
Jan
(8) |
Feb
(16) |
Mar
(38) |
Apr
(74) |
May
(62) |
Jun
(15) |
Jul
(49) |
Aug
(19) |
Sep
(9) |
Oct
|
Nov
|
Dec
|
2014 |
Jan
|
Feb
|
Mar
|
Apr
(2) |
May
(25) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <com...@bi...> - 2013-07-23 22:22:26
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/aece9dcf96c5/ Changeset: aece9dcf96c5 User: alllee Date: 2013-07-24 00:21:56 Summary: - clamping self storage to zero if negative - setting participant ready and harvest decisions to 0 when resource is depleted Affected #: 2 files diff -r 68ff86fb364365d7ce5894c53a27c18bf9f0a3da -r aece9dcf96c5b20256ccaf1c08eace4329ab30cc vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -123,7 +123,7 @@ def get_average_storage(group, round_data): return get_total_storage(group, round_data) / float(group.size) -def get_resource_level_dv(group, round_data=None, round_configuration=None, cluster=None): +def get_resource_level_dv(group, round_data=None, round_configuration=None, cluster=None, shared_resource_enabled=None): ''' Returns either the GroupClusterDataValue (shared resource condition) or the GroupRoundDataValue (standard resource per group condition) for the given group @@ -132,7 +132,9 @@ round_data = group.current_round_data if round_configuration is None: round_configuration = round_data.round_configuration - if is_shared_resource_enabled(round_configuration): + if shared_resource_enabled is None: + shared_resource_enabled = is_shared_resource_enabled(round_configuration) + if shared_resource_enabled: return get_shared_resource_level_dv(group, round_data, cluster) else: return get_unshared_resource_level_dv(group, round_data) @@ -267,26 +269,20 @@ group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) ) - if round_configuration.is_playable_round: - # check for dead participants and set their ready and harvest decision flags - deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), - round_data=round_data, boolean_value=False) - for prdv in deceased_participants: - pgr = prdv.participant_group_relationship - set_harvest_decision(pgr, 0, round_data, submitted=True) - pgr.set_participant_ready(round_data) + shared_resource_enabled = is_shared_resource_enabled(round_configuration) ''' during a practice or regular round, set up resource levels, participant harvest decision parameters, and group formation ''' if should_reset_resource_level(round_configuration, experiment): initial_resource_level = get_max_resource_level(round_configuration) - logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) + logger.debug("Resetting resource level for all groups in %s to %d", round_configuration, initial_resource_level) for group in experiment.groups: ''' set resource level to initial default ''' - existing_resource_level = get_resource_level_dv(group, round_data, round_configuration) + existing_resource_level = get_resource_level_dv(group, round_data, round_configuration, + shared_resource_enabled=shared_resource_enabled) group.log( - "Setting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) + "Resetting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) existing_resource_level.update_int(initial_resource_level) # FIXME: verify that this is expected behavior - if the resource level is reset, reset all participant storages to 0 ParticipantRoundDataValue.objects.for_group(group, parameter=get_storage_parameter(), @@ -294,9 +290,34 @@ # reset all player statuses to alive ParticipantRoundDataValue.objects.for_group(group, parameter=get_player_status_parameter(), round_data=round_data).update(boolean_value=True) + elif round_configuration.is_playable_round: + # first check for a depleted resource + for group in experiment.groups: + existing_resource_level = get_resource_level_dv(group, round_data, round_configuration, + shared_resource_enabled=shared_resource_enabled) + if existing_resource_level.int_value <= 0: + group.log("setting all participant ready flags because of depleted resource %s", + existing_resource_level) + _zero_harvest_decisions(group.participant_group_relationship_set.all(), round_data) + # check for dead participants and set their ready and harvest decision flags + deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), + round_data=round_data, boolean_value=False).values_list('participant_group_relationship', flatten=True) + _zero_harvest_decisions(deceased_participants, round_data) + ''' + for prdv in deceased_participants: + pgr = prdv.participant_group_relationship + set_harvest_decision(pgr, 0, round_data, submitted=True) + pgr.set_participant_ready(round_data) + ''' +def _zero_harvest_decisions(participant_group_relationships, round_data): + # FIXME: possible performance issue, replace with direct update query + for pgr in participant_group_relationships: + set_harvest_decision(pgr, 0, round_data, submitted=True) + pgr.set_participant_ready(round_data) + def adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest, group_size=0): if group_size == 0: group_size = group.size @@ -336,7 +357,7 @@ def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=None): if max_resource_level is None: max_resource_level = get_max_resource_level(round_data.round_configuration) - current_resource_level_dv = get_resource_level_dv(group, round_data) + current_resource_level_dv = get_resource_level_dv(group, round_data, shared_resource_enabled=False) current_resource_level = current_resource_level_dv.int_value group_harvest_dv = get_group_harvest_dv(group, round_data) regrowth_dv = get_regrowth_dv(group, round_data) diff -r 68ff86fb364365d7ce5894c53a27c18bf9f0a3da -r aece9dcf96c5b20256ccaf1c08eace4329ab30cc vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -57,7 +57,7 @@ <tr><th>Trees left in the forest</th><th>Trees harvested</th><th>Trees in storage</th><th>Earnings</th></tr></thead><tbody> - <tr><td data-bind='text: resourceLevel'></td><td data-bind='text: totalHarvest'></td><td data-bind='text: Math.max(0, storage())'></td><td class='text-success' data-bind='text: totalEarnings'></td></tr> + <tr><td data-bind='text: resourceLevel'></td><td data-bind='text: totalHarvest'></td><td data-bind='text: clampedStorage'></td><td class='text-success' data-bind='text: totalEarnings'></td></tr></tbody></table><div class='alert alert-info'> @@ -365,7 +365,7 @@ <div class='alert bound-status-dashboard span1'><h4 id='dashboard-storage'>Total Earnings <i class='icon-info-sign' data-bind='attr: {"data-content": "You need at least " + costOfLiving() + " trees to survive each round."}'></i></h4><p> - <strong data-bind='css: { "text-error": storage() < costOfLiving(), "text-success": storage() > costOfLiving()}'><span data-bind='text: storage'></span><i class='icon-leaf'></i></strong> + <strong data-bind='css: { "text-error": storage() < costOfLiving(), "text-success": storage() > costOfLiving()}'><span data-bind='text: clampedStorage'></span><i class='icon-leaf'></i></strong></p></div><div class='alert bound-status-dashboard span1'> @@ -404,7 +404,7 @@ <tr><td>Earnings</td><!-- ko foreach: playerData --> - <td data-bind='text: Math.max(0, storage()), css: { "current-player": id() === $root.participantGroupId() }'></td> + <td data-bind='text: clampedStorage, css: { "current-player": id() === $root.participantGroupId() }'></td><!-- /ko --></tr><tr> @@ -542,6 +542,9 @@ var self = this; var model = ko.mapping.fromJS(experimentModelJson); model.tour = ko.observable(); + model.clampedStorage = ko.computed(function() { + return Math.max(0, model.storage()); + }); model.harvestDecisionOptions = ko.computed(function() { return ko.utils.range(0, model.maxHarvestDecision()); }); @@ -559,7 +562,7 @@ return formatCurrency(model.sessionTwoStorage() * model.exchangeRate()) }); model.totalEarnings = ko.computed(function() { - return formatCurrency(Math.max(0, model.storage() * model.exchangeRate())); + return formatCurrency(model.clampedStorage() * model.exchangeRate())); }); model.templateId = ko.computed(function() { switch ( model.templateName() ) { https://bitbucket.org/virtualcommons/vcweb/commits/17f4caf07855/ Changeset: 17f4caf07855 User: alllee Date: 2013-07-24 00:22:07 Summary: fixing forestry tests Affected #: 2 files diff -r aece9dcf96c5b20256ccaf1c08eace4329ab30cc -r 17f4caf0785518a6e6312f740c5ff7ecb0c44cbd vcweb/forestry/fixtures/forestry_experiment_metadata.json --- a/vcweb/forestry/fixtures/forestry_experiment_metadata.json +++ b/vcweb/forestry/fixtures/forestry_experiment_metadata.json @@ -84,10 +84,11 @@ "date_created": "2010-09-07 15:13:03", "duration": 0, "experiment_configuration": 1, - "instructions": "The following quiz is designed to ensure you understand the dynamics of this experiment.", + "instructions": "First practice round.", "last_modified": "2010-09-07 15:13:03", "round_type": "PRACTICE", - "sequence_number": 2 + "sequence_number": 2, + "initialize_data_values": true }, "model": "core.roundconfiguration", "pk": 2 @@ -140,7 +141,8 @@ "last_modified": "2010-09-07 15:13:03", "round_type": "REGULAR", "display_number": 1, - "sequence_number": 6 + "sequence_number": 6, + "initialize_data_values": true }, "model": "core.roundconfiguration", "pk": 6 diff -r aece9dcf96c5b20256ccaf1c08eace4329ab30cc -r 17f4caf0785518a6e6312f740c5ff7ecb0c44cbd vcweb/forestry/models.py --- a/vcweb/forestry/models.py +++ b/vcweb/forestry/models.py @@ -122,16 +122,11 @@ round_configuration = experiment.current_round logger.debug("setting up forestry round %s", round_configuration) if round_configuration.is_playable_round: - # participant parameter - harvest_decision_parameter = get_harvest_decision_parameter() - # group parameters - regrowth_parameter = get_regrowth_parameter() - group_harvest_parameter = get_group_harvest_parameter() - resource_level_parameter = get_resource_level_parameter() + # FIXME: push this step into the realm of experiment configuration # initialize group and participant data values experiment.initialize_data_values( - group_parameters=(regrowth_parameter, group_harvest_parameter, resource_level_parameter), - participant_parameters=[harvest_decision_parameter] + group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter()), + participant_parameters=[get_harvest_decision_parameter()] ) ''' during a practice or regular round, set up resource levels and participant @@ -153,32 +148,36 @@ calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth. also responsible for transferring those parameters to the next round as needed. ''' - current_round_configuration = experiment.current_round + round_data = experiment.current_round_data + current_round_configuration = round_data.round_configuration logger.debug("current round: %s", current_round_configuration) max_resource_level = MAX_RESOURCE_LEVEL - for group in experiment.group_set.all(): + for group in experiment.groups: # FIXME: simplify logic logger.debug("group %s has resource level", group) if has_resource_level(group): - current_resource_level_dv = get_resource_level_dv(group) + current_resource_level_dv = get_resource_level_dv(group, round_data) current_resource_level = current_resource_level_dv.int_value + group_harvest_dv = get_group_harvest_dv(group, round_data) + regrowth_dv = get_regrowth_dv(group, round_data) if current_round_configuration.is_playable_round: # FIXME: update this to use django queryset aggregation ala boundaries experiment total_harvest = sum( [ hd.value for hd in get_harvest_decisions(group).all() ]) logger.debug("total harvest for playable round: %d", total_harvest) if current_resource_level > 0 and total_harvest > 0: group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) - set_group_harvest(group, total_harvest) + group_harvest_dv.update_int(total_harvest) current_resource_level = max(current_resource_level - total_harvest, 0) # implements regrowth function inline # FIXME: parameterize regrowth rate. regrowth = current_resource_level / 10 group.log("Regrowth: adding %s to current resource level %s" % (regrowth, current_resource_level)) - set_regrowth(group, regrowth) - current_resource_level_dv.int_value = min(current_resource_level + regrowth, max_resource_level) - current_resource_level_dv.save() + regrowth_dv.update_int(regrowth) + current_resource_level_dv.update_int(min(current_resource_level + regrowth, max_resource_level)) ''' transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % current_resource_level_dv.int_value) - group.copy_to_next_round(current_resource_level_dv) +# FIXME: technically group harvest and regrowth data values should be re-initialized each round. Maybe push initialize +# data values flag into round parameter values + group.copy_to_next_round(current_resource_level_dv, group_harvest_dv, regrowth_dv) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-23 08:29:01
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/d0940c872a60/ Changeset: d0940c872a60 User: alllee Date: 2013-07-23 09:53:53 Summary: removing negative earnings from other group display Affected #: 2 files diff -r 78ec33e8e1ef14c1ca400527db90b5dc02516067 -r d0940c872a60e3f484896ff1992db96781f989a3 vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -57,7 +57,7 @@ <tr><th>Trees left in the forest</th><th>Trees harvested</th><th>Trees in storage</th><th>Earnings</th></tr></thead><tbody> - <tr><td data-bind='text: resourceLevel'></td><td data-bind='text: totalHarvest'></td><td data-bind='text:storage'></td><td class='text-success' data-bind='text: totalEarnings'></td></tr> + <tr><td data-bind='text: resourceLevel'></td><td data-bind='text: totalHarvest'></td><td data-bind='text: Math.max(0, storage())'></td><td class='text-success' data-bind='text: totalEarnings'></td></tr></tbody></table><div class='alert alert-info'> @@ -503,7 +503,7 @@ <tbody><tr><td>forest</td><td data-bind='text:otherGroupResourceLevel'></td></tr><tr><td>average harvest</td><td data-bind='text:otherGroupAverageHarvest().toFixed(1)'></td></tr> - <tr><td>average earnings</td><td data-bind='text:otherGroupAverageStorage().toFixed(1)'></td></tr> + <tr><td>average earnings</td><td data-bind='text:Math.max(0, otherGroupAverageStorage().toFixed(1))'></td></tr><tr><td>number alive</td><td data-bind='text:otherGroupNumberAlive'></td></tr></tbody></table> @@ -559,7 +559,7 @@ return formatCurrency(model.sessionTwoStorage() * model.exchangeRate()) }); model.totalEarnings = ko.computed(function() { - return formatCurrency(model.storage() * model.exchangeRate()); + return formatCurrency(Math.max(0, model.storage() * model.exchangeRate())); }); model.templateId = ko.computed(function() { switch ( model.templateName() ) { @@ -677,7 +677,13 @@ if (model.showTour()) { model.setupTour(); } + if (!model.alive()) { + model.secondsLeft(0); + model.clearCurrentInterval(); + return; + } if (isInt(model.timeRemaining()) && model.timeRemaining() > 0) { + model.initializeChat(); model.secondsLeft(model.timeRemaining()); model.setCurrentInterval( setInterval(function() { @@ -769,8 +775,6 @@ if (model.templateId() === "REGULAR") { model.startRound() } - // FIXME: should move to a model where we only enable chat in REGULAR rounds? - model.initializeChat(); if (model.isSurveyEnabled()) { switch (model.templateId()) { case "PRACTICE_ROUND_RESULTS": diff -r 78ec33e8e1ef14c1ca400527db90b5dc02516067 -r d0940c872a60e3f484896ff1992db96781f989a3 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -1106,7 +1106,7 @@ logger.warning("Tried to transfer participants to an experiment %s that already had participants %s", experiment, experiment.participant_set.all()) def __unicode__(self): - return u"%s #%s | %s" % (self.experiment_metadata.title, self.pk, self.experimenter) + return u"%s #%s | %s" % (self.experiment_configuration, self.pk, self.experimenter) class Meta: ordering = ['date_created', 'status'] https://bitbucket.org/virtualcommons/vcweb/commits/68ff86fb3643/ Changeset: 68ff86fb3643 User: alllee Date: 2013-07-23 10:28:48 Summary: reset_resource_level now respects initial_resource_level parameter again, removing bugged group harvest from group cluster calculations Affected #: 2 files diff -r d0940c872a60e3f484896ff1992db96781f989a3 -r 68ff86fb364365d7ce5894c53a27c18bf9f0a3da vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -5,7 +5,7 @@ GroupCluster, GroupClusterDataValue, RoundData, RoundConfiguration) from vcweb.forestry.models import (get_harvest_decision_parameter, get_harvest_decision, get_harvest_decision_dv, get_regrowth_rate_parameter, get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level as get_unshared_resource_level, - get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, set_resource_level, + get_regrowth_parameter, set_resource_level, get_initial_resource_level_parameter, get_resource_level_parameter, get_resource_level_dv as get_unshared_resource_level_dv, set_group_harvest, get_group_harvest_dv, get_regrowth_dv, set_regrowth, set_harvest_decision) @@ -85,13 +85,17 @@ default=False).boolean_value def get_max_resource_level(round_configuration): - ec = round_configuration.experiment_configuration + initial_resource_level = get_initial_resource_level(round_configuration) + if initial_resource_level is None: + ec = round_configuration.experiment_configuration # FIXME: number of rounds currently hard coded to be 20 for regular rounds, 10 for practice rounds - number_of_rounds = 20 if round_configuration.is_regular_round else 10 - return INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND * ec.max_group_size * number_of_rounds + number_of_rounds = 20 if round_configuration.is_regular_round else 10 + return INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND * ec.max_group_size * number_of_rounds + else: + return initial_resource_level def get_initial_resource_level(round_configuration, default=None): - return get_max_resource_level(round_configuration) + return round_configuration.get_parameter_value(parameter=get_initial_resource_level_parameter(), default=default).int_value def should_reset_resource_level(round_configuration, experiment): if round_configuration.is_repeating_round and experiment.current_repeated_round_sequence_number > 0: @@ -395,7 +399,7 @@ if shared_group_harvest > shared_resource_level: # adjust each individual harvest for each group in this cluster group_harvest = adjust_harvest_decisions(shared_resource_level, group, round_data, group_harvest, group_size=group_cluster_size) - set_group_harvest(group, group_harvest, round_data) + #set_group_harvest(group, group_harvest, round_data) shared_resource_level = shared_resource_level - group_harvest # set regrowth after shared_resource_level has been modified by all groups in this cluster resource_regrowth = calculate_regrowth(shared_resource_level, regrowth_rate, max_resource_level) diff -r d0940c872a60e3f484896ff1992db96781f989a3 -r 68ff86fb364365d7ce5894c53a27c18bf9f0a3da vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -95,8 +95,7 @@ return next_round_data = kwargs.get('next_round_data', None) if not next_round_data: - next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round, - is_next_round_data=True) + next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round, is_next_round_data=True) for existing_dv in data_values: #logger.debug("copying existing dv %s to next round %s", existing_dv, next_round_data) # Taking advantage of a trick from here: Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-23 04:22:19
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/78ec33e8e1ef/ Changeset: 78ec33e8e1ef User: alllee Date: 2013-07-23 06:22:07 Summary: adding ordering to round parameter values by round configuration Affected #: 1 file diff -r bb7b9525bde2db7f923da2f6bacb0a1e200b14a1 -r 78ec33e8e1ef14c1ca400527db90b5dc02516067 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -1541,6 +1541,10 @@ rc = self.round_configuration return u"{0}:{1} -> [{2}: {3}]".format(rc.experiment_configuration, rc.sequence_label, self.parameter, self.value) + class Meta: + ordering = [ 'round_configuration', 'parameter', 'date_created' ] + + class Group(models.Model, DataValueMixin): number = models.PositiveIntegerField() ''' internal numbering unique to the given experiment ''' Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-22 23:37:51
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/bb7b9525bde2/ Changeset: bb7b9525bde2 User: alllee Date: 2013-07-23 01:37:46 Summary: adding a basic lock check to the experimenter monitor interface to try to prevent double advance to next rounds Affected #: 4 files diff -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c -r bb7b9525bde2db7f923da2f6bacb0a1e200b14a1 vcweb/core/__init__.py --- a/vcweb/core/__init__.py +++ b/vcweb/core/__init__.py @@ -2,9 +2,13 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db.models import Model from django.db.models.query import QuerySet +from django.utils.functional import curry from functools import partial +import fcntl import json -from django.utils.functional import curry +import logging + +logger = logging.getLogger(__name__) class VcwebJSONEncoder(DjangoJSONEncoder): def default(self, obj): @@ -36,3 +40,17 @@ def enum(*sequential, **named): enums = dict(zip(sequential, range(len(sequential))), **named) return type('Enum', (), enums) + +class ModelLock(object): + def __init__(self, model): + self.filename = "/tmp/django-%s.%d.lock" % (model._meta.object_name, model.pk) + self.handle = open(self.filename, 'w') + + def acquire(self): + return fcntl.flock(self.handle, fcntl.LOCK_EX) + + def release(self): + return fcntl.flock(self.handle, fcntl.LOCK_UN) + + def __del__(self): + self.handle.close() diff -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c -r bb7b9525bde2db7f923da2f6bacb0a1e200b14a1 vcweb/core/ajax.py --- a/vcweb/core/ajax.py +++ b/vcweb/core/ajax.py @@ -170,13 +170,17 @@ def experiment_controller(request, pk, action=None): experimenter = request.user.experimenter experiment = _get_experiment(request, pk) + logger.debug("experimenter %s invoking %s on %s", experimenter, action, experiment) try: response_tuples = experiment.invoke(action, experimenter) - logger.debug("invoking action %s results: %s", action, str(response_tuples)) - return experiment.to_json() + logger.debug("invoking action %s: %s", action, str(response_tuples)) + return JsonResponse(dumps({ + 'success': True, + 'experiment': experiment.to_dict() + })) except AttributeError as e: logger.warning("no attribute %s on experiment %s (%s)", action, experiment.status_line, e) - return dumps({ + return JsonResponse(dumps({ 'success': False, 'message': 'Invalid experiment action %s' % action - }) + })) diff -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c -r bb7b9525bde2db7f923da2f6bacb0a1e200b14a1 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -1115,9 +1115,9 @@ # FIXME: refactor this into a single data structure # maps round type name to (description, default_template_filename) ROUND_TYPES_DICT = dict( - WELCOME=(_('Initial welcome page / waiting room'), 'welcome.html'), - GENERAL_INSTRUCTIONS=(_('Introduction to the experiment / general instructions page'), 'general-instructions.html'), - REGULAR=('Regular / playable experiment round', 'participate.html'), + WELCOME=(_('Initial welcome page waiting room'), 'welcome.html'), + GENERAL_INSTRUCTIONS=(_('General instructions and introduction to the experiment'), 'general-instructions.html'), + REGULAR=('Regular experiment round', 'participate.html'), CHAT=('Chat round', 'chat.html'), DEBRIEFING=('Debriefing round', 'debriefing.html'), INSTRUCTIONS=('Instructions round', 'instructions.html'), diff -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c -r bb7b9525bde2db7f923da2f6bacb0a1e200b14a1 vcweb/core/templates/experimenter/monitor.html --- a/vcweb/core/templates/experimenter/monitor.html +++ b/vcweb/core/templates/experimenter/monitor.html @@ -230,7 +230,6 @@ } } model.saveExperimenterNotes = function(localModel, evt) { - var notes = $('#experimenterNotesText').val(); $('#submitExperimenterNotesButton').html("Submitting.."); Dajaxice.vcweb.core.save_experimenter_notes(function(data) { @@ -280,12 +279,17 @@ }; model.advanceToNextRound = function() { $('#progress-modal').modal('show'); - Dajaxice.vcweb.core.experiment_controller(function(data) { - ko.mapping.fromJS(data, model); + Dajaxice.vcweb.core.experiment_controller(function(response) { + if (response.success) { + ko.mapping.fromJS(response.experiment, model); model.startTimer(); sendUpdateEvent(); $('#progress-modal').modal('hide'); - }, {pk: {{experiment.pk}}, 'action':"advance_to_next_round"}); + } + else { + console.debug("Unable to advance to next round: " + response.message); + } + }, {pk: {{experiment.pk}}, 'action':"advance_to_next_round"}); } model.confirmExperimentControllerAction = function(shouldUpdateParticipants, localModel, evt) { element = evt.target; @@ -295,13 +299,18 @@ } confirmAction(element, function(confirmed, action) { $('#progress-modal').modal('show'); - Dajaxice.vcweb.core.experiment_controller(function(data) { - ko.mapping.fromJS(data, model); - model.startTimer(); - if (shouldUpdateParticipants) { - sendUpdateEvent(); + Dajaxice.vcweb.core.experiment_controller(function(response) { + if (response.success) { + ko.mapping.fromJS(response.experiment, model); + model.startTimer(); + if (shouldUpdateParticipants) { + sendUpdateEvent(); + } + $('#progress-modal').modal('hide'); } - $('#progress-modal').modal('hide'); + else { + console.debug("Unable to perform experiment controller action: " + response.message); + } }, {pk: {{experiment.pk}}, 'action':action}); }); } @@ -311,14 +320,24 @@ model.startOrStopExperimentActionText = ko.computed(function() { return model.isRoundInProgress() ? "stop" : "start"; }); + // FIXME: poor man's mutex, hacky and unconvinced that it'll provide true thread safety. Revisit. + model.isCheckingParticipants = ko.observable(false); model.checkAllParticipantsReady = function() { + if (model.isCheckingParticipants()) { + console.debug("already checking participants, aborting"); + return; + } + model.isCheckingParticipants(true); console.debug("checking if all participants are ready"); $.get('/experiment/{{experiment.pk}}/check-ready-participants', function(response) { if (response.all_participants_ready) { - model.addMessage("All participants are ready, advancing to next round"); - model.advanceToNextRound(); + model.addMessage("All participants are ready, please advance to the next round"); + // FIXME: need to ensure that this can only get called once, otherwise we run the risk of + // invoking advance to next round twice + model.advanceToNextRound() } }); + model.isCheckingParticipants(false); } model.updateParticipants = function(m, evt) { confirmAction(evt.target, function(confirmed, action) { Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-07-22 21:29:47
|
New issue 113: advance to next round being invoked twice in succession https://bitbucket.org/virtualcommons/vcweb/issue/113/advance-to-next-round-being-invoked-twice A Lee: Need to add some kind of guard to prevent this from occurring on the server-side, it's generating duplicate data values which prevents the rest of the experiment from functioning properly. DEBUG 2013-07-22 07:05:50,586 [vcweb.core.models|invoke:915] experimenter (xec...@gm...) invoking action advance_to_next_round DEBUG 2013-07-22 07:05:50,599 [vcweb.core.models|invoke:915] experimenter (xec...@gm...) invoking action advance_to_next_round DEBUG 2013-07-22 07:05:50,608 [vcweb.core.models|log:799] Boundary Effects Experiment #45 | (xec...@gm...): Ending round with elapsed time 0:01:02.570118 DEBUG 2013-07-22 07:05:50,615 [vcweb.core.models|log:799] Boundary Effects Experiment #45 | (xec...@gm...): Ending round with elapsed time 0:01:02.577792 DEBUG 2013-07-22 07:05:50,623 [vcweb.core.models|end_round:991] about to send round ended signal with sender bound DEBUG 2013-07-22 07:05:50,628 [vcweb.bound.models|round_ended_handler:441] ending boundary effects round: Practice round 3 of 10 [x 10] (Boundary Effects AB ) DEBUG 2013-07-22 07:05:50,631 [vcweb.core.models|end_round:991] about to send round ended signal with sender bound DEBUG 2013-07-22 07:05:50,678 [vcweb.bound.models|update_resource_level:342] Harvest: total group harvest for playable round: 0 DEBUG 2013-07-22 07:05:50,679 [vcweb.core.models|log:1621] current resource level is 0, no one can harvest DEBUG 2013-07-22 07:05:50,679 [vcweb.bound.models|round_ended_handler:441] ending boundary effects round: Practice round 3 of 10 [x 10] (Boundary Effects AB ) DEBUG 2013-07-22 07:05:50,728 [vcweb.bound.models|update_resource_level:342] Harvest: total group harvest for playable round: 0 DEBUG 2013-07-22 07:05:50,731 [vcweb.core.models|log:1621] current resource level is 0, no one can harvest DEBUG 2013-07-22 07:05:50,754 [vcweb.bound.models|update_resource_level:366] copying resource levels to next round DEBUG 2013-07-22 07:05:50,758 [vcweb.core.models|log:1621] Transferring resource level 0 to next round DEBUG 2013-07-22 07:05:50,780 [vcweb.bound.models|update_resource_level:366] copying resource levels to next round DEBUG 2013-07-22 07:05:50,790 [vcweb.core.models|log:1621] Transferring resource level 0 to next round DEBUG 2013-07-22 07:05:50,790 [vcweb.core.models|get_or_create_round_data:946] creating participant ready participant round data values for experimenter driven experiment DEBUG 2013-07-22 07:05:50,809 [vcweb.core.models|get_or_create_round_data:946] creating participant ready participant round data values for experimenter driven experiment Responsible: alllee |
From: <com...@bi...> - 2013-07-22 07:23:25
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/94a0b76f6cbc/ Changeset: 94a0b76f6cbc User: alllee Date: 2013-07-22 09:23:14 Summary: wiring up clone experiment functionality Affected #: 3 files diff -r 7e2d5e56d3b09e1f3be89997b9c77a095e390fba -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c vcweb/core/ajax.py --- a/vcweb/core/ajax.py +++ b/vcweb/core/ajax.py @@ -110,6 +110,15 @@ @experimenter_required @dajaxice_register(method='POST') +def clone_experiment(request, experiment_id): + logger.debug("cloning experiment %s", experiment_id) + experiment = get_object_or_404(Experiment, pk=experiment_id) + experimenter = request.user.experimenter + cloned_experiment = experiment.clone(experimenter=experimenter) + return JsonResponse(dumps({'success': True, 'experiment': cloned_experiment.to_dict(attrs=('monitor_url', 'status_line', 'controller_url'))})) + +@experimenter_required +@dajaxice_register(method='POST') def create_experiment(request, experiment_configuration_id): logger.debug("incoming create experiment request POST: %s with id %s", request.POST, experiment_configuration_id, ) experiment_configuration = get_object_or_404(ExperimentConfiguration.objects.select_related('experiment_metadata'), pk=experiment_configuration_id) diff -r 7e2d5e56d3b09e1f3be89997b9c77a095e390fba -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -909,7 +909,7 @@ self.save() ACCEPTABLE_ACTIONS = ('advance_to_next_round', 'end_round', 'start_round', 'move_to_previous_round', 'activate', - 'deactivate', 'complete', 'restart_round', 'restart') + 'deactivate', 'complete', 'restart_round', 'restart', 'clone') def invoke(self, action_name, experimenter=None): if action_name in Experiment.ACCEPTABLE_ACTIONS: logger.debug("experimenter %s invoking action %s", experimenter, action_name) diff -r 7e2d5e56d3b09e1f3be89997b9c77a095e390fba -r 94a0b76f6cbc871b70de376d6b6238ee32168c5c vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -91,19 +91,19 @@ model.controllerUrl = function(experimentModel, urlAction) { return experimentModel.controller_url() + "/" + urlAction; } + function pendingExperimentCallback(data) { + if (data.success) { + pendingExperimentModel = ko.mapping.fromJS(data.experiment); + model.pendingExperiments.unshift(pendingExperimentModel); + model.activatePendingExperimentsTab(); + } + } model.createNewExperiment = function(configurationModel) { - Dajaxice.vcweb.core.create_experiment(function(data) { - if (data.success) { - pendingExperimentModel = ko.mapping.fromJS(data.experiment); - model.pendingExperiments.unshift(pendingExperimentModel); - model.activatePendingExperimentsTab(); - } - }, - { experiment_configuration_id: configurationModel.pk() }); + Dajaxice.vcweb.core.create_experiment(pendingExperimentCallback, { experiment_configuration_id: configurationModel.pk() }); } - model.cloneExperiment = function() { - console.debug("cloning experiment"); + model.cloneExperiment = function(experimentModel) { + Dajaxice.vcweb.core.clone_experiment(pendingExperimentCallback, { experiment_id: experimentModel.pk() }); } model.modify = function() { console.debug("modify existing configuration"); Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-22 06:57:57
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/7e2d5e56d3b0/ Changeset: 7e2d5e56d3b0 User: alllee Date: 2013-07-22 08:57:45 Summary: - fixed display bug expanding the content div when displaying qualtrics survey iframe - fixed off-by-one bug where repeating rounds were being executed n+1 times instead of n Affected #: 2 files diff -r ee35940a39534c9fc64839e9f9bf30ed23fbc114 -r 7e2d5e56d3b09e1f3be89997b9c77a095e390fba vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -736,7 +736,6 @@ model.update = function() { $.get('view-model', { participant_group_id: model.participantGroupId() }, function(data) { ko.mapping.fromJS(data, model); - model.afterRenderTemplate(); $('#progress-modal').modal('hide'); }); } @@ -772,9 +771,14 @@ } // FIXME: should move to a model where we only enable chat in REGULAR rounds? model.initializeChat(); - if (model.isSurveyEnabled() && model.templateId() === "PRACTICE_ROUND_RESULTS") { - $('#sidebar').hide(); - $('#content').toggleClass('span8 span12'); + if (model.isSurveyEnabled()) { + switch (model.templateId()) { + case "PRACTICE_ROUND_RESULTS": + case "FINAL_DEBRIEFING": + $('#sidebar').hide(); + $('#content').toggleClass('span8 span12'); + break; + } } $('[data-content]').popover({placement: 'top', trigger: 'hover'}); } diff -r ee35940a39534c9fc64839e9f9bf30ed23fbc114 -r 7e2d5e56d3b09e1f3be89997b9c77a095e390fba vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -98,7 +98,7 @@ next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round, is_next_round_data=True) for existing_dv in data_values: - logger.debug("copying existing dv %s to next round %s", existing_dv, next_round_data) + #logger.debug("copying existing dv %s to next round %s", existing_dv, next_round_data) # Taking advantage of a trick from here: # http://stackoverflow.com/questions/12182657/copy-or-clone-an-object-instance-in-django-python existing_dv.pk = None @@ -451,9 +451,15 @@ @property def status_label(self): return u"%s, %s" % (self.get_status_display(), self.current_round.get_round_type_display()) + @property def sequence_label(self): - return u"Round %s of %s" % (self.current_round_sequence_number, self.experiment_configuration.final_sequence_number) + cr = self.current_round + if cr.is_repeating_round: + return u"Round %s (repeating round %d of %d)" % (self.current_round_sequence_number, + self.current_repeated_round_sequence_number + 1, cr.repeat) + else: + return u"Round %s" % cr.sequence_label @property def status_line(self): @@ -545,7 +551,10 @@ if round_configuration.is_repeating_round: crsn = self.current_repeated_round_sequence_number if previous_round: - crsn = crsn - 1 + # XXX: convoluted logic, if we're looking for a previous repeating round and the current repeated + # round sequence number is 0 we need to clamp the repeated round sequence number to N - 1 where N is the + # number of repeats for that repeating round + crsn = round_configuration.repeat - 1 if crsn == 0 else crsn - 1 elif next_round: crsn = crsn + 1 ps.update(repeating_round_sequence_number=crsn) @@ -563,7 +572,7 @@ @property def should_repeat(self): cr = self.current_round - return cr.is_repeating_round and self.current_repeated_round_sequence_number < cr.repeat + return cr.is_repeating_round and self.current_repeated_round_sequence_number + 1 < cr.repeat @property def next_round(self): Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-21 23:12:41
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/65f24ca342db/ Changeset: 65f24ca342db User: alllee Date: 2013-07-22 00:52:40 Summary: patches for handling round data and repeating rounds Affected #: 3 files diff -r b712907c74a7f2a10001318d9554e17ed5f21d91 -r 65f24ca342db8867518da4aa274ad602e4e585cd vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -93,7 +93,9 @@ def get_initial_resource_level(round_configuration, default=None): return get_max_resource_level(round_configuration) -def should_reset_resource_level(round_configuration): +def should_reset_resource_level(round_configuration, experiment): + if round_configuration.is_repeating_round and experiment.current_repeated_round_sequence_number > 0: + return False return round_configuration.get_parameter_value(parameter=get_reset_resource_level_parameter(), default=False).boolean_value @@ -273,10 +275,10 @@ during a practice or regular round, set up resource levels, participant harvest decision parameters, and group formation ''' - if should_reset_resource_level(round_configuration): + if should_reset_resource_level(round_configuration, experiment): initial_resource_level = get_max_resource_level(round_configuration) logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) - for group in experiment.group_set.filter(session_id=round_configuration.session_id): + for group in experiment.groups: ''' set resource level to initial default ''' existing_resource_level = get_resource_level_dv(group, round_data, round_configuration) group.log( @@ -410,7 +412,8 @@ def update_participants(experiment, round_data, round_configuration): logger.debug("updating participants") cost_of_living = get_cost_of_living(round_configuration) - next_round_data, created = experiment.round_data_set.get_or_create(round_configuration=experiment.next_round) + next_round_data, created = experiment.get_or_create_round_data(round_configuration=experiment.next_round, + is_next_round_data=True) for pgr in experiment.participant_group_relationships: player_status_dv = get_player_status_dv(pgr, round_data) storage_dv = get_storage_dv(pgr, round_data) diff -r b712907c74a7f2a10001318d9554e17ed5f21d91 -r 65f24ca342db8867518da4aa274ad602e4e585cd vcweb/bound/views.py --- a/vcweb/bound/views.py +++ b/vcweb/bound/views.py @@ -9,7 +9,7 @@ from vcweb.bound.forms import SingleIntegerDecisionForm from vcweb.bound.models import (get_experiment_metadata, get_regrowth_rate, get_max_allowed_harvest_decision, get_cost_of_living, get_resource_level, get_initial_resource_level, get_total_storage, get_storage, - get_all_session_storages, get_last_harvest_decision, get_harvest_decision_dv, get_harvest_decision_parameter, + get_all_session_storages, get_harvest_decision_dv, get_harvest_decision_parameter, set_harvest_decision, can_observe_other_group, get_average_harvest, get_average_storage, get_total_harvest, get_number_alive, get_player_data) @@ -101,7 +101,8 @@ current_round = experiment.current_round current_round_data = experiment.current_round_data previous_round = experiment.previous_round - previous_round_data = experiment.get_round_data(round_configuration=previous_round) +# FIXME: need to adjust this to work with repeating rounds + previous_round_data = experiment.get_round_data(round_configuration=previous_round, previous_round=True) experiment_model_dict = experiment.to_dict(include_round_data=False, default_value_dict=experiment_model_defaults) logger.debug("returning view model json for round %s" % current_round) diff -r b712907c74a7f2a10001318d9554e17ed5f21d91 -r 65f24ca342db8867518da4aa274ad602e4e585cd vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -58,7 +58,6 @@ elif name: return parameter_value_set.get(parameter__name=name) except parameter_value_set.model.DoesNotExist as e: - logger.debug("%s: (lookup %s %s) returning default %s", e, parameter, name, default) return DefaultValue(default) class DataValueMixin(object): @@ -96,7 +95,8 @@ return next_round_data = kwargs.get('next_round_data', None) if not next_round_data: - next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round) + next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round, + is_next_round_data=True) for existing_dv in data_values: logger.debug("copying existing dv %s to next round %s", existing_dv, next_round_data) # Taking advantage of a trick from here: @@ -538,12 +538,17 @@ return self.get_round_data(round_configuration=self.current_round) # FIXME: cache this as well to avoid a query per invocation - def get_round_data(self, round_configuration=None): + def get_round_data(self, round_configuration=None, previous_round=False, next_round=False): if round_configuration is None: round_configuration = self.current_round ps = dict(round_configuration=round_configuration) if round_configuration.is_repeating_round: - ps.update(repeating_round_sequence_number=self.current_repeated_round_sequence_number) + crsn = self.current_repeated_round_sequence_number + if previous_round: + crsn = crsn - 1 + elif next_round: + crsn = crsn + 1 + ps.update(repeating_round_sequence_number=crsn) return RoundData.objects.select_related('round_configuration').get(experiment=self, **ps) @property @@ -748,8 +753,8 @@ if not round_configuration.initialize_data_values: logger.debug("Aborting, round configuration isn't set to initialize data values") return - elif round_configuration.is_repeating_round and self.repeating_round_sequence_number > 0: - logger.debug("repeating round # %d - aborting data value initialization", self.repeating_round_sequence_number) + elif round_configuration.is_repeating_round and self.current_repeated_round_sequence_number > 0: + logger.debug("repeating round # %d - aborting data value initialization", self.current_repeated_round_sequence_number) return logger.debug("initializing data values for [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) @@ -915,13 +920,14 @@ return return self.start_round() - def get_or_create_round_data(self, round_configuration=None): + def get_or_create_round_data(self, round_configuration=None, is_next_round_data=False): if round_configuration is None: round_configuration = self.current_round ps = dict(round_configuration=round_configuration) if round_configuration.is_repeating_round: # create round data with repeating sequence number - ps['repeating_round_sequence_number'] = self.current_repeated_round_sequence_number + rrsn = self.current_repeated_round_sequence_number + ps['repeating_round_sequence_number'] = rrsn + 1 if is_next_round_data else rrsn round_data, created = self.round_data_set.get_or_create(**ps) if self.experiment_configuration.is_experimenter_driven: # create participant ready data values for every round in experimenter driven experiments @@ -1753,7 +1759,9 @@ return self.round_configuration.session_id def __unicode__(self): - return u"Data for Round %s (%s)" % (self.round_configuration.sequence_number, self.round_configuration.get_round_type_display()) + if self.round_configuration.is_repeating_round: + return u"Round data for %s (repeat #%d)" % (self.round_configuration, self.repeating_round_sequence_number) + return u"Data for round %s" % self.round_configuration class Meta: ordering = [ 'round_configuration' ] https://bitbucket.org/virtualcommons/vcweb/commits/ee35940a3953/ Changeset: ee35940a3953 User: alllee Date: 2013-07-22 01:12:20 Summary: more fixes for repeating rounds, correcting previous round / next round semantics Affected #: 2 files diff -r 65f24ca342db8867518da4aa274ad602e4e585cd -r ee35940a39534c9fc64839e9f9bf30ed23fbc114 vcweb/bound/views.py --- a/vcweb/bound/views.py +++ b/vcweb/bound/views.py @@ -101,7 +101,6 @@ current_round = experiment.current_round current_round_data = experiment.current_round_data previous_round = experiment.previous_round -# FIXME: need to adjust this to work with repeating rounds previous_round_data = experiment.get_round_data(round_configuration=previous_round, previous_round=True) experiment_model_dict = experiment.to_dict(include_round_data=False, default_value_dict=experiment_model_defaults) logger.debug("returning view model json for round %s" % current_round) diff -r 65f24ca342db8867518da4aa274ad602e4e585cd -r ee35940a39534c9fc64839e9f9bf30ed23fbc114 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -567,16 +567,19 @@ @property def next_round(self): - current_round = self.current_round if not self.should_repeat and self.has_next_round: return self.get_round_configuration(self.current_round_sequence_number + 1) else: - return current_round + return self.current_round @property def previous_round(self): # FIXME: loop instead w/ mod? - return self.get_round_configuration(max(self.current_round_sequence_number - 1, 1)) + current_round = self.current_round + if current_round.is_repeating_round and self.current_repeated_round_sequence_number > 0: + return current_round + else: + return self.get_round_configuration(max(self.current_round_sequence_number - 1, 1)) @property def has_next_round(self): Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-20 07:05:00
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/b712907c74a7/ Changeset: b712907c74a7 User: alllee Date: 2013-07-20 09:04:45 Summary: more work on repeating round configurations Affected #: 3 files diff -r 988084df22904d6dc059e209a331ebc9e14fd8d5 -r b712907c74a7f2a10001318d9554e17ed5f21d91 vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -255,13 +255,12 @@ round_configuration = experiment.current_round round_data = experiment.get_round_data(round_configuration) logger.debug("setting up round %s", round_configuration) - # initialize group and participant data values - if round_configuration.initialize_data_values: - experiment.initialize_data_values( + # initialize group and participant data values if necessary + experiment.initialize_data_values( group_cluster_parameters=(get_regrowth_parameter(), get_resource_level_parameter(),), group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) - ) + ) if round_configuration.is_playable_round: # check for dead participants and set their ready and harvest decision flags deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), diff -r 988084df22904d6dc059e209a331ebc9e14fd8d5 -r b712907c74a7f2a10001318d9554e17ed5f21d91 vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -531,7 +531,6 @@ <span class='add-on'><i class='icon-leaf'></i></span><input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: participantGroupId'/><input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: harvestDecision'> - <input id='harvestDecisionSubmitted' style='display: none;' type='checkbox' name='submitted' data-bind='value: submitted'/><select id='harvestDecisionSelect' name='harvest_decision' required="required" form="vcweb-form" data-bind='options: harvestDecisionOptions, value: harvestDecision'></select></div> @@ -706,6 +705,7 @@ model.clearCurrentInterval(); model.submitted(true); model.secondsLeft(0); + model.disableChatForm(); }); } @@ -762,7 +762,7 @@ model.initializeChat = function() { // FIXME: chat is disabled in practice rounds, when it's explicitly disabled, or if the participant is // no longer alive - var chatDisabled = model.isPracticeRound() || ! model.chatEnabled() || ! model.alive(); + var chatDisabled = model.isPracticeRound() || ! model.chatEnabled() || ! model.alive() || model.submitted(); model.setFormDisabled("#chat-form", chatDisabled); model.chatEnabled(! chatDisabled); } diff -r 988084df22904d6dc059e209a331ebc9e14fd8d5 -r b712907c74a7f2a10001318d9554e17ed5f21d91 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -292,8 +292,15 @@ self.max_group_size = 0 @property + def total_number_of_rounds(self): + number_of_rounds = self.round_configuration_set.count() + repeating_rounds = self.round_configuration_set.filter(repeat__gt=0) + number_of_rounds = number_of_rounds - repeating_rounds.count() + sum(repeating_rounds.values_list('repeat', flat=True)) + return number_of_rounds + + @property def final_sequence_number(self): - # FIXME: or max round_configurations.sequence_number (as currently implemented could fail in the face of degenerate data) + # FIXME doesn't work with repeating rounds, also doesn't work well with degenerate data (e.g., manual sequence numbers > count) return self.round_configuration_set.count() @property @@ -735,9 +742,17 @@ Overly complex logic, possible danger to use empty lists as initial keyword args but we only iterate over them (e.g., http://effbot.org/zone/default-values.htm) get_or_create logic can create degenerate data (duplicate group round data values for instance) """ - logger.debug("initializing data values for [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) if round_data is None: round_data = self.current_round_data + round_configuration = round_data.round_configuration + if not round_configuration.initialize_data_values: + logger.debug("Aborting, round configuration isn't set to initialize data values") + return + elif round_configuration.is_repeating_round and self.repeating_round_sequence_number > 0: + logger.debug("repeating round # %d - aborting data value initialization", self.repeating_round_sequence_number) + return + + logger.debug("initializing data values for [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) parameter_defaults = defaultdict(dict) # defaults map parameter model instances to their default initial value, e.g., { footprint-level-parameter: 1, resource-level-parameter: 100 } for parameter in itertools.chain(participant_parameters, group_parameters, group_cluster_parameters): @@ -1009,7 +1024,7 @@ 'roundDataId': "roundData_%s" % round_data.pk, 'experimenterNotes': round_data.experimenter_notes, 'roundType': rc.get_round_type_display(), - 'roundNumber': rc.round_number, + 'roundNumber': round_data.round_number, # empty stubs to be loaded in dynamically when loaded 'groupDataValues': [], 'participantDataValues': [], @@ -1082,9 +1097,9 @@ # FIXME: refactor this into a single data structure # maps round type name to (description, default_template_filename) ROUND_TYPES_DICT = dict( - WELCOME=('Initial welcome round', 'welcome.html'), - GENERAL_INSTRUCTIONS=('General instructions round (introduction)', 'general-instructions.html'), - REGULAR=('Regular experiment round', 'participate.html'), + WELCOME=(_('Initial welcome page / waiting room'), 'welcome.html'), + GENERAL_INSTRUCTIONS=(_('Introduction to the experiment / general instructions page'), 'general-instructions.html'), + REGULAR=('Regular / playable experiment round', 'participate.html'), CHAT=('Chat round', 'chat.html'), DEBRIEFING=('Debriefing round', 'debriefing.html'), INSTRUCTIONS=('Instructions round', 'instructions.html'), @@ -1240,7 +1255,10 @@ @property def sequence_label(self): - return u"%d of %d" % (self.sequence_number, self.experiment_configuration.final_sequence_number) + if self.is_repeating_round: + return u"%d of %d [x %d]" % (self.sequence_number, self.experiment_configuration.final_sequence_number, self.repeat) + else: + return u"%d of %d" % (self.sequence_number, self.experiment_configuration.final_sequence_number) class Meta: ordering = [ 'experiment_configuration', 'sequence_number', 'date_created' ] @@ -1724,6 +1742,13 @@ experimenter_notes = models.TextField(blank=True) @property + def round_number(self): + round_number = self.round_configuration.round_number + if self.round_configuration.is_repeating_round: + return "%s.%s" % (round_number, self.repeating_round_sequence_number) + else: + return round_number + @property def session_id(self): return self.round_configuration.session_id Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-19 20:24:18
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/7b40d30dc9c9/ Changeset: 7b40d30dc9c9 User: alllee Date: 2013-07-19 22:21:12 Summary: adding update convenience methods to parameterized values for updating and saving rvalues, fixing bug where group clusters weren't propagating their regrowth data value across rounds Affected #: 2 files diff -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f -r 7b40d30dc9c9a1250916c06a9d93e733e6e667fa vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -135,7 +135,6 @@ def get_shared_resource_level(group, round_data=None, cluster=None): return get_shared_resource_level_dv(group, round_data, cluster).int_value - def get_shared_resource_level_dv(group=None, round_data=None, cluster=None): if round_data is None: round_data = group.current_round_data @@ -144,6 +143,12 @@ cluster = group_relationship.cluster return cluster.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) +def get_shared_regrowth_dv(cluster=None, round_data=None): + if round_data is None: + round_data = cluster.experiment.current_round_data + return cluster.get_data_value(parameter=get_regrowth_parameter(), round_data=round_data) + + ''' participant data value accessors ''' def get_storage_dv(participant_group_relationship, round_data=None, default=None): @@ -184,8 +189,7 @@ def set_storage(participant_group_relationship, round_data, value): storage_dv = get_storage_dv(participant_group_relationship, round_data) - storage_dv.int_value = value - storage_dv.save() + storage_dv.update_int(value) return storage_dv def get_player_status_dv(participant_group_relationship, round_data, default=True): @@ -203,7 +207,9 @@ round_data__in=[previous_round_data, current_round_data], parameter__in=(get_player_status_parameter(), get_storage_parameter(), get_harvest_decision_parameter()), ) + # nested dict mapping participant group relationship -> dict(parameter -> participant round data value) player_dict = defaultdict(lambda: defaultdict(lambda: None)) + player_status_parameter = get_player_status_parameter() for prdv in prdvs: player_dict[prdv.participant_group_relationship][prdv.parameter] = prdv player_data = [] @@ -213,8 +219,8 @@ for int_parameter in (get_harvest_decision_parameter(), get_storage_parameter()): if pgrdv_dict[int_parameter] is None: pgrdv_dict[int_parameter] = DefaultValue(0) - if pgrdv_dict[get_player_status_parameter()] is None: - pgrdv_dict[get_player_status_parameter()] = DefaultValue(True) + if pgrdv_dict[player_status_parameter] is None: + pgrdv_dict[player_status_parameter] = DefaultValue(True) player_data.append({ 'id': pgr.pk, 'number': pgr.participant_number, @@ -241,7 +247,6 @@ return participant_group_relationship.get_data_value(parameter=get_harvest_decision_parameter(), round_data=round_data, default=0).int_value - @receiver(signals.round_started, sender=EXPERIMENT_METADATA_NAME) def round_started_handler(sender, experiment=None, **kwargs): if experiment is None: @@ -258,7 +263,7 @@ participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) ) if round_configuration.is_playable_round: -# check for dead participants and set their ready and harvest decision flags + # check for dead participants and set their ready and harvest decision flags deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), round_data=round_data, boolean_value=False) for prdv in deceased_participants: @@ -277,9 +282,8 @@ existing_resource_level = get_resource_level_dv(group, round_data, round_configuration) group.log( "Setting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) - existing_resource_level.int_value = initial_resource_level - existing_resource_level.save() - # FIXME: verify that this is expected behavior - if the resource level is reset, reset storage to 0 + existing_resource_level.update_int(initial_resource_level) + # FIXME: verify that this is expected behavior - if the resource level is reset, reset all participant storages to 0 ParticipantRoundDataValue.objects.for_group(group, parameter=get_storage_parameter(), round_data=round_data).update(int_value=0) # reset all player statuses to alive @@ -341,20 +345,16 @@ total_harvest = adjusted_harvest group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) - group_harvest_dv.int_value = total_harvest - group_harvest_dv.save() + group_harvest_dv.update_int(total_harvest) current_resource_level = current_resource_level - total_harvest resource_regrowth = calculate_regrowth(current_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) - regrowth_dv.int_value = resource_regrowth - regrowth_dv.save() + regrowth_dv.update_int(resource_regrowth) # clamp resource - current_resource_level_dv.int_value = min(current_resource_level + resource_regrowth, max_resource_level) - current_resource_level_dv.save() + current_resource_level_dv.update_int(min(current_resource_level + resource_regrowth, max_resource_level)) else: group.log("current resource level is 0, no one can harvest") - group_harvest_dv.int_value = 0 - group_harvest_dv.save() + group_harvest_dv.update_int(0) ParticipantRoundDataValue.objects.for_group(group, parameter=get_harvest_decision_parameter(), round_data=round_data).update(is_active=False) for pgr in group.participant_group_relationship_set.all(): @@ -362,8 +362,6 @@ ParticipantRoundDataValue.objects.create(participant_group_relationship=pgr, round_data=round_data, parameter=get_harvest_decision_parameter(), int_value=0) - - logger.debug("copying resource levels to next round") ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: @@ -380,6 +378,7 @@ max_resource_level = max_resource_level * group_cluster.size shared_resource_level_dv = get_shared_resource_level_dv(cluster=group_cluster, round_data=round_data) shared_resource_level = shared_resource_level_dv.int_value + shared_regrowth_dv = get_shared_regrowth_dv(cluster=group_cluster, round_data=round_data) # FIXME: set up shared group harvest parameter as well shared_group_harvest = 0 group_cluster_size = 0 @@ -400,14 +399,14 @@ # set regrowth after shared_resource_level has been modified by all groups in this cluster resource_regrowth = calculate_regrowth(shared_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to shared resource level %s" % (resource_regrowth, shared_resource_level)) - group_cluster.set_data_value(parameter=get_regrowth_parameter(), round_data=round_data, value=resource_regrowth) + shared_regrowth_dv.update_int(resource_regrowth) + #group_cluster.set_data_value(parameter=get_regrowth_parameter(), round_data=round_data, value=resource_regrowth) # clamp resource level to max_resource_level - shared_resource_level_dv.int_value = min(shared_resource_level + resource_regrowth, max_resource_level) - shared_resource_level_dv.save() + shared_resource_level_dv.update_int(min(shared_resource_level + resource_regrowth, max_resource_level)) if experiment.has_next_round: ''' transfer shared resource levels to next round ''' group.log("Transferring shared resource level %s to next round" % shared_resource_level_dv.int_value) - group_cluster.copy_to_next_round(shared_resource_level_dv) + group_cluster.copy_to_next_round(shared_resource_level_dv, shared_regrowth_dv) def update_participants(experiment, round_data, round_configuration): logger.debug("updating participants") @@ -424,8 +423,7 @@ # player has "died" player_status_dv.boolean_value = False player_status_dv.save() - storage_dv.int_value = updated_storage - storage_dv.save() + storage_dv.update_int(updated_storage) logger.debug("updating participant %s (storage: %s, harvest: %s, status: %s)", pgr, storage_dv.int_value, harvest_decision, player_status_dv.boolean_value) pgr.copy_to_next_round(player_status_dv, storage_dv, next_round_data=next_round_data) @@ -457,7 +455,7 @@ # FIXME: generify and merge update_shared_resource_level and update_resource_level to operate on "group-like" objects if possible if is_shared_resource_enabled(round_configuration): - for group_cluster in GroupCluster.objects.for_experiment(experiment, session_id=round_configuration.session_id): + for group_cluster in experiment.active_group_clusters: update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate) else: for group in experiment.groups: diff -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f -r 7b40d30dc9c9a1250916c06a9d93e733e6e667fa vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -470,7 +470,7 @@ @property def active_group_clusters(self): - return (group_cluster for group_cluster in self.group_cluster_set.filter(session_id=self.current_session_id)) + return self.group_cluster_set.filter(session_id=self.current_session_id) @property def participant_group_relationships(self): @@ -1458,6 +1458,17 @@ converted_value = self.parameter.convert(obj) setattr(self, self.parameter.value_field_name, converted_value) + def update(self, val): + self.value = val + self.save() + + def update_str(self, str_value): + self.string_value = str_value + self.save() + + def update_int(self, integer_value): + self.int_value = integer_value + self.save() def to_dict(self, cacheable=False, **kwargs): p = self.parameter https://bitbucket.org/virtualcommons/vcweb/commits/988084df2290/ Changeset: 988084df2290 User: alllee Date: 2013-07-19 22:24:04 Summary: adjusting test scaffolding to set initialize_data_values to True on the first round configuration Affected #: 1 file diff -r 7b40d30dc9c9a1250916c06a9d93e733e6e667fa -r 988084df22904d6dc059e209a331ebc9e14fd8d5 vcweb/core/tests.py --- a/vcweb/core/tests.py +++ b/vcweb/core/tests.py @@ -57,7 +57,10 @@ name='Test Experiment Configuration', creator=experimenter) logger.debug("creating new experiment configuration: %s", experiment_configuration) for index in xrange(1, 10): - experiment_configuration.round_configuration_set.create(sequence_number=index) + rc = experiment_configuration.round_configuration_set.create(sequence_number=index) + if index == 1: + rc.initialize_data_values = True + rc.save() logger.debug("created round configurations: %s", experiment_configuration.round_configuration_set.all()) return Experiment.objects.create(experimenter=experimenter, experiment_metadata=experiment_metadata, experiment_configuration=experiment_configuration) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-19 08:03:56
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/d6d6eb67d9cf/ Changeset: d6d6eb67d9cf User: alllee Date: 2013-07-19 10:03:44 Summary: makeshifts to transition total group harvest and group harvest data values from round to round, needs refactoring later TODO: performance improvements on round transitions, qualtrics survey isn't loading with the correct width on initial load Affected #: 5 files diff -r 58343192d9b917510e9aae078c4e859aa78f5fd0 -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -7,7 +7,7 @@ get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level as get_unshared_resource_level, get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, set_resource_level, get_resource_level_parameter, get_resource_level_dv as get_unshared_resource_level_dv, - set_group_harvest, set_regrowth, set_harvest_decision) + set_group_harvest, get_group_harvest_dv, get_regrowth_dv, set_regrowth, set_harvest_decision) from collections import defaultdict import logging @@ -329,6 +329,8 @@ max_resource_level = get_max_resource_level(round_data.round_configuration) current_resource_level_dv = get_resource_level_dv(group, round_data) current_resource_level = current_resource_level_dv.int_value + group_harvest_dv = get_group_harvest_dv(group, round_data) + regrowth_dv = get_regrowth_dv(group, round_data) # FIXME: would be nicer to extend Group behavior and have group.get_total_harvest() instead of # get_total_group_harvest(group, ...), see if we can enable this dynamically total_harvest = get_total_group_harvest(group, round_data) @@ -339,17 +341,20 @@ total_harvest = adjusted_harvest group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) - set_group_harvest(group, total_harvest, round_data) + group_harvest_dv.int_value = total_harvest + group_harvest_dv.save() current_resource_level = current_resource_level - total_harvest resource_regrowth = calculate_regrowth(current_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) - set_regrowth(group, resource_regrowth, round_data) + regrowth_dv.int_value = resource_regrowth + regrowth_dv.save() # clamp resource current_resource_level_dv.int_value = min(current_resource_level + resource_regrowth, max_resource_level) current_resource_level_dv.save() else: group.log("current resource level is 0, no one can harvest") - set_group_harvest(group, 0, round_data) + group_harvest_dv.int_value = 0 + group_harvest_dv.save() ParticipantRoundDataValue.objects.for_group(group, parameter=get_harvest_decision_parameter(), round_data=round_data).update(is_active=False) for pgr in group.participant_group_relationship_set.all(): @@ -359,11 +364,12 @@ int_value=0) + logger.debug("copying resource levels to next round") ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % current_resource_level_dv.int_value) - group.copy_to_next_round(current_resource_level_dv) + group.copy_to_next_round(current_resource_level_dv, group_harvest_dv, regrowth_dv) # FIXME: reduce duplication between this and update_resource_level @@ -374,6 +380,7 @@ max_resource_level = max_resource_level * group_cluster.size shared_resource_level_dv = get_shared_resource_level_dv(cluster=group_cluster, round_data=round_data) shared_resource_level = shared_resource_level_dv.int_value +# FIXME: set up shared group harvest parameter as well shared_group_harvest = 0 group_cluster_size = 0 group_harvest_dict = {} diff -r 58343192d9b917510e9aae078c4e859aa78f5fd0 -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f vcweb/bound/views.py --- a/vcweb/bound/views.py +++ b/vcweb/bound/views.py @@ -156,7 +156,6 @@ own_resource_level = get_resource_level(own_group) if current_round.is_playable_round or current_round.is_debriefing_round: player_data, own_data = get_player_data(own_group, previous_round_data, current_round_data, participant_group_relationship) - logger.debug("player data: %s", player_data) experiment_model_dict.update(own_data) experiment_model_dict['playerData'] = player_data experiment_model_dict['averageHarvest'] = get_average_harvest(own_group, previous_round_data) diff -r 58343192d9b917510e9aae078c4e859aa78f5fd0 -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -98,6 +98,7 @@ if not next_round_data: next_round_data, created = e.get_or_create_round_data(round_configuration=e.next_round) for existing_dv in data_values: + logger.debug("copying existing dv %s to next round %s", existing_dv, next_round_data) # Taking advantage of a trick from here: # http://stackoverflow.com/questions/12182657/copy-or-clone-an-object-instance-in-django-python existing_dv.pk = None diff -r 58343192d9b917510e9aae078c4e859aa78f5fd0 -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f vcweb/core/templates/experimenter/monitor.html --- a/vcweb/core/templates/experimenter/monitor.html +++ b/vcweb/core/templates/experimenter/monitor.html @@ -224,9 +224,6 @@ } model.tick = function() { model.timeRemaining(model.timeRemaining() - 1); - if (model.timeRemaining() % 10 == 0) { - model.checkAllParticipantsReady(); - } if (model.timeRemaining() < 0) { model.addMessage("Round time has expired!"); model.clearCurrentInterval(); @@ -359,11 +356,9 @@ // establish sockjs websocket connection var s = connect("/experimenter"); s.onmessage = function(message) { - console.log("sockjs event: " + message); experiment_event = $.parseJSON(message.data); switch (experiment_event.event_type) { case 'update': - console.debug("FIXME: ignoring update requests, patch sockjs broadcast to not send certain messages"); break; case 'chat': experimentModel.chatMessages.unshift(experiment_event); diff -r 58343192d9b917510e9aae078c4e859aa78f5fd0 -r d6d6eb67d9cfb9a66d5bf1434adfc3c91f60356f vcweb/forestry/models.py --- a/vcweb/forestry/models.py +++ b/vcweb/forestry/models.py @@ -14,10 +14,16 @@ ''' returns the group resource level data value scalar ''' return get_resource_level_dv(group, round_data=round_data, **kwargs).int_value +def get_group_harvest_dv(group, round_data=None): + ''' returns the collective group harvest data value ''' + return group.get_data_value(parameter=get_group_harvest_parameter(), round_data=round_data) + def get_group_harvest(group, round_data=None): ''' returns the collective group harvest data value ''' return group.get_data_value(parameter=get_group_harvest_parameter(), round_data=round_data).int_value +def get_regrowth_dv(group, round_data=None): + return group.get_data_value(parameter=get_regrowth_parameter(), round_data=round_data, default=0) # returns the number of resources regenerated for the given group in the given round def get_regrowth(group, round_data=None): return group.get_data_value(parameter=get_regrowth_parameter(), round_data=round_data, default=0).int_value Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-19 07:00:07
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/58343192d9b9/ Changeset: 58343192d9b9 User: alllee Date: 2013-07-19 08:59:52 Summary: separating data value initialization from copy_to_next_round semantics to avoid get_or_create concurrency issues - the first round configuration of a set of rounds must set the "initialize_data_values" flag to true to create the parameters properly. Affected #: 5 files diff -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 -r 58343192d9b917510e9aae078c4e859aa78f5fd0 vcweb/bound/models.py --- a/vcweb/bound/models.py +++ b/vcweb/bound/models.py @@ -251,18 +251,20 @@ round_data = experiment.get_round_data(round_configuration) logger.debug("setting up round %s", round_configuration) # initialize group and participant data values - if round_configuration.is_playable_round: + if round_configuration.initialize_data_values: experiment.initialize_data_values( group_cluster_parameters=(get_regrowth_parameter(), get_resource_level_parameter(),), group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) ) + if round_configuration.is_playable_round: # check for dead participants and set their ready and harvest decision flags deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), round_data=round_data, boolean_value=False) for prdv in deceased_participants: pgr = prdv.participant_group_relationship set_harvest_decision(pgr, 0, round_data, submitted=True) + pgr.set_participant_ready(round_data) ''' during a practice or regular round, set up resource levels, participant harvest decision parameters, and group formation @@ -451,7 +453,7 @@ for group_cluster in GroupCluster.objects.for_experiment(experiment, session_id=round_configuration.session_id): update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate) else: - for group in experiment.group_set.filter(session_id=round_configuration.session_id): + for group in experiment.groups: update_resource_level(experiment, group, round_data, regrowth_rate) update_participants(experiment, round_data, round_configuration) diff -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 -r 58343192d9b917510e9aae078c4e859aa78f5fd0 vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -278,8 +278,9 @@ </p><h4>Sustainable Harvesting</h4><p> -The highest amount of harvesting that the forest can permanently support is 24 trees per season. If that number is -split between the 4 participants in your group, the forest has a capacity to produce 6 trees per individual per round. +The highest amount of harvesting that the forest can permanently support occurs when the growth rate is fastest. The +highest sustainable amount that can be harvested, when the forest is at half its capacity, is 6 trees per individual or +24 trees per group. </p><ul class='pager'><li class='previous'> @@ -476,7 +477,7 @@ <tbody><tr><td>forest</td><td data-bind='text:resourceLevel'></td></tr><tr><td>average harvest</td><td data-bind='text:averageHarvest().toFixed(1)'></td></tr> - <tr><td>average earnings</td><td data-bind='text:averageStorage().toFixed(1)'></td></tr> + <tr><td>average earnings</td><td data-bind='text:Math.max(0, averageStorage().toFixed(1))'></td></tr><tr><td>number alive</td><td data-bind='text:numberAlive'></td></tr></tbody></table> diff -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 -r 58343192d9b917510e9aae078c4e859aa78f5fd0 vcweb/core/migrations/0009_auto__add_bookmarkedexperimentmetadata__add_unique_bookmarkedexperimen.py --- /dev/null +++ b/vcweb/core/migrations/0009_auto__add_bookmarkedexperimentmetadata__add_unique_bookmarkedexperimen.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'BookmarkedExperimentMetadata' + db.create_table(u'core_bookmarkedexperimentmetadata', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('experimenter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='bookmarked_experiment_metadata_set', to=orm['core.Experimenter'])), + ('experiment_metadata', self.gf('django.db.models.fields.related.ForeignKey')(related_name='bookmarked_experiment_metadata_set', to=orm['core.ExperimentMetadata'])), + ('date_created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + )) + db.send_create_signal(u'core', ['BookmarkedExperimentMetadata']) + + # Adding unique constraint on 'BookmarkedExperimentMetadata', fields ['experimenter', 'experiment_metadata'] + db.create_unique(u'core_bookmarkedexperimentmetadata', ['experimenter_id', 'experiment_metadata_id']) + + # Adding field 'RoundConfiguration.initialize_data_values' + db.add_column(u'core_roundconfiguration', 'initialize_data_values', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Removing unique constraint on 'BookmarkedExperimentMetadata', fields ['experimenter', 'experiment_metadata'] + db.delete_unique(u'core_bookmarkedexperimentmetadata', ['experimenter_id', 'experiment_metadata_id']) + + # Deleting model 'BookmarkedExperimentMetadata' + db.delete_table(u'core_bookmarkedexperimentmetadata') + + # Deleting field 'RoundConfiguration.initialize_data_values' + db.delete_column(u'core_roundconfiguration', 'initialize_data_values') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'core.activitylog': { + 'Meta': {'object_name': 'ActivityLog'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_message': ('django.db.models.fields.TextField', [], {}) + }, + u'core.address': { + 'Meta': {'object_name': 'Address'}, + 'city': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'street1': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'street2': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'zipcode': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}) + }, + u'core.bookmarkedexperimentmetadata': { + 'Meta': {'ordering': "['experimenter', 'experiment_metadata']", 'unique_together': "(('experimenter', 'experiment_metadata'),)", 'object_name': 'BookmarkedExperimentMetadata'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bookmarked_experiment_metadata_set'", 'to': u"orm['core.ExperimentMetadata']"}), + 'experimenter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bookmarked_experiment_metadata_set'", 'to': u"orm['core.Experimenter']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'core.chatmessage': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'ChatMessage', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}), + 'target_participant': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'target_participant_chat_message_set'", 'null': 'True', 'to': u"orm['core.ParticipantGroupRelationship']"}) + }, + u'core.comment': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Comment', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'core.experiment': { + 'Meta': {'ordering': "['date_created', 'status']", 'object_name': 'Experiment'}, + 'amqp_exchange_name': ('django.db.models.fields.CharField', [], {'default': "'vcweb.default.exchange'", 'max_length': '64'}), + 'authentication_code': ('django.db.models.fields.CharField', [], {'default': "'vcweb.auth.code'", 'max_length': '32'}), + 'current_repeated_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'current_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'current_round_start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'duration': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentConfiguration']"}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentMetadata']"}), + 'experimenter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experimenter']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'INACTIVE'", 'max_length': '32'}), + 'tick_duration': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}) + }, + u'core.experimentactivitylog': { + 'Meta': {'object_name': 'ExperimentActivityLog', '_ormbases': [u'core.ActivityLog']}, + u'activitylog_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ActivityLog']", 'unique': 'True', 'primary_key': 'True'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log_set'", 'to': u"orm['core.Experiment']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.experimentconfiguration': { + 'Meta': {'ordering': "['experiment_metadata', 'creator', 'date_created']", 'object_name': 'ExperimentConfiguration'}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_configuration_set'", 'to': u"orm['core.Experimenter']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'exchange_rate': ('django.db.models.fields.DecimalField', [], {'default': '0.2', 'null': 'True', 'max_digits': '6', 'decimal_places': '2', 'blank': 'True'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_configuration_set'", 'to': u"orm['core.ExperimentMetadata']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation_subject': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'invitation_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_experimenter_driven': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'max_group_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5'}), + 'max_number_of_participants': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'treatment_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}) + }, + u'core.experimenter': { + 'Meta': {'ordering': "['user']", 'object_name': 'Experimenter'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'authentication_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'failed_password_attempts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'institution': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Institution']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'experimenter'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'core.experimenterrequest': { + 'Meta': {'object_name': 'ExperimenterRequest'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'core.experimentmetadata': { + 'Meta': {'ordering': "['namespace', 'date_created']", 'object_name': 'ExperimentMetadata'}, + 'about_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentConfiguration']", 'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'namespace': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'short_name': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'core.experimentparametervalue': { + 'Meta': {'object_name': 'ExperimentParameterValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.ExperimentConfiguration']"}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + u'core.experimentsession': { + 'Meta': {'object_name': 'ExperimentSession'}, + 'capacity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '20'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_session_set'", 'to': u"orm['auth.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_session_set'", 'to': u"orm['core.ExperimentMetadata']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'scheduled_date': ('django.db.models.fields.DateTimeField', [], {}), + 'scheduled_end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.group': { + 'Meta': {'ordering': "['experiment', 'number']", 'object_name': 'Group'}, + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5'}), + 'number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'session_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}) + }, + u'core.groupactivitylog': { + 'Meta': {'object_name': 'GroupActivityLog', '_ormbases': [u'core.ActivityLog']}, + u'activitylog_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ActivityLog']", 'unique': 'True', 'primary_key': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log_set'", 'to': u"orm['core.Group']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.groupcluster': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'GroupCluster'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_cluster_set'", 'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'session_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}) + }, + u'core.groupclusterdatavalue': { + 'Meta': {'object_name': 'GroupClusterDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'group_cluster': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.GroupCluster']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_cluster_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + u'core.grouprelationship': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'GroupRelationship'}, + 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_relationship_set'", 'to': u"orm['core.GroupCluster']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'core.grouprounddatavalue': { + 'Meta': {'ordering': "['round_data', 'group', 'parameter']", 'object_name': 'GroupRoundDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + u'core.institution': { + 'Meta': {'object_name': 'Institution'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + u'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment_session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentSession']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Participant']"}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'core.like': { + 'Meta': {'ordering': "['-date_created', 'round_data', 'participant_group_relationship', 'parameter']", 'object_name': 'Like', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'core.parameter': { + 'Meta': {'ordering': "['name']", 'unique_together': "(('name', 'experiment_metadata', 'scope'),)", 'object_name': 'Parameter'}, + 'class_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experimenter']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_value_string': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'display_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'enum_choices': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentMetadata']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'scope': ('django.db.models.fields.CharField', [], {'default': "'round'", 'max_length': '32'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + u'core.participant': { + 'Meta': {'ordering': "['user']", 'object_name': 'Participant'}, + 'address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Address']", 'null': 'True', 'blank': 'True'}), + 'authentication_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'birthdate': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'can_receive_invitations': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'experiments': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'participant_set'", 'symmetrical': 'False', 'through': u"orm['core.ParticipantExperimentRelationship']", 'to': u"orm['core.Experiment']"}), + 'failed_password_attempts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'gender': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'participant_set'", 'symmetrical': 'False', 'through': u"orm['core.ParticipantGroupRelationship']", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'institution': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Institution']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'participant'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'core.participantexperimentrelationship': { + 'Meta': {'object_name': 'ParticipantExperimentRelationship'}, + 'additional_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'current_location': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_relationship_set'", 'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_completed_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_relationship_set'", 'to': u"orm['core.Participant']"}), + 'participant_identifier': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'sequential_participant_identifier': ('django.db.models.fields.PositiveIntegerField', [], {}) + }, + u'core.participantgrouprelationship': { + 'Meta': {'ordering': "['group', 'participant_number']", 'object_name': 'ParticipantGroupRelationship'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'first_visit': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_group_relationship_set'", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notifications_since': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_group_relationship_set'", 'to': u"orm['core.Participant']"}), + 'participant_number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'round_joined': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.participantrounddatavalue': { + 'Meta': {'ordering': "['-date_created', 'round_data', 'participant_group_relationship', 'parameter']", 'object_name': 'ParticipantRoundDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.ParticipantGroupRelationship']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'submitted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'target_data_value': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'target_data_value_set'", 'null': 'True', 'to': u"orm['core.ParticipantRoundDataValue']"}) + }, + u'core.participantsignup': { + 'Meta': {'object_name': 'ParticipantSignup'}, + 'attendance': ('django.db.models.fields.PositiveIntegerField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signup_set'", 'to': u"orm['core.Invitation']"}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signup_set'", 'to': u"orm['core.Participant']"}) + }, + u'core.quizquestion': { + 'Meta': {'object_name': 'QuizQuestion'}, + 'answer': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_quiz_question_set'", 'null': 'True', 'to': u"orm['core.Experiment']"}), + 'explanation': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'input_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'quiz_question_set'", 'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.roundconfiguration': { + 'Meta': {'ordering': "['experiment_configuration', 'sequence_number', 'date_created']", 'object_name': 'RoundConfiguration'}, + 'chat_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'create_group_clusters': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'debriefing': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'duration': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_configuration_set'", 'to': u"orm['core.ExperimentConfiguration']"}), + 'group_cluster_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'initialize_data_values': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'instructions': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'preserve_existing_groups': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'randomize_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'repeat': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'round_type': ('django.db.models.fields.CharField', [], {'default': "'REGULAR'", 'max_length': '32'}), + 'sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'session_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}), + 'survey_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'template_filename': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'template_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}) + }, + u'core.rounddata': { + 'Meta': {'ordering': "['round_configuration']", 'unique_together': "(('round_configuration', 'repeating_round_sequence_number', 'experiment'),)", 'object_name': 'RoundData'}, + 'elapsed_time': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_data_set'", 'to': u"orm['core.Experiment']"}), + 'experimenter_notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'repeating_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_data_set'", 'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.roundparametervalue': { + 'Meta': {'object_name': 'RoundParameterValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.RoundConfiguration']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + u'core.spoolparticipantstatistics': { + 'Meta': {'object_name': 'SpoolParticipantStatistics'}, + 'absences': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'discharges': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitations': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'spool_statistics_set'", 'to': u"orm['core.Participant']"}), + 'participations': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + } + } + + complete_apps = ['core'] \ No newline at end of file diff -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 -r 58343192d9b917510e9aae078c4e859aa78f5fd0 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -726,16 +726,19 @@ users.append(user) self.register_participants(users=users, institution=institution, password=password) -# FIXME: figure out how to declaratively do this so experiments can more easily notify "I have these data values to -# initialize at the start of each round. -# XXX: it can be dangerous to use empty lists as initial keyword args but we only iterate over them (e.g., -# http://effbot.org/zone/default-values.htm) -# defaults map parameter model instances to their default initial value, e.g., { footprint-level-parameter: 1, resource-level-parameter: 100 } def initialize_data_values(self, group_parameters=[], participant_parameters=[], group_cluster_parameters=[], round_data=None, defaults={}): - logger.debug("initializing [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) + """ + FIXME: needs refactoring, replace get_or_create with creates and separate initialization of data values from copy_to_next_round semantics + Issues: + Make it simple for experimenters/experiments to signal "I have these data values to initialize at the start of each round" + Overly complex logic, possible danger to use empty lists as initial keyword args but we only iterate over them (e.g., http://effbot.org/zone/default-values.htm) + get_or_create logic can create degenerate data (duplicate group round data values for instance) + """ + logger.debug("initializing data values for [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) if round_data is None: round_data = self.current_round_data parameter_defaults = defaultdict(dict) + # defaults map parameter model instances to their default initial value, e.g., { footprint-level-parameter: 1, resource-level-parameter: 100 } for parameter in itertools.chain(participant_parameters, group_parameters, group_cluster_parameters): if parameter in defaults: parameter_defaults[parameter] = { parameter.value_field_name: defaults[parameter] } @@ -746,17 +749,17 @@ for parameter in group_cluster_parameters: gcdv, created = GroupClusterDataValue.objects.get_or_create(round_data=round_data, parameter=parameter, group_cluster=group_cluster, defaults=parameter_defaults[parameter]) - #logger.debug("%s (%s)", gcdv, created) + logger.debug("gcdv: %s (%s)", gcdv, created) for group in self.groups: for parameter in group_parameters: group_data_value, created = GroupRoundDataValue.objects.get_or_create(round_data=round_data, group=group, parameter=parameter, defaults=parameter_defaults[parameter]) - #logger.debug("%s (%s)", group_data_value, created) + logger.debug("grdv: %s (%s)", group_data_value, created) if participant_parameters: for pgr in group.participant_group_relationship_set.all(): for parameter in participant_parameters: participant_data_value, created = ParticipantRoundDataValue.objects.get_or_create(round_data=round_data, participant_group_relationship=pgr, parameter=parameter, defaults=parameter_defaults[parameter]) - # logger.debug("%s (%s)", participant_data_value, created) + logger.debug("prdv: %s (%s)", participant_data_value, created) def log(self, log_message): if log_message: @@ -911,7 +914,7 @@ ParticipantRoundDataValue.objects.get_or_create(participant_group_relationship=pgr, parameter=get_participant_ready_parameter(), round_data=round_data, defaults={'boolean_value': False}) if not created: - logger.debug("already created round data: %s", round_data) + logger.debug("round data already created: %s", round_data) return round_data, created def start_round(self, sender=None): @@ -1140,6 +1143,7 @@ Group/ParticipantGroupRelationship models. ''')) repeat = models.PositiveIntegerField(default=0, help_text=_('If set to a positive integer n, this round will repeat itself n times with the same configuration and parameter values.')) + initialize_data_values = models.BooleanField(default=False, help_text=_("Re-initialize all group and participant parameters at the start of this round. ")) @property def custom_template_filename(self): diff -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 -r 58343192d9b917510e9aae078c4e859aa78f5fd0 vcweb/core/templates/experimenter/monitor.html --- a/vcweb/core/templates/experimenter/monitor.html +++ b/vcweb/core/templates/experimenter/monitor.html @@ -217,7 +217,6 @@ model.currentInterval(intervalId); } model.clearCurrentInterval = function() { - console.debug("clearing current interval: " + model.currentInterval()); if (model.currentInterval()) { clearInterval(model.currentInterval()); model.currentInterval(undefined); Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-18 08:18:00
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/893fc0aa8ed1/ Changeset: 893fc0aa8ed1 User: alllee Date: 2013-07-18 10:17:48 Summary: removing sidebar toggle from activate template which is too broad to cover it properly Affected #: 1 file diff -r e5e7dc110a9554eee700aa651e3a35053077770a -r 893fc0aa8ed1fc6580585d274d69a01fda8a7ab3 vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -92,7 +92,7 @@ </div><ul class='pager'><li class='next'> - <a href='javascript: void()' data-bind='click: activateTemplate.bind($data, "PAID_EXPERIMENT_INSTRUCTIONS", true)'>Ok, continue</a> + <a href='javascript: void()' data-bind='click: completePracticeRoundSurvey'>Ok, continue</a></li></ul></script> @@ -615,13 +615,15 @@ return model.secondsLeft() > 0; }); // activate instructions click bindings - model.activateTemplate = function(name, toggleSidebar, experimentModel) { + model.activateTemplate = function(name, experimentModel) { model.templateName(name); - if (toggleSidebar) { - $('#sidebar').show(); - $('#content').toggleClass('span8 span12'); - } } + model.completePracticeRoundSurvey = function() { + console.debug("completing practice round survey"); + $('#sidebar').show(); + $('#content').toggleClass('span8 span12'); + model.activateTemplate("PAID_EXPERIMENT_INSTRUCTIONS"); + }; model.readyParticipantsPercentage = ko.computed(function() { return (model.readyParticipants() / model.participantCount()) * 100; }); @@ -769,7 +771,7 @@ } // FIXME: should move to a model where we only enable chat in REGULAR rounds? model.initializeChat(); - if (model.isSurveyEnabled()) { + if (model.isSurveyEnabled() && model.templateId() === "PRACTICE_ROUND_RESULTS") { $('#sidebar').hide(); $('#content').toggleClass('span8 span12'); } Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-18 00:05:10
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/e5e7dc110a95/ Changeset: e5e7dc110a95 User: alllee Date: 2013-07-18 02:04:57 Summary: qualtrics survey is now embedded as an iframe instead of an external link TODO: - wire up survey completion with moving on in the experiment - iframe styling Affected #: 7 files diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -73,16 +73,26 @@ <script type='text/html' id='PRACTICE_ROUND_RESULTS'><h3>Practice Round Results</h3><p> - Your final savings were <span class='badge badge-success' data-bind='text: storage'></span> points. + Your final savings were <span class='badge badge-success' data-bind='text: storage'></span> tokens. If this had been been a paid session, you would have earned <strong class='text-success' data-bind='text: totalEarnings'></strong>. </p><p> We will now move on to the <strong>paid rounds.</strong> - Before you continue, please <a target='_blank' data-bind='attr: { href: surveyUrl }'>fill out a questionnaire by clicking this link.</a> + Before you continue, please fill out the following questionnaire. </p> + <div class='qualtrics-survey'> + <iframe width="100%" height="800" data-bind='attr: { src: surveyUrl }'></iframe> + </div> + {% comment %} + need to disable this until the survey is completed + {% endcomment %} + <div class='alert alert-error'> + FIXME: this button should be initially disabled when we wire up proper activation from the qualtrics survey and then enabled after the + qualtrics survey is completed + </div><ul class='pager'><li class='next'> - <a href='javascript: void()' data-bind='click: activateTemplate.bind($data, "PAID_EXPERIMENT_INSTRUCTIONS")'>Ok, continue</a> + <a href='javascript: void()' data-bind='click: activateTemplate.bind($data, "PAID_EXPERIMENT_INSTRUCTIONS", true)'>Ok, continue</a></li></ul></script> @@ -127,8 +137,14 @@ Thank you for participating. As you recall from the initial instructions, we will pay you for one of the two experimental sessions you just completed. You will select, at random, which session you will be paid for by flipping a coin. The experimenter will be with you shortly to assist you with this process. While you wait, please - <a data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a> + fill out the following questionnaire. </p> + + <div class='qualtrics-survey'> + <iframe width="100%" height="100%" data-bind='attr: { src: surveyUrl }'> + Your browser doesn't seem to support iframes, please <a data-bind='attr: { href: surveyUrl }'>follow this link to take the survey.</a> + </iframe> + </div><h2>Payments</h2><table class='table'><thead> @@ -222,7 +238,7 @@ <h4>Cost of living</h4><p> Each round you must pay living expenses of <span class='badge badge-important' data-bind='text: costOfLiving'></span> -savings points. If at any point you do not have enough savings to pay the living expenses, you will not become +tokens. If at any point you do not have enough savings to pay the living expenses, you will become "deceased" for the remainder of the session, preventing you from harvesting or receiving payment for that session. You will need to wait until the session is complete before joining again. </p> @@ -282,7 +298,7 @@ You have been assigned to a random group with <strong data-bind='text:participantsPerGroup'></strong> members. In this experiment you will be sharing a virtual forest with the <span data-bind='text: participantsPerGroup() - 1'></span> other members in your group. All participants in the experiment are in identical conditions and must harvest -trees to pay the cost of living (<span data-bind='text: costOfLiving'></span> savings points per round) to make a profit +trees to pay the cost of living (<span data-bind='text: costOfLiving'></span> tokens per round) to make a profit and accrue savings. You will be able to text chat only with members of your own group. </p><p> @@ -387,7 +403,7 @@ <tr><td>Earnings</td><!-- ko foreach: playerData --> - <td data-bind='text: storage, css: { "current-player": id() === $root.participantGroupId() }'></td> + <td data-bind='text: Math.max(0, storage()), css: { "current-player": id() === $root.participantGroupId() }'></td><!-- /ko --></tr><tr> @@ -587,8 +603,7 @@ model.currentInterval(intervalId); } model.clearCurrentInterval = function() { - console.debug("clearing current interval: " + model.currentInterval()); - if (model.currentInterval()) { + if (model.currentInterval()) { clearInterval(model.currentInterval()); model.currentInterval(undefined); } @@ -600,16 +615,18 @@ return model.secondsLeft() > 0; }); // activate instructions click bindings - model.activateTemplate = function(name) { + model.activateTemplate = function(name, toggleSidebar, experimentModel) { model.templateName(name); + if (toggleSidebar) { + $('#sidebar').show(); + $('#content').toggleClass('span8 span12'); + } } model.readyParticipantsPercentage = ko.computed(function() { return (model.readyParticipants() / model.participantCount()) * 100; }); model.participantReady = function(message) { $.post('/experiment/participant-ready', { participant_group_id: model.participantGroupId() }, function(response) { - console.debug("successfully posted to server, notifying sockjs"); - console.debug(response); model.readyParticipants(response.number_of_ready_participants); getWebSocket().send(createReadyEvent(message)); model.activateTemplate("WAITING_ROOM"); @@ -648,7 +665,6 @@ }; model.blockResourceVisualizationImageHeight = function(resourceLevel) { var rowsToDisplay = model.rowsToDisplay(resourceLevel); - console.debug("displaying " + rowsToDisplay + " rows."); return (rowsToDisplay * model.resourceImageHeight()) + "px"; }; model.remainderResourceImageWidth = function(resourceLevel) { @@ -659,7 +675,6 @@ if (model.showTour()) { model.setupTour(); } - console.debug("starting round"); if (isInt(model.timeRemaining()) && model.timeRemaining() > 0) { model.secondsLeft(model.timeRemaining()); model.setCurrentInterval( @@ -681,11 +696,8 @@ } model.submitDecision = function() { model.endTour(); - console.debug("submitting decision for form " + model.harvestDecisionFormId() + " with harvest decision:" + model.harvestDecision()); - $.post('submit-harvest-decision', - {participant_group_id: model.participantGroupId(),integer_decision: model.harvestDecision(), submitted: true }, + $.post('submit-harvest-decision', {participant_group_id: model.participantGroupId(),integer_decision: model.harvestDecision(), submitted: true }, function(response) { - console.debug(response); // hide selection, disable chat, show waiting room model.participantReady(response.message); model.clearCurrentInterval(); @@ -701,9 +713,7 @@ } var formId = "#" + model.harvestDecisionFormId(numberOfTrees) var form = $(formId); - console.debug("formId: " + formId + " -- number of trees: " + numberOfTrees); var formData = form.serialize(); - console.debug(formData); $.post('submit-harvest-decision', formData, function(response) { model.selectedHarvestDecision(true); model.harvestDecision(numberOfTrees); @@ -722,8 +732,6 @@ } model.update = function() { $.get('view-model', { participant_group_id: model.participantGroupId() }, function(data) { - console.debug("retrieved view model successfully"); - console.debug(data); ko.mapping.fromJS(data, model); model.afterRenderTemplate(); $('#progress-modal').modal('hide'); @@ -748,7 +756,7 @@ model.disableChatForm = function() { model.setFormDisabled("#chat-form", true); } - model.setupChat = function() { + model.initializeChat = function() { // FIXME: chat is disabled in practice rounds, when it's explicitly disabled, or if the participant is // no longer alive var chatDisabled = model.isPracticeRound() || ! model.chatEnabled() || ! model.alive(); @@ -759,7 +767,12 @@ if (model.templateId() === "REGULAR") { model.startRound() } - model.setupChat(); + // FIXME: should move to a model where we only enable chat in REGULAR rounds? + model.initializeChat(); + if (model.isSurveyEnabled()) { + $('#sidebar').hide(); + $('#content').toggleClass('span8 span12'); + } $('[data-content]').popover({placement: 'top', trigger: 'hover'}); } return model; @@ -769,12 +782,9 @@ ko.applyBindings(experimentModel); var s = connect(); s.onmessage = function(message) { - console.debug(message); var data = $.parseJSON(message.data); - console.debug(data); switch (data.event_type) { case 'chat': - console.debug("received chat message:" + message); experimentModel.chatMessages.unshift(data); break; case 'update': diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/bound/views.py --- a/vcweb/bound/views.py +++ b/vcweb/bound/views.py @@ -92,6 +92,7 @@ 'averageHarvest': 0, 'averageStorage': 0, 'numberAlive': '4 out of 4', + 'isSurveyEnabled': False, 'surveyUrl': 'http://survey.qualtrics.com/SE/?SID=SV_0vzmIj5UsOgjoTX', } # FIXME: bloated method with too many special cases, try to refactor @@ -132,7 +133,6 @@ experiment_model_dict['sessonTwoStorage'] = session_two_storage if current_round.is_survey_enabled: - logger.debug("survey was enabled") query_parameters = urlencode({ 'pid': participant_group_relationship.pk, 'eid': experiment.pk, @@ -143,6 +143,8 @@ if separator in survey_url: separator = '&' experiment_model_dict['surveyUrl'] = "{0}{1}{2}".format(current_round.survey_url, separator, query_parameters) + experiment_model_dict['isSurveyEnabled'] = True + logger.debug("survey was enabled, setting survey url to %s", experiment_model_dict['surveyUrl']) # participant data diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/core/ajax.py --- a/vcweb/core/ajax.py +++ b/vcweb/core/ajax.py @@ -121,7 +121,7 @@ experiment_configuration=experiment_configuration, status=Experiment.Status.INACTIVE ) - return JsonResponse(dumps({'success': True, 'experiment': e.to_dict(attrs=('monitor_url', 'status_line',)) })) + return JsonResponse(dumps({'success': True, 'experiment': e.to_dict(attrs=('monitor_url', 'status_line','controller_url',)) })) @experimenter_required diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -1027,6 +1027,7 @@ 'exchangeRate': float(ec.exchange_rate), 'readyParticipants': self.number_of_ready_participants, 'status': self.status, + 'pk': self.pk, }) if include_round_data: # XXX: stubs for round data diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -95,7 +95,8 @@ Dajaxice.vcweb.core.create_experiment(function(data) { if (data.success) { pendingExperimentModel = ko.mapping.fromJS(data.experiment); - model.pendingExperiments.push(pendingExperimentModel); + model.pendingExperiments.unshift(pendingExperimentModel); + model.activatePendingExperimentsTab(); } }, { experiment_configuration_id: configurationModel.pk() }); diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/core/templates/includes/sockjs.html --- a/vcweb/core/templates/includes/sockjs.html +++ b/vcweb/core/templates/includes/sockjs.html @@ -1,6 +1,4 @@ <script type="text/javascript" src="https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js"></script> -<!--<script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>--> -<!--<script type="text/javascript" src="{{STATIC_URL}}js/sockjs-0.3.min.js"></script>--><script type="text/javascript"> var socket; var DEFAULT_PORT = {{WEBSOCKET_PORT|default_if_none:8882}}; @@ -23,14 +21,13 @@ console.log("Establishing connection to " + sockjsServer); socket = new SockJS(sockjsServer); socket.onopen = function() { - console.log('default vcweb sockjs open'); socket.send(createConnectionEvent()); }; socket.onmessage = function(e) { console.log('received message', e.data); }; socket.onclose = function() { - console.log('default vcweb sockjs closed'); + console.log('sockjs connection closed'); }; console.log("connecting to " + host + ":" + port + uri); return socket; diff -r 8784e849cb30c4921a55b23774219fda3670aa38 -r e5e7dc110a9554eee700aa651e3a35053077770a vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -94,7 +94,7 @@ data = em.to_dict(include_configurations=True) experiment_metadata_list.append(data) experiment_status_dict = defaultdict(list) - for e in Experiment.objects.for_experimenter(experimenter): + for e in Experiment.objects.for_experimenter(experimenter).order_by('-pk'): experiment_status_dict[e.status].append(e.to_dict(attrs=('monitor_url', 'status_line', 'controller_url'))) pending_experiments = experiment_status_dict['INACTIVE'] running_experiments = experiment_status_dict['ACTIVE'] + experiment_status_dict['ROUND_IN_PROGRESS'] Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-17 19:14:29
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/8784e849cb30/ Changeset: 8784e849cb30 User: alllee Date: 2013-07-17 21:14:03 Summary: version bumps on JS libraries Affected #: 4 files diff -r 9aa770b95a35bb62e36f86c98a6a5e3ddd2e98bf -r 8784e849cb30c4921a55b23= 774219fda3670aa38 vcweb/static/js/bootbox.min.js --- a/vcweb/static/js/bootbox.min.js +++ b/vcweb/static/js/bootbox.min.js @@ -1,17 +1,6 @@ /** - * bootbox.js v3.2.0 + * bootbox.js v3.3.0 * * http://bootboxjs.com/license.txt */ -var bootbox=3Dwindow.bootbox||function(w,n){function k(b,a){"undefined"=3D= =3D=3Dtypeof a&&(a=3Dp);return"string"=3D=3D=3Dtypeof j[a][b]?j[a][b]:a!=3D= t?k(b,t):b}var p=3D"en",t=3D"en",u=3D!0,s=3D"static",v=3D"",l=3D{},g=3D{},m= =3D{setLocale:function(b){for(var a in j)if(a=3D=3Db){p=3Db;return}throw Er= ror("Invalid locale: "+b);},addLocale:function(b,a){"undefined"=3D=3D=3Dtyp= eof j[b]&&(j[b]=3D{});for(var c in a)j[b][c]=3Da[c]},setIcons:function(b){g= =3Db;if("object"!=3D=3Dtypeof g||null=3D=3D=3Dg)g=3D{}},setBtnClasses:funct= ion(b){l=3Db;if("object"!=3D=3Dtypeof l||null=3D=3D=3D -l)l=3D{}},alert:function(){var b=3D"",a=3Dk("OK"),c=3Dnull;switch(argument= s.length){case 1:b=3Darguments[0];break;case 2:b=3Darguments[0];"function"= =3D=3Dtypeof arguments[1]?c=3Darguments[1]:a=3Darguments[1];break;case 3:b= =3Darguments[0];a=3Darguments[1];c=3Darguments[2];break;default:throw Error= ("Incorrect number of arguments: expected 1-3");}return m.dialog(b,{label:a= ,icon:g.OK,"class":l.OK,callback:c},{onEscape:c||!0})},confirm:function(){v= ar b=3D"",a=3Dk("CANCEL"),c=3Dk("CONFIRM"),e=3Dnull;switch(arguments.length= ){case 1:b=3Darguments[0]; -break;case 2:b=3Darguments[0];"function"=3D=3Dtypeof arguments[1]?e=3Dargu= ments[1]:a=3Darguments[1];break;case 3:b=3Darguments[0];a=3Darguments[1];"f= unction"=3D=3Dtypeof arguments[2]?e=3Darguments[2]:c=3Darguments[2];break;c= ase 4:b=3Darguments[0];a=3Darguments[1];c=3Darguments[2];e=3Darguments[3];b= reak;default:throw Error("Incorrect number of arguments: expected 1-4");}va= r h=3Dfunction(){if("function"=3D=3D=3Dtypeof e)return e(!1)};return m.dial= og(b,[{label:a,icon:g.CANCEL,"class":l.CANCEL,callback:h},{label:c,icon:g.C= ONFIRM,"class":l.CONFIRM, -callback:function(){if("function"=3D=3D=3Dtypeof e)return e(!0)}}],{onEsca= pe:h})},prompt:function(){var b=3D"",a=3Dk("CANCEL"),c=3Dk("CONFIRM"),e=3Dn= ull,h=3D"";switch(arguments.length){case 1:b=3Darguments[0];break;case 2:b= =3Darguments[0];"function"=3D=3Dtypeof arguments[1]?e=3Darguments[1]:a=3Dar= guments[1];break;case 3:b=3Darguments[0];a=3Darguments[1];"function"=3D=3Dt= ypeof arguments[2]?e=3Darguments[2]:c=3Darguments[2];break;case 4:b=3Dargum= ents[0];a=3Darguments[1];c=3Darguments[2];e=3Darguments[3];break;case 5:b= =3Darguments[0];a=3Darguments[1]; -c=3Darguments[2];e=3Darguments[3];h=3Darguments[4];break;default:throw Err= or("Incorrect number of arguments: expected 1-5");}var q=3Dn("<form></form>= ");q.append("<input autocomplete=3Doff type=3Dtext value=3D'"+h+"' />");var= h=3Dfunction(){if("function"=3D=3D=3Dtypeof e)return e(null)},d=3Dm.dialog= (q,[{label:a,icon:g.CANCEL,"class":l.CANCEL,callback:h},{label:c,icon:g.CON= FIRM,"class":l.CONFIRM,callback:function(){if("function"=3D=3D=3Dtypeof e)r= eturn e(q.find("input[type=3Dtext]").val())}}],{header:b,show:!1,onEscape:h= });d.on("shown", -function(){q.find("input[type=3Dtext]").focus();q.on("submit",function(a){= a.preventDefault();d.find(".btn-primary").click()})});d.modal("show");retur= n d},dialog:function(b,a,c){function e(){var a=3Dnull;"function"=3D=3D=3Dty= peof c.onEscape&&(a=3Dc.onEscape());!1!=3D=3Da&&f.modal("hide")}var h=3D"",= l=3D[];c||(c=3D{});"undefined"=3D=3D=3Dtypeof a?a=3D[]:"undefined"=3D=3Dtyp= eof a.length&&(a=3D[a]);for(var d=3Da.length;d--;){var g=3Dnull,k=3Dnull,j= =3Dnull,m=3D"",p=3Dnull;if("undefined"=3D=3Dtypeof a[d].label&&"undefined"= =3D=3Dtypeof a[d]["class"]&&"undefined"=3D=3D -typeof a[d].callback){var g=3D0,k=3Dnull,r;for(r in a[d])if(k=3Dr,1<++g)br= eak;1=3D=3Dg&&"function"=3D=3Dtypeof a[d][r]&&(a[d].label=3Dk,a[d].callback= =3Da[d][r])}"function"=3D=3Dtypeof a[d].callback&&(p=3Da[d].callback);a[d][= "class"]?j=3Da[d]["class"]:d=3D=3Da.length-1&&2>=3Da.length&&(j=3D"btn-prim= ary");g=3Da[d].label?a[d].label:"Option "+(d+1);a[d].icon&&(m=3D"<i class= =3D'"+a[d].icon+"'></i> ");k=3Da[d].href?a[d].href:"javascript:;";h=3D"<a d= ata-handler=3D'"+d+"' class=3D'btn "+j+"' href=3D'"+k+"'>"+m+""+g+"</a>"+h;= l[d]=3Dp}d=3D["<div class=3D'bootbox modal' tabindex=3D'-1' style=3D'overfl= ow:hidden;'>"]; -if(c.header){j=3D"";if("undefined"=3D=3Dtypeof c.headerCloseButton||c.head= erCloseButton)j=3D"<a href=3D'javascript:;' class=3D'close'>×</a>";d.= push("<div class=3D'modal-header'>"+j+"<h3>"+c.header+"</h3></div>")}d.push= ("<div class=3D'modal-body'></div>");h&&d.push("<div class=3D'modal-footer'= >"+h+"</div>");d.push("</div>");var f=3Dn(d.join("\n"));("undefined"=3D=3D= =3Dtypeof c.animate?u:c.animate)&&f.addClass("fade");(h=3D"undefined"=3D=3D= =3Dtypeof c.classes?v:c.classes)&&f.addClass(h);f.find(".modal-body").html(= b);f.on("keyup.dismiss.modal", -function(a){27=3D=3D=3Da.which&&c.onEscape&&e("escape")});f.on("click","a.= close",function(a){a.preventDefault();e("close")});f.on("shown",function(){= f.find("a.btn-primary:first").focus()});f.on("hidden",function(){f.remove()= });f.on("click",".modal-footer a",function(b){var c=3Dn(this).data("handler= "),d=3Dl[c],e=3Dnull;"undefined"!=3D=3Dtypeof c&&"undefined"!=3D=3Dtypeof a= [c].href||(b.preventDefault(),"function"=3D=3D=3Dtypeof d&&(e=3Dd()),!1!=3D= =3De&&f.modal("hide"))});n("body").append(f);f.modal({backdrop:"undefined"= =3D=3D=3Dtypeof c.backdrop? -s:c.backdrop,keyboard:!1,show:!1});f.on("show",function(){n(w).off("focusi= n.modal")});("undefined"=3D=3D=3Dtypeof c.show||!0=3D=3D=3Dc.show)&&f.modal= ("show");return f},modal:function(){var b,a,c,e=3D{onEscape:null,keyboard:!= 0,backdrop:s};switch(arguments.length){case 1:b=3Darguments[0];break;case 2= :b=3Darguments[0];"object"=3D=3Dtypeof arguments[1]?c=3Darguments[1]:a=3Dar= guments[1];break;case 3:b=3Darguments[0];a=3Darguments[1];c=3Darguments[2];= break;default:throw Error("Incorrect number of arguments: expected 1-3");}e= .header=3Da; -c=3D"object"=3D=3Dtypeof c?n.extend(e,c):e;return m.dialog(b,[],c)},hideAl= l:function(){n(".bootbox").modal("hide")},animate:function(b){u=3Db},backdr= op:function(b){s=3Db},classes:function(b){v=3Db}},j=3D{en:{OK:"OK",CANCEL:"= Cancel",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},de:{= OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},es:{OK:"OK",CANCEL:"Cance= lar",CONFIRM:"Aceptar"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},nl:{OK= :"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},ru:{OK:"OK",CANCEL:"\u041e\u= 0442\u043c\u0435\u043d\u0430", -CONFIRM:"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c"},it:{OK:"= OK",CANCEL:"Annulla",CONFIRM:"Conferma"}};return m}(document,window.jQuery)= ;window.bootbox=3Dbootbox; +var bootbox=3Dwindow.bootbox||function(a,b){function c(a,b){return"undefin= ed"=3D=3Dtypeof b&&(b=3Dd),"string"=3D=3Dtypeof m[b][a]?m[b][a]:b!=3De?c(a,= e):a}var d=3D"en",e=3D"en",f=3D!0,g=3D"static",h=3D"javascript:;",i=3D"",j= =3D{},k=3D{},l=3D{};l.setLocale=3Dfunction(a){for(var b in m)if(b=3D=3Da)re= turn d=3Da,void 0;throw new Error("Invalid locale: "+a)},l.addLocale=3Dfunc= tion(a,b){"undefined"=3D=3Dtypeof m[a]&&(m[a]=3D{});for(var c in b)m[a][c]= =3Db[c]},l.setIcons=3Dfunction(a){k=3Da,("object"!=3Dtypeof k||null=3D=3D= =3Dk)&&(k=3D{})},l.setBtnClasses=3Dfunction(a){j=3Da,("object"!=3Dtypeof j|= |null=3D=3D=3Dj)&&(j=3D{})},l.alert=3Dfunction(){var a=3D"",b=3Dc("OK"),d= =3Dnull;switch(arguments.length){case 1:a=3Darguments[0];break;case 2:a=3Da= rguments[0],"function"=3D=3Dtypeof arguments[1]?d=3Darguments[1]:b=3Dargume= nts[1];break;case 3:a=3Darguments[0],b=3Darguments[1],d=3Darguments[2];brea= k;default:throw new Error("Incorrect number of arguments: expected 1-3")}re= turn l.dialog(a,{label:b,icon:k.OK,"class":j.OK,callback:d},{onEscape:d||!0= })},l.confirm=3Dfunction(){var a=3D"",b=3Dc("CANCEL"),d=3Dc("CONFIRM"),e=3D= null;switch(arguments.length){case 1:a=3Darguments[0];break;case 2:a=3Dargu= ments[0],"function"=3D=3Dtypeof arguments[1]?e=3Darguments[1]:b=3Darguments= [1];break;case 3:a=3Darguments[0],b=3Darguments[1],"function"=3D=3Dtypeof a= rguments[2]?e=3Darguments[2]:d=3Darguments[2];break;case 4:a=3Darguments[0]= ,b=3Darguments[1],d=3Darguments[2],e=3Darguments[3];break;default:throw new= Error("Incorrect number of arguments: expected 1-4")}var f=3Dfunction(){re= turn"function"=3D=3Dtypeof e?e(!1):void 0},g=3Dfunction(){return"function"= =3D=3Dtypeof e?e(!0):void 0};return l.dialog(a,[{label:b,icon:k.CANCEL,"cla= ss":j.CANCEL,callback:f},{label:d,icon:k.CONFIRM,"class":j.CONFIRM,callback= :g}],{onEscape:f})},l.prompt=3Dfunction(){var a=3D"",d=3Dc("CANCEL"),e=3Dc(= "CONFIRM"),f=3Dnull,g=3D"";switch(arguments.length){case 1:a=3Darguments[0]= ;break;case 2:a=3Darguments[0],"function"=3D=3Dtypeof arguments[1]?f=3Dargu= ments[1]:d=3Darguments[1];break;case 3:a=3Darguments[0],d=3Darguments[1],"f= unction"=3D=3Dtypeof arguments[2]?f=3Darguments[2]:e=3Darguments[2];break;c= ase 4:a=3Darguments[0],d=3Darguments[1],e=3Darguments[2],f=3Darguments[3];b= reak;case 5:a=3Darguments[0],d=3Darguments[1],e=3Darguments[2],f=3Dargument= s[3],g=3Darguments[4];break;default:throw new Error("Incorrect number of ar= guments: expected 1-5")}var h=3Da,i=3Db("<form></form>");i.append("<input c= lass=3D'input-block-level' autocomplete=3Doff type=3Dtext value=3D'"+g+"' /= >");var m=3Dfunction(){return"function"=3D=3Dtypeof f?f(null):void 0},n=3Df= unction(){return"function"=3D=3Dtypeof f?f(i.find("input[type=3Dtext]").val= ()):void 0},o=3Dl.dialog(i,[{label:d,icon:k.CANCEL,"class":j.CANCEL,callbac= k:m},{label:e,icon:k.CONFIRM,"class":j.CONFIRM,callback:n}],{header:h,show:= !1,onEscape:m});return o.on("shown",function(){i.find("input[type=3Dtext]")= .focus(),i.on("submit",function(a){a.preventDefault(),o.find(".btn-primary"= ).click()})}),o.modal("show"),o},l.dialog=3Dfunction(c,d,e){function j(){va= r a=3Dnull;"function"=3D=3Dtypeof e.onEscape&&(a=3De.onEscape()),a!=3D=3D!1= &&x.modal("hide")}var k=3D"",l=3D[];e||(e=3D{}),"undefined"=3D=3Dtypeof d?d= =3D[]:"undefined"=3D=3Dtypeof d.length&&(d=3D[d]);for(var m=3Dd.length;m--;= ){var n=3Dnull,o=3Dnull,p=3Dnull,q=3D"",r=3Dnull;if("undefined"=3D=3Dtypeof= d[m].label&&"undefined"=3D=3Dtypeof d[m]["class"]&&"undefined"=3D=3Dtypeof= d[m].callback){var s=3D0,t=3Dnull;for(var u in d[m])if(t=3Du,++s>1)break;1= =3D=3Ds&&"function"=3D=3Dtypeof d[m][u]&&(d[m].label=3Dt,d[m].callback=3Dd[= m][u])}"function"=3D=3Dtypeof d[m].callback&&(r=3Dd[m].callback),d[m]["clas= s"]?p=3Dd[m]["class"]:m=3D=3Dd.length-1&&d.length<=3D2&&(p=3D"btn-primary")= ,d[m].link!=3D=3D!0&&(p=3D"btn "+p),n=3Dd[m].label?d[m].label:"Option "+(m+= 1),d[m].icon&&(q=3D"<i class=3D'"+d[m].icon+"'></i> "),o=3Dd[m].href?d[m].h= ref:h,k=3D"<a data-handler=3D'"+m+"' class=3D'"+p+"' href=3D'"+o+"'>"+q+n+"= </a>"+k,l[m]=3Dr}var v=3D["<div class=3D'bootbox modal' tabindex=3D'-1' sty= le=3D'overflow:hidden;'>"];if(e.header){var w=3D"";("undefined"=3D=3Dtypeof= e.headerCloseButton||e.headerCloseButton)&&(w=3D"<a href=3D'"+h+"' class= =3D'close'>×</a>"),v.push("<div class=3D'modal-header'>"+w+"<h3>"+e.h= eader+"</h3></div>")}v.push("<div class=3D'modal-body'></div>"),k&&v.push("= <div class=3D'modal-footer'>"+k+"</div>"),v.push("</div>");var x=3Db(v.join= ("\n")),y=3D"undefined"=3D=3Dtypeof e.animate?f:e.animate;y&&x.addClass("fa= de");var z=3D"undefined"=3D=3Dtypeof e.classes?i:e.classes;return z&&x.addC= lass(z),x.find(".modal-body").html(c),x.on("keyup.dismiss.modal",function(a= ){27=3D=3D=3Da.which&&e.onEscape&&j("escape")}),x.on("click","a.close",func= tion(a){a.preventDefault(),j("close")}),x.on("shown",function(){x.find("a.b= tn-primary:first").focus()}),x.on("hidden",function(a){a.target=3D=3D=3Dthi= s&&x.remove()}),x.on("click",".modal-footer a",function(a){var c=3Db(this).= data("handler"),e=3Dl[c],f=3Dnull;("undefined"=3D=3Dtypeof c||"undefined"= =3D=3Dtypeof d[c].href)&&(a.preventDefault(),"function"=3D=3Dtypeof e&&(f= =3De(a)),f!=3D=3D!1&&x.modal("hide"))}),b("body").append(x),x.modal({backdr= op:"undefined"=3D=3Dtypeof e.backdrop?g:e.backdrop,keyboard:!1,show:!1}),x.= on("show",function(){b(a).off("focusin.modal")}),("undefined"=3D=3Dtypeof e= .show||e.show=3D=3D=3D!0)&&x.modal("show"),x},l.modal=3Dfunction(){var a,c,= d,e=3D{onEscape:null,keyboard:!0,backdrop:g};switch(arguments.length){case = 1:a=3Darguments[0];break;case 2:a=3Darguments[0],"object"=3D=3Dtypeof argum= ents[1]?d=3Darguments[1]:c=3Darguments[1];break;case 3:a=3Darguments[0],c= =3Darguments[1],d=3Darguments[2];break;default:throw new Error("Incorrect n= umber of arguments: expected 1-3")}return e.header=3Dc,d=3D"object"=3D=3Dty= peof d?b.extend(e,d):e,l.dialog(a,[],d)},l.hideAll=3Dfunction(){b(".bootbox= ").modal("hide")},l.animate=3Dfunction(a){f=3Da},l.backdrop=3Dfunction(a){g= =3Da},l.classes=3Dfunction(a){i=3Da};var m=3D{br:{OK:"OK",CANCEL:"Cancelar"= ,CONFIRM:"Sim"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"O= K",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},en:{OK:"OK",CANCEL:"Cancel",CO= NFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},fr:{OK:"OK",CA= NCEL:"Annuler",CONFIRM:"D'accord"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Co= nferma"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},pl:{OK:"OK",C= ANCEL:"Anuluj",CONFIRM:"Potwierd=C5=BA"},ru:{OK:"OK",CANCEL:"=D0=9E=D1=82= =D0=BC=D0=B5=D0=BD=D0=B0",CONFIRM:"=D0=9F=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D0= =B8=D1=82=D1=8C"},zh_CN:{OK:"OK",CANCEL:"=E5=8F=96=E6=B6=88",CONFIRM:"=E7= =A1=AE=E8=AE=A4"},zh_TW:{OK:"OK",CANCEL:"=E5=8F=96=E6=B6=88",CONFIRM:"=E7= =A2=BA=E8=AA=8D"}};return l}(document,window.jQuery);window.bootbox=3Dbootb= ox; \ No newline at end of file diff -r 9aa770b95a35bb62e36f86c98a6a5e3ddd2e98bf -r 8784e849cb30c4921a55b23= 774219fda3670aa38 vcweb/static/js/bootstrap-tour.js --- a/vcweb/static/js/bootstrap-tour.js +++ b/vcweb/static/js/bootstrap-tour.js @@ -1,7 +1,6 @@ -// Generated by CoffeeScript 1.6.2 -/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D -# bootstrap-tour.js v0.3.0 -# http://bootstraptour.com/ +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +# bootstrap-tour - v0.5.0 +# http://bootstraptour.com # =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D # Copyright 2012-2013 Ulrich Sossou # @@ -17,479 +16,4 @@ # See the License for the specific language governing permissions and # limitations under the License. */ - - -(function() { - (function($, window) { - var Tour, document; - - document =3D window.document; - Tour =3D (function() { - function Tour(options) { - this._options =3D $.extend({ - name: 'tour', - labels: { - end: 'End tour', - next: 'Next »', - prev: '« Prev' - }, - template: "<div class=3D'popover tour'><div class=3D'arrow'></di= v><h3 class=3D'popover-title'></h3><div class=3D'popover-content'></div></d= iv>", - container: 'body', - keyboard: true, - useLocalStorage: false, - debug: false, - backdrop: false, - afterSetState: function(key, value) {}, - afterGetState: function(key, value) {}, - afterRemoveState: function(key) {}, - onStart: function(tour) {}, - onEnd: function(tour) {}, - onShow: function(tour) {}, - onShown: function(tour) {}, - onHide: function(tour) {}, - onHidden: function(tour) {} - }, options); - this._steps =3D []; - this.setCurrentStep(); - this.backdrop =3D { - overlay: null, - step: null, - background: null - }; - } - - Tour.prototype.setState =3D function(key, value) { - key =3D "" + this._options.name + "_" + key; - if (this._options.useLocalStorage) { - window.localStorage.setItem(key, value); - } else { - $.cookie(key, value, { - expires: 36500, - path: '/' - }); - } - return this._options.afterSetState(key, value); - }; - - Tour.prototype.removeState =3D function(key) { - key =3D "" + this._options.name + "_" + key; - if (this._options.useLocalStorage) { - window.localStorage.removeItem(key); - } else { - $.removeCookie(key, { - path: '/' - }); - } - return this._options.afterRemoveState(key); - }; - - Tour.prototype.getState =3D function(key) { - var value; - - if (this._options.useLocalStorage) { - value =3D window.localStorage.getItem("" + this._options.name + = "_" + key); - } else { - value =3D $.cookie("" + this._options.name + "_" + key); - } - if (value =3D=3D=3D void 0 || value =3D=3D=3D "null") { - value =3D null; - } - this._options.afterGetState(key, value); - return value; - }; - - Tour.prototype.addStep =3D function(step) { - return this._steps.push(step); - }; - - Tour.prototype.getStep =3D function(i) { - if (this._steps[i] !=3D null) { - return $.extend({ - path: "", - placement: "right", - title: "", - content: "", - id: "step-" + i, - next: i =3D=3D=3D this._steps.length - 1 ? -1 : i + 1, - prev: i - 1, - animation: true, - onShow: this._options.onShow, - onShown: this._options.onShown, - onHide: this._options.onHide, - onHidden: this._options.onHidden, - template: this._options.template, - container: this._options.container - }, this._steps[i]); - } - }; - - Tour.prototype.start =3D function(force) { - var promise, - _this =3D this; - - if (force =3D=3D null) { - force =3D false; - } - if (this.ended() && !force) { - return this._debug("Tour ended, start prevented."); - } - $(document).off("click.bootstrap-tour", ".popover .next").on("clic= k.bootstrap-tour", ".popover .next", function(e) { - e.preventDefault(); - return _this.next(); - }); - $(document).off("click.bootstrap-tour", ".popover .prev").on("clic= k.bootstrap-tour", ".popover .prev", function(e) { - e.preventDefault(); - return _this.prev(); - }); - $(document).off("click.bootstrap-tour", ".popover .end").on("click= .bootstrap-tour", ".popover .end", function(e) { - e.preventDefault(); - return _this.end(); - }); - this._onresize(function() { - return _this.showStep(_this._current); - }); - this._setupKeyboardNavigation(); - promise =3D this._makePromise(this._options.onStart !=3D null ? th= is._options.onStart(this) : void 0); - return this._callOnPromiseDone(promise, this.showStep, this._curre= nt); - }; - - Tour.prototype.next =3D function() { - var promise; - - promise =3D this.hideStep(this._current); - return this._callOnPromiseDone(promise, this.showNextStep); - }; - - Tour.prototype.prev =3D function() { - var promise; - - promise =3D this.hideStep(this._current); - return this._callOnPromiseDone(promise, this.showPrevStep); - }; - - Tour.prototype.end =3D function() { - var endHelper, hidePromise, - _this =3D this; - - endHelper =3D function(e) { - $(document).off("click.bootstrap-tour"); - $(document).off("keyup.bootstrap-tour"); - $(window).off("resize.bootstrap-tour"); - _this.setState("end", "yes"); - _this._hideBackdrop(); - if (_this._options.onEnd !=3D null) { - return _this._options.onEnd(_this); - } - }; - hidePromise =3D this.hideStep(this._current); - return this._callOnPromiseDone(hidePromise, endHelper); - }; - - Tour.prototype.ended =3D function() { - return !!this.getState("end"); - }; - - Tour.prototype.restart =3D function() { - this.removeState("current_step"); - this.removeState("end"); - this.setCurrentStep(0); - return this.start(); - }; - - Tour.prototype.hideStep =3D function(i) { - var hideStepHelper, promise, step, - _this =3D this; - - step =3D this.getStep(i); - promise =3D this._makePromise((step.onHide !=3D null ? step.onHide= (this) : void 0)); - hideStepHelper =3D function(e) { - var $element; - - $element =3D $(step.element).popover("hide"); - if (step.reflex) { - $element.css("cursor", "").off("click.boostrap-tour"); - } - if (step.backdrop) { - _this._hideBackdrop(); - } - if (step.onHidden !=3D null) { - return step.onHidden(_this); - } - }; - this._callOnPromiseDone(promise, hideStepHelper); - return promise; - }; - - Tour.prototype.showStep =3D function(i) { - var promise, showStepHelper, step, - _this =3D this; - - step =3D this.getStep(i); - if (!step) { - return; - } - promise =3D this._makePromise((step.onShow !=3D null ? step.onShow= (this) : void 0)); - showStepHelper =3D function(e) { - var current_path, path; - - _this.setCurrentStep(i); - path =3D typeof step.path =3D=3D=3D "function" ? step.path.call(= ) : step.path; - current_path =3D [document.location.pathname, document.location.= hash].join(''); - if (_this._redirect(path, current_path)) { - _this._debug("Redirect to " + path); - document.location.href =3D path; - return; - } - if (!((step.element !=3D null) && $(step.element).length !=3D=3D= 0 && $(step.element).is(":visible"))) { - _this._debug("Skip the step " + (_this._current + 1) + ". The = element does not exist or is not visible."); - _this.showNextStep(); - return; - } - if (step.backdrop) { - _this._showBackdrop(step.element); - } - _this._showPopover(step, i); - if (step.onShown !=3D null) { - step.onShown(_this); - } - return _this._debug("Step " + (_this._current + 1) + " of " + _t= his._steps.length); - }; - return this._callOnPromiseDone(promise, showStepHelper); - }; - - Tour.prototype.setCurrentStep =3D function(value) { - if (value !=3D null) { - this._current =3D value; - return this.setState("current_step", value); - } else { - this._current =3D this.getState("current_step"); - if (this._current =3D=3D=3D null) { - return this._current =3D 0; - } else { - return this._current =3D parseInt(this._current); - } - } - }; - - Tour.prototype.showNextStep =3D function() { - var step; - - step =3D this.getStep(this._current); - return this.showStep(step.next); - }; - - Tour.prototype.showPrevStep =3D function() { - var step; - - step =3D this.getStep(this._current); - return this.showStep(step.prev); - }; - - Tour.prototype._debug =3D function(text) { - if (this._options.debug) { - return window.console.log("Bootstrap Tour '" + this._options.nam= e + "' | " + text); - } - }; - - Tour.prototype._redirect =3D function(path, currentPath) { - return (path !=3D null) && path !=3D=3D "" && path.replace(/\?.*$/= , "").replace(/\/?$/, "") !=3D=3D currentPath.replace(/\/?$/, ""); - }; - - Tour.prototype._showPopover =3D function(step, i) { - var $tip, content, nav, options, - _this =3D this; - - content =3D "" + step.content + "<br /><p>"; - options =3D $.extend({}, this._options); - if (step.options) { - $.extend(options, step.options); - } - if (step.reflex) { - $(step.element).css("cursor", "pointer").on("click.bootstrap-tou= r", function(e) { - return _this.next(); - }); - } - nav =3D []; - if (step.prev >=3D 0) { - nav.push("<a href=3D'#' class=3D'prev'>" + options.labels.prev += "</a>"); - } - if (step.next >=3D 0) { - nav.push("<a href=3D'#' class=3D'next'>" + options.labels.next += "</a>"); - } - content +=3D nav.join(" | "); - content +=3D "<a href=3D'#' class=3D'pull-right end'>" + options.l= abels.end + "</a>"; - $(step.element).popover('destroy').popover({ - placement: step.placement, - trigger: "manual", - title: step.title, - content: content, - html: true, - animation: step.animation, - container: step.container, - template: step.template - }).popover("show"); - $tip =3D $(step.element).data("popover").tip(); - $tip.attr("id", step.id); - this._reposition($tip, step); - return this._scrollIntoView($tip); - }; - - Tour.prototype._reposition =3D function(tip, step) { - var offsetBottom, offsetRight, original_left, original_offsetHeigh= t, original_offsetWidth, original_top, tipOffset; - - original_offsetWidth =3D tip[0].offsetWidth; - original_offsetHeight =3D tip[0].offsetHeight; - tipOffset =3D tip.offset(); - original_left =3D tipOffset.left; - original_top =3D tipOffset.top; - offsetBottom =3D $(document).outerHeight() - tipOffset.top - $(tip= ).outerHeight(); - if (offsetBottom < 0) { - tipOffset.top =3D tipOffset.top + offsetBottom; - } - offsetRight =3D $("html").outerWidth() - tipOffset.left - $(tip).o= uterWidth(); - if (offsetRight < 0) { - tipOffset.left =3D tipOffset.left + offsetRight; - } - if (tipOffset.top < 0) { - tipOffset.top =3D 0; - } - if (tipOffset.left < 0) { - tipOffset.left =3D 0; - } - tip.offset(tipOffset); - if (step.placement =3D=3D=3D 'bottom' || step.placement =3D=3D=3D = 'top') { - if (original_left !=3D=3D tipOffset.left) { - return this._replaceArrow(tip, (tipOffset.left - original_left= ) * 2, original_offsetWidth, 'left'); - } - } else { - if (original_top !=3D=3D tipOffset.top) { - return this._replaceArrow(tip, (tipOffset.top - original_top) = * 2, original_offsetHeight, 'top'); - } - } - }; - - Tour.prototype._replaceArrow =3D function(tip, delta, dimension, pos= ition) { - return tip.find(".arrow").css(position, delta ? 50 * (1 - delta / = dimension) + "%" : ''); - }; - - Tour.prototype._scrollIntoView =3D function(tip) { - var tipRect; - - tipRect =3D tip.get(0).getBoundingClientRect(); - if (!(tipRect.top >=3D 0 && tipRect.bottom < $(window).height() &&= tipRect.left >=3D 0 && tipRect.right < $(window).width())) { - return tip.get(0).scrollIntoView(true); - } - }; - - Tour.prototype._onresize =3D function(cb, timeout) { - return $(window).on("resize.bootstrap-tour", function() { - clearTimeout(timeout); - return timeout =3D setTimeout(cb, 100); - }); - }; - - Tour.prototype._setupKeyboardNavigation =3D function() { - var _this =3D this; - - if (this._options.keyboard) { - return $(document).on("keyup.bootstrap-tour", function(e) { - if (!e.which) { - return; - } - switch (e.which) { - case 39: - e.preventDefault(); - if (_this._current < _this._steps.length - 1) { - return _this.next(); - } else { - return _this.end(); - } - break; - case 37: - e.preventDefault(); - if (_this._current > 0) { - return _this.prev(); - } - } - }); - } - }; - - Tour.prototype._makePromise =3D function(result) { - if (result && $.isFunction(result.then)) { - return result; - } else { - return null; - } - }; - - Tour.prototype._callOnPromiseDone =3D function(promise, cb, arg) { - var _this =3D this; - - if (promise) { - return promise.then(function(e) { - return cb.call(_this, arg); - }); - } else { - return cb.call(this, arg); - } - }; - - Tour.prototype._showBackdrop =3D function(el) { - if (this.backdrop.overlay !=3D=3D null) { - return; - } - this._showOverlay(); - return this._showOverlayElement(el); - }; - - Tour.prototype._hideBackdrop =3D function() { - if (this.backdrop.overlay =3D=3D=3D null) { - return; - } - this._hideOverlayElement(); - return this._hideOverlay(); - }; - - Tour.prototype._showOverlay =3D function() { - this.backdrop =3D $('<div/>'); - this.backdrop.addClass('tour-backdrop'); - this.backdrop.height($(document).innerHeight()); - return $('body').append(this.backdrop); - }; - - Tour.prototype._hideOverlay =3D function() { - this.backdrop.remove(); - return this.backdrop.overlay =3D null; - }; - - Tour.prototype._showOverlayElement =3D function(el) { - var background, offset, padding, step; - - step =3D $(el); - padding =3D 5; - offset =3D step.offset(); - offset.top =3D offset.top - padding; - offset.left =3D offset.left - padding; - background =3D $('<div/>'); - background.width(step.innerWidth() + padding).height(step.innerHei= ght() + padding).addClass('tour-step-background').offset(offset); - step.addClass('tour-step-backdrop'); - $('body').append(background); - this.backdrop.step =3D step; - return this.backdrop.background =3D background; - }; - - Tour.prototype._hideOverlayElement =3D function() { - this.backdrop.step.removeClass('tour-step-backdrop'); - this.backdrop.background.remove(); - this.backdrop.step =3D null; - return this.backdrop.background =3D null; - }; - - return Tour; - - })(); - return window.Tour =3D Tour; - })(jQuery, window); - -}).call(this); +!function(){!function(a,b){var c,d;return d=3Db.document,c=3Dfunction(){fu= nction c(b){this._options=3Da.extend({name:"tour",container:"body",keyboard= :!0,useLocalStorage:!1,debug:!1,backdrop:!1,redirect:!0,basePath:"",templat= e:"<div class=3D'popover tour'><div class=3D'arrow'></div><h3 class=3D'popo= ver-title'></h3><div class=3D'popover-content'></div><div class=3D'popover-= navigation'><button class=3D'btn' data-role=3D'prev'>« Prev</button><= span data-role=3D'separator'>|</span><button class=3D'btn' data-role=3D'nex= t'>Next »</button><button class=3D'btn' data-role=3D'end'>End tour</b= utton></div></div>",afterSetState:function(){},afterGetState:function(){},a= fterRemoveState:function(){},onStart:function(){},onEnd:function(){},onShow= :function(){},onShown:function(){},onHide:function(){},onHidden:function(){= },onNext:function(){},onPrev:function(){}},b),this._options.useLocalStorage= ||a.cookie||this._debug("jQuery.cookie is not loaded."),this._steps=3D[],th= is.setCurrentStep(),this.backdrop=3D{overlay:null,step:null,background:null= }}return c.prototype.setState=3Dfunction(c,d){return c=3D""+this._options.n= ame+"_"+c,this._options.useLocalStorage?b.localStorage.setItem(c,d):a.cooki= e(c,d,{expires:36500,path:"/"}),this._options.afterSetState(c,d)},c.prototy= pe.removeState=3Dfunction(c){return c=3D""+this._options.name+"_"+c,this._o= ptions.useLocalStorage?b.localStorage.removeItem(c):a.removeCookie(c,{path:= "/"}),this._options.afterRemoveState(c)},c.prototype.getState=3Dfunction(c)= {var d;return d=3Dthis._options.useLocalStorage?b.localStorage.getItem(""+t= his._options.name+"_"+c):a.cookie(""+this._options.name+"_"+c),(void 0=3D= =3D=3Dd||"null"=3D=3D=3Dd)&&(d=3Dnull),this._options.afterGetState(c,d),d},= c.prototype.addSteps=3Dfunction(a){var b,c,d,e;for(e=3D[],c=3D0,d=3Da.lengt= h;d>c;c++)b=3Da[c],e.push(this.addStep(b));return e},c.prototype.addStep=3D= function(a){return this._steps.push(a)},c.prototype.getStep=3Dfunction(b){r= eturn null!=3Dthis._steps[b]?a.extend({id:"step-"+b,path:"",placement:"righ= t",title:"",content:"<p></p>",next:b=3D=3D=3Dthis._steps.length-1?-1:b+1,pr= ev:b-1,animation:!0,container:this._options.container,backdrop:this._option= s.backdrop,redirect:this._options.redirect,template:this._options.template,= onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._opti= ons.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPr= ev:this._options.onPrev},this._steps[b]):void 0},c.prototype.start=3Dfuncti= on(b){var c,e=3Dthis;return null=3D=3Db&&(b=3D!1),this.ended()&&!b?this._de= bug("Tour ended, start prevented."):(a(d).off("click.bootstrap-tour",".popo= ver *[data-role=3Dnext]").on("click.bootstrap-tour",".popover *[data-role= =3Dnext]",function(a){return a.preventDefault(),e.next()}),a(d).off("click.= bootstrap-tour",".popover *[data-role=3Dprev]").on("click.bootstrap-tour","= .popover *[data-role=3Dprev]",function(a){return a.preventDefault(),e.prev(= )}),a(d).off("click.bootstrap-tour",".popover *[data-role=3Dend]").on("clic= k.bootstrap-tour",".popover *[data-role=3Dend]",function(a){return a.preven= tDefault(),e.end()}),this._onresize(function(){return e.showStep(e._current= )}),this._setupKeyboardNavigation(),c=3Dthis._makePromise(null!=3Dthis._opt= ions.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(c,= this.showStep,this._current))},c.prototype.next=3Dfunction(){var a;return a= =3Dthis.hideStep(this._current),this._callOnPromiseDone(a,this.showNextStep= )},c.prototype.prev=3Dfunction(){var a;return a=3Dthis.hideStep(this._curre= nt),this._callOnPromiseDone(a,this.showPrevStep)},c.prototype.end=3Dfunctio= n(){var c,e,f=3Dthis;return c=3Dfunction(){return a(d).off("click.bootstrap= -tour"),a(d).off("keyup.bootstrap-tour"),a(b).off("resize.bootstrap-tour"),= f.setState("end","yes"),f._hideBackdrop(),null!=3Df._options.onEnd?f._optio= ns.onEnd(f):void 0},e=3Dthis.hideStep(this._current),this._callOnPromiseDon= e(e,c)},c.prototype.ended=3Dfunction(){return!!this.getState("end")},c.prot= otype.restart=3Dfunction(){return this.removeState("current_step"),this.rem= oveState("end"),this.setCurrentStep(0),this.start()},c.prototype.hideStep= =3Dfunction(b){var c,d,e,f=3Dthis;return e=3Dthis.getStep(b),d=3Dthis._make= Promise(null!=3De.onHide?e.onHide(this):void 0),c=3Dfunction(){var b;return= b=3Da(e.element).popover("hide"),e.reflex&&b.css("cursor","").off("click.b= ootstrap-tour"),e.backdrop&&f._hideBackdrop(),null!=3De.onHidden?e.onHidden= (f):void 0},this._callOnPromiseDone(d,c),d},c.prototype.showStep=3Dfunction= (b){var c,e,f,g=3Dthis;return(f=3Dthis.getStep(b))?(c=3Dthis._makePromise(n= ull!=3Df.onShow?f.onShow(this):void 0),e=3Dfunction(){var c,e;return g.setC= urrentStep(b),e=3Da.isFunction(f.path)?f.path.call():g._options.basePath+f.= path,c=3D[d.location.pathname,d.location.hash].join(""),g._isRedirect(e,c)?= (g._redirect(f,e),void 0):null!=3Df.element&&0!=3D=3Da(f.element).length&&a= (f.element).is(":visible")?(f.backdrop&&g._showBackdrop(f.element),g._showP= opover(f,b),null!=3Df.onShown&&f.onShown(g),g._debug("Step "+(g._current+1)= +" of "+g._steps.length)):(g._debug("Skip the step "+(g._current+1)+". The = element does not exist or is not visible."),g.showNextStep(),void 0)},this.= _callOnPromiseDone(c,e)):void 0},c.prototype.setCurrentStep=3Dfunction(a){r= eturn null!=3Da?(this._current=3Da,this.setState("current_step",a)):(this._= current=3Dthis.getState("current_step"),this._current=3Dnull=3D=3D=3Dthis._= current?0:parseInt(this._current))},c.prototype.showNextStep=3Dfunction(){v= ar a,b,c,d=3Dthis;return c=3Dthis.getStep(this._current),b=3Dfunction(){ret= urn d.showStep(c.next)},a=3Dthis._makePromise(null!=3Dc.onNext?c.onNext(thi= s):void 0),this._callOnPromiseDone(a,b)},c.prototype.showPrevStep=3Dfunctio= n(){var a,b,c,d=3Dthis;return c=3Dthis.getStep(this._current),b=3Dfunction(= ){return d.showStep(c.prev)},a=3Dthis._makePromise(null!=3Dc.onPrev?c.onPre= v(this):void 0),this._callOnPromiseDone(a,b)},c.prototype._debug=3Dfunction= (a){return this._options.debug?b.console.log("Bootstrap Tour '"+this._optio= ns.name+"' | "+a):void 0},c.prototype._isRedirect=3Dfunction(a,b){return nu= ll!=3Da&&""!=3D=3Da&&a.replace(/\?.*$/,"").replace(/\/?$/,"")!=3D=3Db.repla= ce(/\/?$/,"")},c.prototype._redirect=3Dfunction(b,c){return a.isFunction(b.= redirect)?b.redirect.call(this,c):b.redirect=3D=3D=3D!0?(this._debug("Redir= ect to "+c),d.location.href=3Dc):void 0},c.prototype._renderNavigation=3Dfu= nction(b,c){var d;return d=3Da.isFunction(b.template)?a(b.template(c,b)):a(= b.template),b.prev>=3D0||d.find(".popover-navigation *[data-role=3Dprev]").= remove(),b.next>=3D0||d.find(".popover-navigation *[data-role=3Dnext]").rem= ove(),b.prev>=3D0&&b.next>=3D0||d.find(".popover-navigation *[data-role=3Ds= eparator]").remove(),d.clone().wrap("<div>").parent().html()},c.prototype._= showPopover=3Dfunction(b,c){var d,e,f,g,h=3Dthis;return f=3Da.extend({},thi= s._options),b.options&&a.extend(f,b.options),b.reflex&&a(b.element).css("cu= rsor","pointer").on("click.bootstrap-tour",function(){return h.next()}),g= =3Dthis._renderNavigation(b,c,f),d=3Da(b.element),d.data("popover")&&d.popo= ver("destroy"),d.popover({placement:b.placement,trigger:"manual",title:b.ti= tle,content:b.content,html:!0,animation:b.animation,container:b.container,t= emplate:g,selector:b.element}).popover("show"),e=3Da(b.element).data("popov= er").tip(),e.attr("id",b.id),this._reposition(e,b),this._scrollIntoView(e)}= ,c.prototype._reposition=3Dfunction(b,c){var e,f,g,h,i,j,k;if(i=3Db[0].offs= etWidth,h=3Db[0].offsetHeight,k=3Db.offset(),g=3Dk.left,j=3Dk.top,e=3Da(d).= outerHeight()-k.top-a(b).outerHeight(),0>e&&(k.top=3Dk.top+e),f=3Da("html")= .outerWidth()-k.left-a(b).outerWidth(),0>f&&(k.left=3Dk.left+f),k.top<0&&(k= .top=3D0),k.left<0&&(k.left=3D0),b.offset(k),"bottom"=3D=3D=3Dc.placement||= "top"=3D=3D=3Dc.placement){if(g!=3D=3Dk.left)return this._replaceArrow(b,2*= (k.left-g),i,"left")}else if(j!=3D=3Dk.top)return this._replaceArrow(b,2*(k= .top-j),h,"top")},c.prototype._replaceArrow=3Dfunction(a,b,c,d){return a.fi= nd(".arrow").css(d,b?50*(1-b/c)+"%":"")},c.prototype._scrollIntoView=3Dfunc= tion(c){var d;return d=3Dc.get(0).getBoundingClientRect(),d.top>=3D0&&d.bot= tom<a(b).height()&&d.left>=3D0&&d.right<a(b).width()?void 0:c.get(0).scroll= IntoView(!0)},c.prototype._onresize=3Dfunction(c,d){return a(b).on("resize.= bootstrap-tour",function(){return clearTimeout(d),d=3DsetTimeout(c,100)})},= c.prototype._setupKeyboardNavigation=3Dfunction(){var b=3Dthis;return this.= _options.keyboard?a(d).on("keyup.bootstrap-tour",function(a){if(a.which)swi= tch(a.which){case 39:return a.preventDefault(),b._current<b._steps.length-1= ?b.next():b.end();case 37:if(a.preventDefault(),b._current>0)return b.prev(= );break;case 27:return a.preventDefault(),b.end()}}):void 0},c.prototype._m= akePromise=3Dfunction(b){return b&&a.isFunction(b.then)?b:null},c.prototype= ._callOnPromiseDone=3Dfunction(a,b,c){var d=3Dthis;return a?a.then(function= (){return b.call(d,c)}):b.call(this,c)},c.prototype._showBackdrop=3Dfunctio= n(a){return null=3D=3D=3Dthis.backdrop.overlay?(this._showOverlay(),this._s= howOverlayElement(a)):void 0},c.prototype._hideBackdrop=3Dfunction(){return= null!=3D=3Dthis.backdrop.overlay?(this._hideOverlayElement(),this._hideOve= rlay()):void 0},c.prototype._showOverlay=3Dfunction(){return this.backdrop= =3Da("<div/>"),this.backdrop.addClass("tour-backdrop"),this.backdrop.height= (a(d).innerHeight()),a("body").append(this.backdrop)},c.prototype._hideOver= lay=3Dfunction(){return this.backdrop.remove(),this.backdrop.overlay=3Dnull= },c.prototype._showOverlayElement=3Dfunction(b){var c,d,e,f;return f=3Da(b)= ,e=3D5,d=3Df.offset(),d.top=3Dd.top-e,d.left=3Dd.left-e,c=3Da("<div/>"),c.w= idth(f.innerWidth()+e).height(f.innerHeight()+e).addClass("tour-step-backgr= ound").offset(d),f.addClass("tour-step-backdrop"),a("body").append(c),this.= backdrop.step=3Df,this.backdrop.background=3Dc},c.prototype._hideOverlayEle= ment=3Dfunction(){return this.backdrop.step.removeClass("tour-step-backdrop= "),this.backdrop.background.remove(),this.backdrop.step=3Dnull,this.backdro= p.background=3Dnull},c}(),b.Tour=3Dc}(jQuery,window)}.call(this); \ No newline at end of file diff -r 9aa770b95a35bb62e36f86c98a6a5e3ddd2e98bf -r 8784e849cb30c4921a55b23= 774219fda3670aa38 vcweb/static/js/ko/knockout.min.js --- a/vcweb/static/js/ko/knockout.min.js +++ b/vcweb/static/js/ko/knockout.min.js @@ -1,85 +1,88 @@ -// Knockout JavaScript library v2.2.1 +// Knockout JavaScript library v2.3.0 // (c) Steven Sanderson - http://knockoutjs.com/ // License: MIT (http://www.opensource.org/licenses/mit-license.php) =20 -(function() {function j(w){throw w;}var m=3D!0,p=3Dnull,r=3D!1;function u(= w){return function(){return w}};var x=3Dwindow,y=3Ddocument,ga=3Dnavigator,= F=3Dwindow.jQuery,I=3Dvoid 0; -function L(w){function ha(a,d,c,e,f){var g=3D[];a=3Db.j(function(){var a= =3Dd(c,f)||[];0<g.length&&(b.a.Ya(M(g),a),e&&b.r.K(e,p,[c,a,f]));g.splice(0= ,g.length);b.a.P(g,a)},p,{W:a,Ka:function(){return 0=3D=3Dg.length||!b.a.X(= g[0])}});return{M:g,j:a.pa()?a:I}}function M(a){for(;a.length&&!b.a.X(a[0])= ;)a.splice(0,1);if(1<a.length){for(var d=3Da[0],c=3Da[a.length-1],e=3D[d];d= !=3D=3Dc;){d=3Dd.nextSibling;if(!d)return;e.push(d)}Array.prototype.splice.= apply(a,[0,a.length].concat(e))}return a}function S(a,b,c,e,f){var g=3DMath= .min, -h=3DMath.max,k=3D[],l,n=3Da.length,q,s=3Db.length,v=3Ds-n||1,G=3Dn+s+1,J,A= ,z;for(l=3D0;l<=3Dn;l++){A=3DJ;k.push(J=3D[]);z=3Dg(s,l+v);for(q=3Dh(0,l-1)= ;q<=3Dz;q++)J[q]=3Dq?l?a[l-1]=3D=3D=3Db[q-1]?A[q-1]:g(A[q]||G,J[q-1]||G)+1:= q+1:l+1}g=3D[];h=3D[];v=3D[];l=3Dn;for(q=3Ds;l||q;)s=3Dk[l][q]-1,q&&s=3D=3D= =3Dk[l][q-1]?h.push(g[g.length]=3D{status:c,value:b[--q],index:q}):l&&s=3D= =3D=3Dk[l-1][q]?v.push(g[g.length]=3D{status:e,value:a[--l],index:l}):(g.pu= sh({status:"retained",value:b[--q]}),--l);if(h.length&&v.length){a=3D10*n;v= ar t;for(b=3Dc=3D0;(f||b<a)&&(t=3Dh[c]);c++){for(e=3D -0;k=3Dv[e];e++)if(t.value=3D=3D=3Dk.value){t.moved=3Dk.index;k.moved=3Dt.i= ndex;v.splice(e,1);b=3De=3D0;break}b+=3De}}return g.reverse()}function T(a,= d,c,e,f){f=3Df||{};var g=3Da&&N(a),g=3Dg&&g.ownerDocument,h=3Df.templateEng= ine||O;b.za.vb(c,h,g);c=3Dh.renderTemplate(c,e,f,g);("number"!=3Dtypeof c.l= ength||0<c.length&&"number"!=3Dtypeof c[0].nodeType)&&j(Error("Template eng= ine must return an array of DOM nodes"));g=3Dr;switch(d){case "replaceChild= ren":b.e.N(a,c);g=3Dm;break;case "replaceNode":b.a.Ya(a,c);g=3Dm;break;case= "ignoreTargetNode":break; -default:j(Error("Unknown renderMode: "+d))}g&&(U(c,e),f.afterRender&&b.r.K= (f.afterRender,p,[c,e.$data]));return c}function N(a){return a.nodeType?a:0= <a.length?a[0]:p}function U(a,d){if(a.length){var c=3Da[0],e=3Da[a.length-1= ];V(c,e,function(a){b.Da(d,a)});V(c,e,function(a){b.s.ib(a,[d])})}}function= V(a,d,c){var e;for(d=3Db.e.nextSibling(d);a&&(e=3Da)!=3D=3Dd;)a=3Db.e.next= Sibling(e),(1=3D=3D=3De.nodeType||8=3D=3D=3De.nodeType)&&c(e)}function W(a,= d,c){a=3Db.g.aa(a);for(var e=3Db.g.Q,f=3D0;f<a.length;f++){var g=3Da[f].key= ;if(e.hasOwnProperty(g)){var h=3D -e[g];"function"=3D=3D=3Dtypeof h?(g=3Dh(a[f].value))&&j(Error(g)):h||j(Err= or("This template engine does not support the '"+g+"' binding within its te= mplates"))}}a=3D"ko.__tr_ambtns(function($context,$element){return(function= (){return{ "+b.g.ba(a)+" } })()})";return c.createJavaScriptEvaluatorBlock(= a)+d}function X(a,d,c,e){function f(a){return function(){return k[a]}}funct= ion g(){return k}var h=3D0,k,l;b.j(function(){var n=3Dc&&c instanceof b.z?c= :new b.z(b.a.d(c)),q=3Dn.$data;e&&b.eb(a,n);if(k=3D("function"=3D=3Dtypeof = d? -d(n,a):d)||b.J.instance.getBindings(a,n)){if(0=3D=3D=3Dh){h=3D1;for(var s = in k){var v=3Db.c[s];v&&8=3D=3D=3Da.nodeType&&!b.e.I[s]&&j(Error("The bindi= ng '"+s+"' cannot be used with virtual elements"));if(v&&"function"=3D=3Dty= peof v.init&&(v=3D(0,v.init)(a,f(s),g,q,n))&&v.controlsDescendantBindings)l= !=3D=3DI&&j(Error("Multiple bindings ("+l+" and "+s+") are trying to contro= l descendant bindings of the same element. You cannot use these bindings to= gether on the same element.")),l=3Ds}h=3D2}if(2=3D=3D=3Dh)for(s in k)(v=3Db= .c[s])&&"function"=3D=3D -typeof v.update&&(0,v.update)(a,f(s),g,q,n)}},p,{W:a});return{Nb:l=3D=3D= =3DI}}function Y(a,d,c){var e=3Dm,f=3D1=3D=3D=3Dd.nodeType;f&&b.e.Ta(d);if(= f&&c||b.J.instance.nodeHasBindings(d))e=3DX(d,p,a,c).Nb;e&&Z(a,d,!f)}functi= on Z(a,d,c){for(var e=3Db.e.firstChild(d);d=3De;)e=3Db.e.nextSibling(d),Y(a= ,d,c)}function $(a,b){var c=3Daa(a,b);return c?0<c.length?c[c.length-1].nex= tSibling:a.nextSibling:p}function aa(a,b){for(var c=3Da,e=3D1,f=3D[];c=3Dc.= nextSibling;){if(H(c)&&(e--,0=3D=3D=3De))return f;f.push(c);B(c)&&e++}b||j(= Error("Cannot find closing comment tag to match: "+ -a.nodeValue));return p}function H(a){return 8=3D=3Da.nodeType&&(K?a.text:a= .nodeValue).match(ia)}function B(a){return 8=3D=3Da.nodeType&&(K?a.text:a.n= odeValue).match(ja)}function P(a,b){for(var c=3Dp;a!=3Dc;)c=3Da,a=3Da.repla= ce(ka,function(a,c){return b[c]});return a}function la(){var a=3D[],d=3D[];= this.save=3Dfunction(c,e){var f=3Db.a.i(a,c);0<=3Df?d[f]=3De:(a.push(c),d.p= ush(e))};this.get=3Dfunction(c){c=3Db.a.i(a,c);return 0<=3Dc?d[c]:I}}functi= on ba(a,b,c){function e(e){var g=3Db(a[e]);switch(typeof g){case "boolean":= case "number":case "string":case "function":f[e]=3D -g;break;case "object":case "undefined":var h=3Dc.get(g);f[e]=3Dh!=3D=3DI?h= :ba(g,b,c)}}c=3Dc||new la;a=3Db(a);if(!("object"=3D=3Dtypeof a&&a!=3D=3Dp&&= a!=3D=3DI&&!(a instanceof Date)))return a;var f=3Da instanceof Array?[]:{};= c.save(a,f);var g=3Da;if(g instanceof Array){for(var h=3D0;h<g.length;h++)e= (h);"function"=3D=3Dtypeof g.toJSON&&e("toJSON")}else for(h in g)e(h);retur= n f}function ca(a,d){if(a)if(8=3D=3Da.nodeType){var c=3Db.s.Ua(a.nodeValue)= ;c!=3Dp&&d.push({sb:a,Fb:c})}else if(1=3D=3Da.nodeType)for(var c=3D0,e=3Da.= childNodes,f=3De.length;c<f;c++)ca(e[c], -d)}function Q(a,d,c,e){b.c[a]=3D{init:function(a){b.a.f.set(a,da,{});retur= n{controlsDescendantBindings:m}},update:function(a,g,h,k,l){h=3Db.a.f.get(a= ,da);g=3Db.a.d(g());k=3D!c!=3D=3D!g;var n=3D!h.Za;if(n||d||k!=3D=3Dh.qb)n&&= (h.Za=3Db.a.Ia(b.e.childNodes(a),m)),k?(n||b.e.N(a,b.a.Ia(h.Za)),b.Ea(e?e(l= ,g):l,a)):b.e.Y(a),h.qb=3Dk}};b.g.Q[a]=3Dr;b.e.I[a]=3Dm}function ea(a,d,c){= c&&d!=3D=3Db.k.q(a)&&b.k.T(a,d);d!=3D=3Db.k.q(a)&&b.r.K(b.a.Ba,p,[a,"change= "])}var b=3D"undefined"!=3D=3Dtypeof w?w:{};b.b=3Dfunction(a,d){for(var c= =3Da.split("."),e=3Db,f=3D0;f< -c.length-1;f++)e=3De[c[f]];e[c[c.length-1]]=3Dd};b.p=3Dfunction(a,b,c){a[b= ]=3Dc};b.version=3D"2.2.1";b.b("version",b.version);b.a=3Dnew function(){fu= nction a(a,d){if("input"!=3D=3Db.a.u(a)||!a.type||"click"!=3Dd.toLowerCase(= ))return r;var c=3Da.type;return"checkbox"=3D=3Dc||"radio"=3D=3Dc}var d=3D/= ^(\s|\u00A0)+|(\s|\u00A0)+$/g,c=3D{},e=3D{};c[/Firefox\/2/i.test(ga.userAge= nt)?"KeyboardEvent":"UIEvents"]=3D["keyup","keydown","keypress"];c.MouseEve= nts=3D"click dblclick mousedown mouseup mousemove mouseover mouseout mousee= nter mouseleave".split(" "); -for(var f in c){var g=3Dc[f];if(g.length)for(var h=3D0,k=3Dg.length;h<k;h+= +)e[g[h]]=3Df}var l=3D{propertychange:m},n,c=3D3;f=3Dy.createElement("div")= ;for(g=3Df.getElementsByTagName("i");f.innerHTML=3D"\x3c!--[if gt IE "+ ++c= +"]><i></i><![endif]--\x3e",g[0];);n=3D4<c?c:I;return{Na:["authenticity_tok= en",/^__RequestVerificationToken(_.*)?$/],o:function(a,b){for(var d=3D0,c= =3Da.length;d<c;d++)b(a[d])},i:function(a,b){if("function"=3D=3Dtypeof Arra= y.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var d=3D0,= c=3Da.length;d< -c;d++)if(a[d]=3D=3D=3Db)return d;return-1},lb:function(a,b,d){for(var c=3D= 0,e=3Da.length;c<e;c++)if(b.call(d,a[c]))return a[c];return p},ga:function(= a,d){var c=3Db.a.i(a,d);0<=3Dc&&a.splice(c,1)},Ga:function(a){a=3Da||[];for= (var d=3D[],c=3D0,e=3Da.length;c<e;c++)0>b.a.i(d,a[c])&&d.push(a[c]);return= d},V:function(a,b){a=3Da||[];for(var d=3D[],c=3D0,e=3Da.length;c<e;c++)d.p= ush(b(a[c]));return d},fa:function(a,b){a=3Da||[];for(var d=3D[],c=3D0,e=3D= a.length;c<e;c++)b(a[c])&&d.push(a[c]);return d},P:function(a,b){if(b insta= nceof Array)a.push.apply(a, -b);else for(var d=3D0,c=3Db.length;d<c;d++)a.push(b[d]);return a},extend:f= unction(a,b){if(b)for(var d in b)b.hasOwnProperty(d)&&(a[d]=3Db[d]);return = a},ka:function(a){for(;a.firstChild;)b.removeNode(a.firstChild)},Hb:functio= n(a){a=3Db.a.L(a);for(var d=3Dy.createElement("div"),c=3D0,e=3Da.length;c<e= ;c++)d.appendChild(b.A(a[c]));return d},Ia:function(a,d){for(var c=3D0,e=3D= a.length,g=3D[];c<e;c++){var f=3Da[c].cloneNode(m);g.push(d?b.A(f):f)}retur= n g},N:function(a,d){b.a.ka(a);if(d)for(var c=3D0,e=3Dd.length;c<e;c++)a.ap= pendChild(d[c])}, -Ya:function(a,d){var c=3Da.nodeType?[a]:a;if(0<c.length){for(var e=3Dc[0],= g=3De.parentNode,f=3D0,h=3Dd.length;f<h;f++)g.insertBefore(d[f],e);f=3D0;fo= r(h=3Dc.length;f<h;f++)b.removeNode(c[f])}},bb:function(a,b){7>n?a.setAttri= bute("selected",b):a.selected=3Db},D:function(a){return(a||"").replace(d,""= )},Rb:function(a,d){for(var c=3D[],e=3D(a||"").split(d),f=3D0,g=3De.length;= f<g;f++){var h=3Db.a.D(e[f]);""!=3D=3Dh&&c.push(h)}return c},Ob:function(a,= b){a=3Da||"";return b.length>a.length?r:a.substring(0,b.length)=3D=3D=3Db},= tb:function(a,b){if(b.compareDocumentPosition)return 16=3D=3D -(b.compareDocumentPosition(a)&16);for(;a!=3Dp;){if(a=3D=3Db)return m;a=3Da= .parentNode}return r},X:function(a){return b.a.tb(a,a.ownerDocument)},u:fun= ction(a){return a&&a.tagName&&a.tagName.toLowerCase()},n:function(b,d,c){va= r e=3Dn&&l[d];if(!e&&"undefined"!=3Dtypeof F){if(a(b,d)){var f=3Dc;c=3Dfunc= tion(a,b){var d=3Dthis.checked;b&&(this.checked=3Db.nb!=3D=3Dm);f.call(this= ,a);this.checked=3Dd}}F(b).bind(d,c)}else!e&&"function"=3D=3Dtypeof b.addEv= entListener?b.addEventListener(d,c,r):"undefined"!=3Dtypeof b.attachEvent?b= .attachEvent("on"+ -d,function(a){c.call(b,a)}):j(Error("Browser doesn't support addEventListe= ner or attachEvent"))},Ba:function(b,d){(!b||!b.nodeType)&&j(Error("element= must be a DOM node when calling triggerEvent"));if("undefined"!=3Dtypeof F= ){var c=3D[];a(b,d)&&c.push({nb:b.checked});F(b).trigger(d,c)}else"function= "=3D=3Dtypeof y.createEvent?"function"=3D=3Dtypeof b.dispatchEvent?(c=3Dy.c= reateEvent(e[d]||"HTMLEvents"),c.initEvent(d,m,m,x,0,0,0,0,0,r,r,r,r,0,b),b= .dispatchEvent(c)):j(Error("The supplied element doesn't support dispatchEv= ent")): -"undefined"!=3Dtypeof b.fireEvent?(a(b,d)&&(b.checked=3Db.checked!=3D=3Dm)= ,b.fireEvent("on"+d)):j(Error("Browser doesn't support triggering events"))= },d:function(a){return b.$(a)?a():a},ua:function(a){return b.$(a)?a.t():a},= da:function(a,d,c){if(d){var e=3D/[\w-]+/g,f=3Da.className.match(e)||[];b.a= .o(d.match(e),function(a){var d=3Db.a.i(f,a);0<=3Dd?c||f.splice(d,1):c&&f.p= ush(a)});a.className=3Df.join(" ")}},cb:function(a,d){var c=3Db.a.d(d);if(c= =3D=3D=3Dp||c=3D=3D=3DI)c=3D"";if(3=3D=3D=3Da.nodeType)a.data=3Dc;else{var = e=3Db.e.firstChild(a); -!e||3!=3De.nodeType||b.e.nextSibling(e)?b.e.N(a,[y.createTextNode(c)]):e.d= ata=3Dc;b.a.wb(a)}},ab:function(a,b){a.name=3Db;if(7>=3Dn)try{a.mergeAttrib= utes(y.createElement("<input name=3D'"+a.name+"'/>"),r)}catch(d){}},wb:func= tion(a){9<=3Dn&&(a=3D1=3D=3Da.nodeType?a:a.parentNode,a.style&&(a.style.zoo= m=3Da.style.zoom))},ub:function(a){if(9<=3Dn){var b=3Da.style.width;a.style= .width=3D0;a.style.width=3Db}},Lb:function(a,d){a=3Db.a.d(a);d=3Db.a.d(d);f= or(var c=3D[],e=3Da;e<=3Dd;e++)c.push(e);return c},L:function(a){for(var b= =3D[],d=3D0,c=3Da.length;d< -c;d++)b.push(a[d]);return b},Pb:6=3D=3D=3Dn,Qb:7=3D=3D=3Dn,Z:n,Oa:function= (a,d){for(var c=3Db.a.L(a.getElementsByTagName("input")).concat(b.a.L(a.get= ElementsByTagName("textarea"))),e=3D"string"=3D=3Dtypeof d?function(a){retu= rn a.name=3D=3D=3Dd}:function(a){return d.test(a.name)},f=3D[],g=3Dc.length= -1;0<=3Dg;g--)e(c[g])&&f.push(c[g]);return f},Ib:function(a){return"string"= =3D=3Dtypeof a&&(a=3Db.a.D(a))?x.JSON&&x.JSON.parse?x.JSON.parse(a):(new Fu= nction("return "+a))():p},xa:function(a,d,c){("undefined"=3D=3Dtypeof JSON|= |"undefined"=3D=3Dtypeof JSON.stringify)&& -j(Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't = support it natively, but you can overcome this by adding a script reference= to json2.js, downloadable from http://www.json.org/json2.js"));return JSON= .stringify(b.a.d(a),d,c)},Jb:function(a,d,c){c=3Dc||{};var e=3Dc.params||{}= ,f=3Dc.includeFields||this.Na,g=3Da;if("object"=3D=3Dtypeof a&&"form"=3D=3D= =3Db.a.u(a))for(var g=3Da.action,h=3Df.length-1;0<=3Dh;h--)for(var k=3Db.a.= Oa(a,f[h]),l=3Dk.length-1;0<=3Dl;l--)e[k[l].name]=3Dk[l].value;d=3Db.a.d(d)= ;var n=3Dy.createElement("form"); -n.style.display=3D"none";n.action=3Dg;n.method=3D"post";for(var w in d)a= =3Dy.createElement("input"),a.name=3Dw,a.value=3Db.a.xa(b.a.d(d[w])),n.appe= ndChild(a);for(w in e)a=3Dy.createElement("input"),a.name=3Dw,a.value=3De[w= ],n.appendChild(a);y.body.appendChild(n);c.submitter?c.submitter(n):n.submi= t();setTimeout(function(){n.parentNode.removeChild(n)},0)}}};b.b("utils",b.= a);b.b("utils.arrayForEach",b.a.o);b.b("utils.arrayFirst",b.a.lb);b.b("util= s.arrayFilter",b.a.fa);b.b("utils.arrayGetDistinctValues",b.a.Ga);b.b("util= s.arrayIndexOf", -b.a.i);b.b("utils.arrayMap",b.a.V);b.b("utils.arrayPushAll",b.a.P);b.b("ut= ils.arrayRemoveItem",b.a.ga);b.b("utils.extend",b.a.extend);b.b("utils.fiel= dsIncludedWithJsonPost",b.a.Na);b.b("utils.getFormFields",b.a.Oa);b.b("util= s.peekObservable",b.a.ua);b.b("utils.postJson",b.a.Jb);b.b("utils.parseJson= ",b.a.Ib);b.b("utils.registerEventHandler",b.a.n);b.b("utils.stringifyJson"= ,b.a.xa);b.b("utils.range",b.a.Lb);b.b("utils.toggleDomNodeCssClass",b.a.da= );b.b("utils.triggerEvent",b.a.Ba);b.b("utils.unwrapObservable", -b.a.d);Function.prototype.bind||(Function.prototype.bind=3Dfunction(a){var= b=3Dthis,c=3DArray.prototype.slice.call(arguments);a=3Dc.shift();return fu= nction(){return b.apply(a,c.concat(Array.prototype.slice.call(arguments)))}= });b.a.f=3Dnew function(){var a=3D0,d=3D"__ko__"+(new Date).getTime(),c=3D{= };return{get:function(a,d){var c=3Db.a.f.la(a,r);return c=3D=3D=3DI?I:c[d]}= ,set:function(a,d,c){c=3D=3D=3DI&&b.a.f.la(a,r)=3D=3D=3DI||(b.a.f.la(a,m)[d= ]=3Dc)},la:function(b,f){var g=3Db[d];if(!g||!("null"!=3D=3Dg&&c[g])){if(!f= )return I;g=3Db[d]=3D"ko"+ -a++;c[g]=3D{}}return c[g]},clear:function(a){var b=3Da[d];return b?(delete= c[b],a[d]=3Dp,m):r}}};b.b("utils.domData",b.a.f);b.b("utils.domData.clear"= ,b.a.f.clear);b.a.F=3Dnew function(){function a(a,d){var e=3Db.a.f.get(a,c)= ;e=3D=3D=3DI&&d&&(e=3D[],b.a.f.set(a,c,e));return e}function d(c){var e=3Da= (c,r);if(e)for(var e=3De.slice(0),k=3D0;k<e.length;k++)e[k](c);b.a.f.clear(= c);"function"=3D=3Dtypeof F&&"function"=3D=3Dtypeof F.cleanData&&F.cleanDat= a([c]);if(f[c.nodeType])for(e=3Dc.firstChild;c=3De;)e=3Dc.nextSibling,8=3D= =3D=3Dc.nodeType&&d(c)} -var c=3D"__ko_domNodeDisposal__"+(new Date).getTime(),e=3D{1:m,8:m,9:m},f= =3D{1:m,9:m};return{Ca:function(b,d){"function"!=3Dtypeof d&&j(Error("Callb= ack must be a function"));a(b,m).push(d)},Xa:function(d,e){var f=3Da(d,r);f= &&(b.a.ga(f,e),0=3D=3Df.length&&b.a.f.set(d,c,I))},A:function(a){if(e[a.nod= eType]&&(d(a),f[a.nodeType])){var c=3D[];b.a.P(c,a.getElementsByTagName("*"= ));for(var k=3D0,l=3Dc.length;k<l;k++)d(c[k])}return a},removeNode:function= (a){b.A(a);a.parentNode&&a.parentNode.removeChild(a)}}};b.A=3Db.a.F.A;b.rem= oveNode=3D -b.a.F.removeNode;b.b("cleanNode",b.A);b.b("removeNode",b.removeNode);b.b("= utils.domNodeDisposal",b.a.F);b.b("utils.domNodeDisposal.addDisposeCallback= ",b.a.F.Ca);b.b("utils.domNodeDisposal.removeDisposeCallback",b.a.F.Xa);b.a= .ta=3Dfunction(a){var d;if("undefined"!=3Dtypeof F)if(F.parseHTML)d=3DF.par= seHTML(a);else{if((d=3DF.clean([a]))&&d[0]){for(a=3Dd[0];a.parentNode&&11!= =3D=3Da.parentNode.nodeType;)a=3Da.parentNode;a.parentNode&&a.parentNode.re= moveChild(a)}}else{var c=3Db.a.D(a).toLowerCase();d=3Dy.createElement("div"= ); -c=3Dc.match(/^<(thead|tbody|tfoot)/)&&[1,"<table>","</table>"]||!c.indexOf= ("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!c.indexOf("<td")||!c.in= dexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||[0,"",""];= a=3D"ignored<div>"+c[1]+a+c[2]+"</div>";for("function"=3D=3Dtypeof x.innerS= hiv?d.appendChild(x.innerShiv(a)):d.innerHTML=3Da;c[0]--;)d=3Dd.lastChild;d= =3Db.a.L(d.lastChild.childNodes)}return d};b.a.ca=3Dfunction(a,d){b.a.ka(a)= ;d=3Db.a.d(d);if(d!=3D=3Dp&&d!=3D=3DI)if("string"!=3Dtypeof d&&(d=3Dd.toStr= ing()), -"undefined"!=3Dtypeof F)F(a).html(d);else for(var c=3Db.a.ta(d),e=3D0;e<c.= length;e++)a.appendChild(c[e])};b.b("utils.parseHtmlFragment",b.a.ta);b.b("= utils.setHtml",b.a.ca);var R=3D{};b.s=3D{ra:function(a){"function"!=3Dtypeo= f a&&j(Error("You can only pass a function to ko.memoization.memoize()"));v= ar b=3D(4294967296*(1+Math.random())|0).toString(16).substring(1)+(42949672= 96*(1+Math.random())|0).toString(16).substring(1);R[b]=3Da;return"\x3c!--[k= o_memo:"+b+"]--\x3e"},hb:function(a,b){var c=3DR[a];c=3D=3D=3DI&&j(Error("C= ouldn't find any memo with ID "+ -a+". Perhaps it's already been unmemoized."));try{return c.apply(p,b||[]),= m}finally{delete R[a]}},ib:function(a,d){var c=3D[];ca(a,c);for(var e=3D0,f= =3Dc.length;e<f;e++){var g=3Dc[e].sb,h=3D[g];d&&b.a.P(h,d);b.s.hb(c[e].Fb,h= );g.nodeValue=3D"";g.parentNode&&g.parentNode.removeChild(g)}},Ua:function(= a){return(a=3Da.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:p}};b.b("memoization",b= .s);b.b("memoization.memoize",b.s.ra);b.b("memoization.unmemoize",b.s.hb);b= .b("memoization.parseMemoText",b.s.Ua);b.b("memoization.unmemoizeDomNodeAnd= Descendants", -b.s.ib);b.Ma=3D{throttle:function(a,d){a.throttleEvaluation=3Dd;var c=3Dp;= return b.j({read:a,write:function(b){clearTimeout(c);c=3DsetTimeout(functio= n(){a(b)},d)}})},notify:function(a,d){a.equalityComparer=3D"always"=3D=3Dd?= u(r):b.m.fn.equalityComparer;return a}};b.b("extenders",b.Ma);b.fb=3Dfuncti= on(a,d,c){this.target=3Da;this.ha=3Dd;this.rb=3Dc;b.p(this,"dispose",this.B= )};b.fb.prototype.B=3Dfunction(){this.Cb=3Dm;this.rb()};b.S=3Dfunction(){th= is.w=3D{};b.a.extend(this,b.S.fn);b.p(this,"subscribe",this.ya);b.p(this,"e= xtend", -this.extend);b.p(this,"getSubscriptionsCount",this.yb)};b.S.fn=3D{ya:funct= ion(a,d,c){c=3Dc||"change";var e=3Dnew b.fb(this,d?a.bind(d):a,function(){b= .a.ga(this.w[c],e)}.bind(this));this.w[c]||(this.w[c]=3D[]);this.w[c].push(= e);return e},notifySubscribers:function(a,d){d=3Dd||"change";this.w[d]&&b.r= .K(function(){b.a.o(this.w[d].slice(0),function(b){b&&b.Cb!=3D=3Dm&&b.ha(a)= })},this)},yb:function(){var a=3D0,b;for(b in this.w)this.w.hasOwnProperty(= b)&&(a+=3Dthis.w[b].length);return a},extend:function(a){var d=3Dthis;if(a)= for(var c in a){var e=3D -b.Ma[c];"function"=3D=3Dtypeof e&&(d=3De(d,a[c]))}return d}};b.Qa=3Dfuncti= on(a){return"function"=3D=3Dtypeof a.ya&&"function"=3D=3Dtypeof a.notifySub= scribers};b.b("subscribable",b.S);b.b("isSubscribable",b.Qa);var C=3D[];b.r= =3D{mb:function(a){C.push({ha:a,La:[]})},end:function(){C.pop()},Wa:fun... [truncated message content] |
From: <com...@bi...> - 2013-07-17 07:33:43
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/714cac410dd1/ Changeset: 714cac410dd1 User: alllee Date: 2013-07-17 03:23:34 Summary: experiment dashboard actions are now crudely functional, still more work to do to convert the stateful actions into POSTs Affected #: 1 file diff -r 2f54ba527206a7899c4a24687bdb4ccc5aed93c0 -r 714cac410dd1d39280f61b26d8e162768d9b51be vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -14,7 +14,7 @@ <div class='tab-content'><div class='tab-pane' id='start'><div data-bind='foreach: experimentMetadataList'> - <h3><span data-bind='text: title'></span> Configurations</h3> + <h3><span data-bind='text: title'></span></h3><div data-bind='foreach: configurations'><div class='alert alert-message'><h4><span data-bind='text: name'>configuration name</span> (treatment id: <span data-bind='text: treatment_id() || "None"'></span>) <span class='pull-right' data-bind='text: date_created'></span></h4> @@ -57,21 +57,21 @@ <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4><div class='btn-group'><a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> - <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' - data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> - <a class='btn' data-content='Register participants for this experiment with actual email addresses' - data-bind='attr: {href: $root.controllerUrl($data, "register-email-list")}'> - <i class='icon-group'></i> register participants</a> - <a class='btn' data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - data-bind='attr: {href: $root.controllerUrl($data, "register-test-participants")}'> - <i class='icon-group'></i> test participants</a> - <a data-content='Remove all participants from the experiment, including any data they may have generated.' - data-bind='attr: {href: $root.controllerUrl($data, "clear-participants")}' class='btn confirm-experiment-action'> - <i class='icon-trash'></i> clear all participants</a> - <a data-action='complete' data-content='Archive and mark this experiment as completed' - data-bind='attr: {href: $root.controllerUrl($data, "archive")}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> - <a data-action='deactivate' data-content='Flag this experiment as inactive' - data-bind='attr: { href: $root.controllerUrl($data, "deactivate")}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</a> + <button class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' + data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</button> + <button class='btn' data-content='Register participants for this experiment with actual email addresses' + data-bind='enable: $root.canRegisterParticipants($data), click: $root.gotoExperimentUrl.bind($data, "register-email-list")'> + <i class='icon-group'></i> register participants</button> + <button class='btn' data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + data-bind='enable: $root.canRegisterParticipants($data), click: $root.gotoExperimentUrl.bind($data, "register-test-participants")'> + <i class='icon-group'></i> test participants</button> + <button data-content='Remove all participants from the experiment, including any data they may have generated.' + data-bind='enable: $root.canClearParticipants($data), attr: {href: $root.controllerUrl($data, "clear-participants")}' class='btn confirm-experiment-action'> + <i class='icon-trash'></i> clear all participants</button> + <button data-action='complete' data-content='Archive and mark this experiment as completed' + data-bind='attr: {href: $root.controllerUrl($data, "archive")}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</button> + <button data-action='deactivate' data-content='Flag this experiment as inactive' + data-bind='attr: { href: $root.controllerUrl($data, "deactivate")}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</button></div></div></script> @@ -84,6 +84,10 @@ var model = ko.mapping.fromJS(dashboardViewModelJson); model.activateStartExperimentTab = activateTabFunctor('#start'); model.activatePendingExperimentsTab = activateTabFunctor('#pending'); + model.gotoExperimentUrl = function(urlAction, experimentModel) { + var url = model.controllerUrl(experimentModel, urlAction); + window.location = url; + } model.controllerUrl = function(experimentModel, urlAction) { return experimentModel.controller_url() + "/" + urlAction; } @@ -103,25 +107,25 @@ model.modify = function() { console.debug("modify existing configuration"); } - model.canDelete = function(experimentModel) { - console.debug("experiment status: " + experimentModel.status()); + model.canClearParticipants = function(experimentModel) { return experimentModel.status() === 'INACTIVE' && experimentModel.participantCount() > 0; } model.canRegisterParticipants = function(experimentModel) { - return ! experimentModel.isArchived() && experimentModel.participantCount() === 0; + return ! experimentModel.isArchived() && (experimentModel.participantCount() === 0); } return model; } var model = new DashboardViewModel(dashboardViewModelJson); ko.applyBindings(model); - $('a.confirm-experiment-action').on("click", function(evt) { + // FIXME: rework this so that we make proper ajax posts on confirmation instead of get requests that modify + // experiment state. + $('a.confirm-experiment-action').click(function(evt) { evt.preventDefault(); var self = $(this); var description = self.attr("data-content"); var href = self.attr("href"); if (self.hasClass('disabled')) { - console.debug("disabled action " + action + " - ignoring"); return false; } bootbox.confirm(description + " Continue?", function(confirmed) { https://bitbucket.org/virtualcommons/vcweb/commits/9aa770b95a35/ Changeset: 9aa770b95a35 User: alllee Date: 2013-07-17 09:33:27 Summary: pushing trees closer together Affected #: 2 files diff -r 714cac410dd1d39280f61b26d8e162768d9b51be -r 9aa770b95a35bb62e36f86c98a6a5e3ddd2e98bf vcweb/bound/static/css/bound/style.css --- a/vcweb/bound/static/css/bound/style.css +++ b/vcweb/bound/static/css/bound/style.css @@ -30,31 +30,35 @@ margin: 0 0 0 -5px; width: 40px; height: 40px; - background: url('/static/images/bound/zero-trees-inactive.png'); + background: url('/static/images/bound/zero-trees-inactive.png') no-repeat; } .zero-harvest-decision.selected { - background: url('/static/images/bound/zero-trees-active.png') !important; + background: url('/static/images/bound/zero-trees-active.png') no-repeat !important; } .zero-harvest-decision:hover { - background: url('/static/images/bound/zero-trees-hover.png'); + background: url('/static/images/bound/zero-trees-hover.png') no-repeat; } .harvest-decision-tree { padding: 0 0 0 0; margin: 0 0 0 -5px; width: 40px; height: 40px; - background: url('/static/images/bound/tree-inactive.png'); + background: url('/static/images/bound/tree-inactive.png') no-repeat; } .harvest-decision-tree.selected { - background: url('/static/images/bound/tree-active.png') !important; + background: url('/static/images/bound/tree-active.png') no-repeat !important; } .harvest-decision-tree:hover { - background: url('/static/images/bound/tree-hover.png'); + background: url('/static/images/bound/tree-hover.png') no-repeat; } .harvest-decision-tree-div { cursor: pointer; - padding: 3px 3px 3px 8px; - margin: 5px 5px 5px 5px; + padding: 1px 1px 1px 8px; + margin: 1px 1px 1px 1px; +} +td.harvest-decision-tree-td { + padding: 0px; + margin: 0px; } col.current-player { background-color: #ffb914; diff -r 714cac410dd1d39280f61b26d8e162768d9b51be -r 9aa770b95a35bb62e36f86c98a6a5e3ddd2e98bf vcweb/bound/templates/bound/participate.html --- a/vcweb/bound/templates/bound/participate.html +++ b/vcweb/bound/templates/bound/participate.html @@ -406,11 +406,14 @@ <div class='row' data-bind='ifnot: isResourceEmpty()'><div class='span8'><h3>Harvest</h3> + {% comment %} + FIXME: disabled <div data-bind='if: submitHarvestDecisionEnabled'><div class='alert alert-success'><i class='icon-leaf'></i> You have currently <span data-bind='text: submitted ? "submitted" : "selected"'></span> a harvest decision of <span class='badge badge-info' data-bind='text: harvestDecision'></span> trees. Click the 'Ok I'm ready' button to continue. </div></div> + {% endcomment %} <div data-bind='ifnot: alive'><div class='alert alert-error'><b><i class='icon-ambulance'></i></b> You didn't harvest enough trees to meet the cost of living and are now @@ -491,9 +494,9 @@ </div></script><script type='text/html' id='harvest-decision-multiselect-template'> - <table class='table'> - <tr class='row harvest-decision-resources' data-bind='foreach: harvestDecisionOptions'> - <td data-bind='attr: { id: "harvest-decision-td-" + $data }'> + <table> + <tr class='harvest-decision-resources' data-bind='foreach: harvestDecisionOptions'> + <td class='harvest-decision-tree-td' data-bind='attr: { id: "harvest-decision-td-" + $data }'><form data-bind='attr: { id: $root.harvestDecisionFormId($data) }'><input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: $root.participantGroupId'/><input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: $data'> Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-07-16 21:27:16
|
New issue 112: bound: tree selection improvements https://bitbucket.org/virtualcommons/vcweb/issue/112/bound-tree-selection-improvements A Lee: Redo tree selection UI using something like http://rateit.codeplex.com/ to mimic familiar select / reselect UIs from rating systems Responsible: alllee |
From: <com...@bi...> - 2013-07-13 00:38:36
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/2f54ba527206/ Changeset: 2f54ba527206 User: alllee Date: 2013-07-13 02:38:20 Summary: displaying all experiment quick actions on dashboard, should disable/enable dynamically instead of hiding Affected #: 2 files diff -r 3bf1217dffcf18c30cc81fa8d797dd9b48fb243f -r 2f54ba527206a7899c4a24687bdb4ccc5aed93c0 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -491,26 +491,10 @@ return "/%s/experimenter" % self.get_absolute_url() @property - def configure_url(self): - return "/%s/configure" % self.get_absolute_url() - - @property def monitor_url(self): return "%s/monitor" % self.controller_url @property - def complete_url(self): - return "%s/complete" % self.controller_url - - @property - def deactivate_url(self): - return "%s/deactivate" % self.controller_url - - @property - def clone_url(self): - return "%s/clone" % self.controller_url - - @property def controller_url(self): return "/experiment/%s" % self.pk @@ -1042,6 +1026,7 @@ 'isArchived': self.is_archived, 'exchangeRate': float(ec.exchange_rate), 'readyParticipants': self.number_of_ready_participants, + 'status': self.status, }) if include_round_data: # XXX: stubs for round data diff -r 3bf1217dffcf18c30cc81fa8d797dd9b48fb243f -r 2f54ba527206a7899c4a24687bdb4ccc5aed93c0 vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -59,35 +59,19 @@ <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a><a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> - <span data-bind='if: $root.canRegisterParticipants($data)'> - <a class='btn' data-content='Register participants for this experiment with actual email addresses' - data-bind='attr: {href: $root.controllerUrl($data, "register-email-list")}'> - <i class='icon-group'></i> register participants</a> - <a class='btn' data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - data-bind='attr: {href: $root.controllerUrl($data, "register-test-participants")}'> - <i class='icon-group'></i> test participants</a> - </span> - {% comment %} - FIXME: update to KO logic - {% if not e.is_archived %} - {% if e.participant_set.count == 0 %} - <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> - <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - class='btn' href='{{e.controller_url}}/register-test-participants'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> - {% else %} - <a data-content='Remove all participants from the experiment, including any data they may have generated.' href='{{e.controller_url}}/clear-participants' class='btn confirm-experiment-action'> - <i class='icon-trash'></i> clear all participants</a> - {% endif %} - {% if e.is_active %} - <a data-action='complete' data-content='Mark this experiment as completed' href='{{e.complete_url}}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> - <a data-action='deactivate' data-content='deactivate this experiment' href='{{e.deactivate_url}}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</a> - {% else %} - <a class='btn' data-content='Configure this experiment' href='{{e.configure_url}}'><i class='icon-wrench'></i> configure</a> - {% endif %} - {% endif %} - {% endcomment %} + <a class='btn' data-content='Register participants for this experiment with actual email addresses' + data-bind='attr: {href: $root.controllerUrl($data, "register-email-list")}'> + <i class='icon-group'></i> register participants</a> + <a class='btn' data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + data-bind='attr: {href: $root.controllerUrl($data, "register-test-participants")}'> + <i class='icon-group'></i> test participants</a> + <a data-content='Remove all participants from the experiment, including any data they may have generated.' + data-bind='attr: {href: $root.controllerUrl($data, "clear-participants")}' class='btn confirm-experiment-action'> + <i class='icon-trash'></i> clear all participants</a> + <a data-action='complete' data-content='Archive and mark this experiment as completed' + data-bind='attr: {href: $root.controllerUrl($data, "archive")}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> + <a data-action='deactivate' data-content='Flag this experiment as inactive' + data-bind='attr: { href: $root.controllerUrl($data, "deactivate")}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</a></div></div></script> @@ -119,6 +103,10 @@ model.modify = function() { console.debug("modify existing configuration"); } + model.canDelete = function(experimentModel) { + console.debug("experiment status: " + experimentModel.status()); + return experimentModel.status() === 'INACTIVE' && experimentModel.participantCount() > 0; + } model.canRegisterParticipants = function(experimentModel) { return ! experimentModel.isArchived() && experimentModel.participantCount() === 0; } Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-12 23:46:34
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/3bf1217dffcf/ Changeset: 3bf1217dffcf User: alllee Date: 2013-07-13 01:46:19 Summary: tweaking experimenter dashboard UI a bit more, running experiments are included first and fixing controller url links Affected #: 2 files diff -r 87eecaa1a706ea8b2efc706621b5b4b0af8850ea -r 3bf1217dffcf18c30cc81fa8d797dd9b48fb243f vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -6,13 +6,13 @@ {% comment %} FIXME: UI refinement needed as the list of experiments grows {% endcomment %} <div id='dashboard'><ul class='nav nav-tabs'> - <li class='active'><a href='#start' data-toggle='tab'><i class='icon-beaker'></i> Start a new experiment</a></li> + <li class='active'><a href='#running' data-toggle='tab'><i class='icon-wrench'></i> Running experiments</a></li><li><a href='#pending' data-toggle='tab'><i class='icon-wrench'></i> Pending experiments</a></li> - <li><a href='#running' data-toggle='tab'><i class='icon-wrench'></i> Running experiments</a></li><li><a href='#archived' data-toggle='tab'><i class='icon-archive'></i> Archived experiments</a></li> + <li><a href='#start' data-toggle='tab'><i class='icon-beaker'></i> Start a new experiment</a></li></ul><div class='tab-content'> - <div class='tab-pane active' id='start'> + <div class='tab-pane' id='start'><div data-bind='foreach: experimentMetadataList'><h3><span data-bind='text: title'></span> Configurations</h3><div data-bind='foreach: configurations'> @@ -36,9 +36,9 @@ <div data-bind='template: { name: "experiment-controller-template", foreach: pendingExperiments }'></div></div> - <div class='tab-pane' id='running'> + <div class='tab-pane active' id='running'><div data-bind='if: runningExperiments().length === 0'> - No running experiments. + You currently don't have any active experiments - would you like to <a href='#start' data-bind='click: activateStartExperimentTab'>create a new one?</a>. </div><div data-bind='template: { name: "experiment-controller-template", foreach: runningExperiments }'></div></div> @@ -53,17 +53,19 @@ {% endblock page %} {% block javascript %} <script type='text/html' id='experiment-controller-template'> + <div class='alert alert-message'><h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4><div class='btn-group'><a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a><a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a><span data-bind='if: $root.canRegisterParticipants($data)'> - <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> - <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - class='btn' href='{{e.controller_url}}/register-test-participants'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> + <a class='btn' data-content='Register participants for this experiment with actual email addresses' + data-bind='attr: {href: $root.controllerUrl($data, "register-email-list")}'> + <i class='icon-group'></i> register participants</a> + <a class='btn' data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + data-bind='attr: {href: $root.controllerUrl($data, "register-test-participants")}'> + <i class='icon-group'></i> test participants</a></span> {% comment %} FIXME: update to KO logic @@ -87,6 +89,7 @@ {% endif %} {% endcomment %} </div> +</div></script> {{ block.super }} <script type='text/javascript'> @@ -95,16 +98,15 @@ function DashboardViewModel(dashboardViewModelJson) { var self = this; var model = ko.mapping.fromJS(dashboardViewModelJson); + model.activateStartExperimentTab = activateTabFunctor('#start'); + model.activatePendingExperimentsTab = activateTabFunctor('#pending'); + model.controllerUrl = function(experimentModel, urlAction) { + return experimentModel.controller_url() + "/" + urlAction; + } model.createNewExperiment = function(configurationModel) { - console.debug("create new experiment from configuration"); - console.debug(configurationModel); Dajaxice.vcweb.core.create_experiment(function(data) { if (data.success) { - console.debug("created new experiment: "); - console.debug(data); - console.debug(data.experiment); pendingExperimentModel = ko.mapping.fromJS(data.experiment); - console.debug(pendingExperimentModel); model.pendingExperiments.push(pendingExperimentModel); } }, diff -r 87eecaa1a706ea8b2efc706621b5b4b0af8850ea -r 3bf1217dffcf18c30cc81fa8d797dd9b48fb243f vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -95,7 +95,7 @@ experiment_metadata_list.append(data) experiment_status_dict = defaultdict(list) for e in Experiment.objects.for_experimenter(experimenter): - experiment_status_dict[e.status].append(e.to_dict(attrs=('monitor_url', 'status_line'))) + experiment_status_dict[e.status].append(e.to_dict(attrs=('monitor_url', 'status_line', 'controller_url'))) pending_experiments = experiment_status_dict['INACTIVE'] running_experiments = experiment_status_dict['ACTIVE'] + experiment_status_dict['ROUND_IN_PROGRESS'] archived_experiments = experiment_status_dict['ARCHIVED'] Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-12 06:05:02
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/87eecaa1a706/ Changeset: 87eecaa1a706 User: alllee Date: 2013-07-12 08:04:49 Summary: tweaking query performance on dashboard, combining 3 separate queries into a single one and partitioning afterwards Affected #: 3 files diff -r 8e93ac74388b8c78608397df18a58f24d6a036d4 -r 87eecaa1a706ea8b2efc706621b5b4b0af8850ea vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -164,7 +164,7 @@ 'description': self.description, } if include_configurations: - configurations = [ec.to_dict() for ec in ExperimentConfiguration.objects.filter(experiment_metadata=self)] + configurations = [ec.to_dict() for ec in ExperimentConfiguration.objects.select_related('creator').filter(experiment_metadata=self)] data['configurations'] = configurations return data @@ -347,16 +347,16 @@ return self.filter(status__in=ExperimentQuerySet.ACTIVE_STATUSES, **kwargs) def for_participant(self, participant, **kwargs): return participant.experiments.filter(status__in=ExperimentQuerySet.ACTIVE_STATUSES) + def for_experimenter(self, experimenter, **kwargs): + return self.select_related('experimenter').filter(experimenter=experimenter, **kwargs) class Experiment(models.Model): """ - Experiment instances are a concrete parameterization of an ExperimentMetadata record, with associated - ExperimentConfiguration, Experimenter, etc. In other words, they represent an actual experiment run. - """ + An Experiment represents a concrete treatment run for a given ExperimentMetadata -- a combination of + ExperimentMetadata and ExperimentConfiguration for a given Experimenter """ Status = Choices( ('INACTIVE', _('Not active')), ('ACTIVE', _('Active, no round in progress')), - ('PAUSED', _('Paused')), ('ROUND_IN_PROGRESS', _('Round in progress')), ('COMPLETED', _('Completed'))) authentication_code = models.CharField(max_length=32, default="vcweb.auth.code") @@ -377,7 +377,7 @@ # status = StatusField() status = models.CharField(max_length=32, choices=Status, default=Status.INACTIVE) """ - the status of an experiment can be either INACTIVE, ACTIVE, PAUSED, ROUND_IN_PROGRESS, or COMPLETED + the status of an experiment can be either INACTIVE, ACTIVE, ROUND_IN_PROGRESS, or COMPLETED """ date_created = models.DateTimeField(default=datetime.now) last_modified = AutoDateTimeField(default=datetime.now) diff -r 8e93ac74388b8c78608397df18a58f24d6a036d4 -r 87eecaa1a706ea8b2efc706621b5b4b0af8850ea vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -7,6 +7,7 @@ <div id='dashboard'><ul class='nav nav-tabs'><li class='active'><a href='#start' data-toggle='tab'><i class='icon-beaker'></i> Start a new experiment</a></li> + <li><a href='#pending' data-toggle='tab'><i class='icon-wrench'></i> Pending experiments</a></li><li><a href='#running' data-toggle='tab'><i class='icon-wrench'></i> Running experiments</a></li><li><a href='#archived' data-toggle='tab'><i class='icon-archive'></i> Archived experiments</a></li></ul> @@ -24,16 +25,17 @@ <a class='btn' disabled data-bind='click: $root.modify'><i class='icon-edit'></i> edit configuration</a></div></div> - </div></div> - <h3>Inactive experiments</h3> - <div data-bind='if: inactiveExperiments().length === 0'> - No inactive experiments. + </div> + <div class='tab-pane' id='pending'> + <h3>Pending experiments</h3> + <div data-bind='if: pendingExperiments().length === 0'> + No pending experiments. </div> - <div data-bind='template: { name: "experiment-controller-template", foreach: inactiveExperiments }'></div> + <div data-bind='template: { name: "experiment-controller-template", foreach: pendingExperiments }'></div> + </div> - </div><div class='tab-pane' id='running'><div data-bind='if: runningExperiments().length === 0'> No running experiments. @@ -101,9 +103,9 @@ console.debug("created new experiment: "); console.debug(data); console.debug(data.experiment); - inactiveExperimentModel = ko.mapping.fromJS(data.experiment); - console.debug(inactiveExperimentModel); - model.inactiveExperiments.push(inactiveExperimentModel); + pendingExperimentModel = ko.mapping.fromJS(data.experiment); + console.debug(pendingExperimentModel); + model.pendingExperiments.push(pendingExperimentModel); } }, { experiment_configuration_id: configurationModel.pk() }); diff -r 8e93ac74388b8c78608397df18a58f24d6a036d4 -r 87eecaa1a706ea8b2efc706621b5b4b0af8850ea vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -1,3 +1,4 @@ +from collections import defaultdict from django.contrib import auth, messages from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse @@ -92,12 +93,15 @@ for em in ExperimentMetadata.objects.all(): data = em.to_dict(include_configurations=True) experiment_metadata_list.append(data) - inactive_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.inactive(experimenter=experimenter)] - running_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.active(experimenter=experimenter)] - archived_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.archived(experimenter=experimenter)] + experiment_status_dict = defaultdict(list) + for e in Experiment.objects.for_experimenter(experimenter): + experiment_status_dict[e.status].append(e.to_dict(attrs=('monitor_url', 'status_line'))) + pending_experiments = experiment_status_dict['INACTIVE'] + running_experiments = experiment_status_dict['ACTIVE'] + experiment_status_dict['ROUND_IN_PROGRESS'] + archived_experiments = experiment_status_dict['ARCHIVED'] data = { 'experimentMetadataList': experiment_metadata_list, - 'inactiveExperiments': inactive_experiments, + 'pendingExperiments': pending_experiments, 'runningExperiments': running_experiments, 'archivedExperiments': archived_experiments, } Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-07-11 23:18:43
|
New issue 111: bound: improve advance to next round logic https://bitbucket.org/virtualcommons/vcweb/issue/111/bound-improve-advance-to-next-round-logic A Lee: should advance to next round when all *living* participants have submitted a decision Responsible: alllee |
From: A L. <iss...@bi...> - 2013-07-11 19:06:28
|
New issue 110: embed qualtrics survey as an iframe https://bitbucket.org/virtualcommons/vcweb/issue/110/embed-qualtrics-survey-as-an-iframe A Lee: e.g., http://sites.duke.edu/blog/2011/10/31/how-can-i-embed-a-duke-qualtrics-form-into-my-wordpress-post-or-page/ http://www.qualtrics.com/research-suite/distributing-surveys/ Responsible: alllee |
From: <com...@bi...> - 2013-07-11 00:15:41
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/8e93ac74388b/ Changeset: 8e93ac74388b User: alllee Date: 2013-07-11 02:15:23 Summary: wiring up creation of new experiments from experiment configurations Affected #: 4 files diff -r c4fb999359c21f17f157d409c05aa0fa676cfc87 -r 8e93ac74388b8c78608397df18a58f24d6a036d4 vcweb/core/ajax.py --- a/vcweb/core/ajax.py +++ b/vcweb/core/ajax.py @@ -10,7 +10,7 @@ from vcweb.core.forms import BookmarkExperimentMetadataForm from vcweb.core.http import JsonResponse from vcweb.core.models import (Experiment, RoundData, ExperimentMetadata, BookmarkedExperimentMetadata, - get_chat_message_parameter, ) + get_chat_message_parameter, ExperimentConfiguration) import logging logger = logging.getLogger(__name__) @@ -108,10 +108,20 @@ return JsonResponse(dumps({'success': False})) +@experimenter_required @dajaxice_register(method='POST') def create_experiment(request, experiment_configuration_id): logger.debug("incoming create experiment request POST: %s with id %s", request.POST, experiment_configuration_id, ) - return JsonResponse(dumps({'success': True})) + experiment_configuration = get_object_or_404(ExperimentConfiguration.objects.select_related('experiment_metadata'), pk=experiment_configuration_id) + experimenter = request.user.experimenter + authentication_code = 'test' + e = Experiment.objects.create(experimenter=experimenter, + authentication_code=authentication_code, + experiment_metadata=experiment_configuration.experiment_metadata, + experiment_configuration=experiment_configuration, + status=Experiment.Status.INACTIVE + ) + return JsonResponse(dumps({'success': True, 'experiment': e.to_dict(attrs=('monitor_url', 'status_line',)) })) @experimenter_required diff -r c4fb999359c21f17f157d409c05aa0fa676cfc87 -r 8e93ac74388b8c78608397df18a58f24d6a036d4 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -341,6 +341,8 @@ return self.completed(**kwargs) def completed(self, **kwargs): return self.filter(status='COMPLETED', **kwargs) + def inactive(self, **kwargs): + return self.filter(status='INACTIVE', **kwargs) def active(self, **kwargs): return self.filter(status__in=ExperimentQuerySet.ACTIVE_STATUSES, **kwargs) def for_participant(self, participant, **kwargs): diff -r c4fb999359c21f17f157d409c05aa0fa676cfc87 -r 8e93ac74388b8c78608397df18a58f24d6a036d4 vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -7,7 +7,7 @@ <div id='dashboard'><ul class='nav nav-tabs'><li class='active'><a href='#start' data-toggle='tab'><i class='icon-beaker'></i> Start a new experiment</a></li> - <li><a href='#manage' data-toggle='tab'><i class='icon-wrench'></i> Manage running experiments</a></li> + <li><a href='#running' data-toggle='tab'><i class='icon-wrench'></i> Running experiments</a></li><li><a href='#archived' data-toggle='tab'><i class='icon-archive'></i> Archived experiments</a></li></ul><div class='tab-content'> @@ -21,27 +21,48 @@ <br><div class='btn-group'><a class='btn' data-bind='click: $root.createNewExperiment.bind($data)'><i class='icon-plus-sign'></i> start new experiment</a> - <a class='btn' data-bind='click: $root.modify'><i class='icon-edit'></i> edit configuration</a> + <a class='btn' disabled data-bind='click: $root.modify'><i class='icon-edit'></i> edit configuration</a></div></div></div></div> + <h3>Inactive experiments</h3> + <div data-bind='if: inactiveExperiments().length === 0'> + No inactive experiments. + </div> + <div data-bind='template: { name: "experiment-controller-template", foreach: inactiveExperiments }'></div> + </div> - <div class='tab-pane' id='manage'> - <div data-bind='foreach: runningExperiments'> - <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4> - <div class='btn-group'> - <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> - <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' - data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> - <span data-bind='if: $root.canRegisterParticipants($data)'> - <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> - <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - class='btn' href='{{e.controller_url}}/register-test-participants'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> - </span> + <div class='tab-pane' id='running'> + <div data-bind='if: runningExperiments().length === 0'> + No running experiments. + </div> + <div data-bind='template: { name: "experiment-controller-template", foreach: runningExperiments }'></div> + </div> + <div class='tab-pane' id='archived'> + <div data-bind='if: archivedExperiments().length == 0'> + No archived experiments. + </div> + <div data-bind='template: { name: "experiment-controller-template", foreach: archivedExperiments }'></div> + </div> + </div> +</div> +{% endblock page %} +{% block javascript %} +<script type='text/html' id='experiment-controller-template'> + <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4> + <div class='btn-group'> + <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> + <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' + data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> + <span data-bind='if: $root.canRegisterParticipants($data)'> + <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> + <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + class='btn' href='{{e.controller_url}}/register-test-participants'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> + </span> {% comment %} FIXME: update to KO logic {% if not e.is_archived %} @@ -63,26 +84,8 @@ {% endif %} {% endif %} {% endcomment %} - </div> - </div> - </div> - <div class='tab-pane' id='archived'> - <div data-bind='if: archivedExperiments().length == 0'> - No archived experiments. - </div> - <div data-bind='foreach: archivedExperiments'> - <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4> - <div class='btn-group'> - <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> - <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' - data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> - </div> - </div> - </div></div> -</div> -{% endblock page %} -{% block javascript %} +</script> {{ block.super }} <script type='text/javascript'> var dashboardViewModelJson = $.parseJSON("{{ dashboardViewModelJson|escapejs }}"); @@ -97,17 +100,21 @@ if (data.success) { console.debug("created new experiment: "); console.debug(data); + console.debug(data.experiment); + inactiveExperimentModel = ko.mapping.fromJS(data.experiment); + console.debug(inactiveExperimentModel); + model.inactiveExperiments.push(inactiveExperimentModel); } }, { experiment_configuration_id: configurationModel.pk() }); } + model.cloneExperiment = function() { + console.debug("cloning experiment"); + } model.modify = function() { console.debug("modify existing configuration"); } - model.cloneExperiment = function() { - console.debug("cloning experiment"); - } model.canRegisterParticipants = function(experimentModel) { return ! experimentModel.isArchived() && experimentModel.participantCount() === 0; } diff -r c4fb999359c21f17f157d409c05aa0fa676cfc87 -r 8e93ac74388b8c78608397df18a58f24d6a036d4 vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -92,10 +92,12 @@ for em in ExperimentMetadata.objects.all(): data = em.to_dict(include_configurations=True) experiment_metadata_list.append(data) + inactive_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.inactive(experimenter=experimenter)] running_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.active(experimenter=experimenter)] archived_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.archived(experimenter=experimenter)] data = { 'experimentMetadataList': experiment_metadata_list, + 'inactiveExperiments': inactive_experiments, 'runningExperiments': running_experiments, 'archivedExperiments': archived_experiments, } Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-09 22:18:36
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/c4fb999359c2/ Changeset: c4fb999359c2 User: alllee Date: 2013-07-10 00:18:24 Summary: starting to wire up dajaxice POSTs when manipulating experiments via the experimenter dashboard Affected #: 4 files diff -r 5d706e056a0132b89de4b8725b623f73e5015bcf -r c4fb999359c21f17f157d409c05aa0fa676cfc87 vcweb/core/ajax.py --- a/vcweb/core/ajax.py +++ b/vcweb/core/ajax.py @@ -1,10 +1,12 @@ +from dajaxice.decorators import dajaxice_register + from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.template.loader_tags import BlockNode, ExtendsNode from django.template import loader, Context, RequestContext from vcweb.core import dumps -from vcweb.core.decorators import experimenter_required, dajaxice_register +from vcweb.core.decorators import experimenter_required from vcweb.core.forms import BookmarkExperimentMetadataForm from vcweb.core.http import JsonResponse from vcweb.core.models import (Experiment, RoundData, ExperimentMetadata, BookmarkedExperimentMetadata, @@ -106,6 +108,12 @@ return JsonResponse(dumps({'success': False})) +@dajaxice_register(method='POST') +def create_experiment(request, experiment_configuration_id): + logger.debug("incoming create experiment request POST: %s with id %s", request.POST, experiment_configuration_id, ) + return JsonResponse(dumps({'success': True})) + + @experimenter_required @dajaxice_register def save_experimenter_notes(request, experiment_id, notes=None): diff -r 5d706e056a0132b89de4b8725b623f73e5015bcf -r c4fb999359c21f17f157d409c05aa0fa676cfc87 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -306,6 +306,7 @@ def to_dict(self, **kwargs): return { + 'pk': self.pk, 'name': self.name, 'treatment_id': self.treatment_id, 'date_created': self.date_created.strftime("%m-%d-%Y %H:%M"), @@ -333,6 +334,7 @@ class ExperimentQuerySet(models.query.QuerySet): + ACTIVE_STATUSES = ('ACTIVE', 'ROUND_IN_PROGRESS') def public(self, **kwargs): return self.filter(experiment_configuration__is_public=True, **kwargs) def archived(self, **kwargs): @@ -340,9 +342,9 @@ def completed(self, **kwargs): return self.filter(status='COMPLETED', **kwargs) def active(self, **kwargs): - return self.filter(status__in=('ACTIVE', 'ROUND_IN_PROGRESS'), **kwargs) + return self.filter(status__in=ExperimentQuerySet.ACTIVE_STATUSES, **kwargs) def for_participant(self, participant, **kwargs): - return participant.experiments.filter(status__in=('ACTIVE', 'ROUND_IN_PROGRESS')) + return participant.experiments.filter(status__in=ExperimentQuerySet.ACTIVE_STATUSES) class Experiment(models.Model): """ @@ -479,6 +481,7 @@ def namespace(self): return self.experiment_metadata.namespace +# FIXME: remove these after new model of dashboard experiment controller is done # The following URL helper properties are generic experiment management URLs # available to experimenters but not participants @property diff -r 5d706e056a0132b89de4b8725b623f73e5015bcf -r c4fb999359c21f17f157d409c05aa0fa676cfc87 vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -12,17 +12,16 @@ </ul><div class='tab-content'><div class='tab-pane active' id='start'> - Experiment metadata objects go here <div data-bind='foreach: experimentMetadataList'> - <h3 data-bind='text: title'></h3> + <h3><span data-bind='text: title'></span> Configurations</h3><div data-bind='foreach: configurations'><div class='alert alert-message'><h4><span data-bind='text: name'>configuration name</span> (treatment id: <span data-bind='text: treatment_id() || "None"'></span>) <span class='pull-right' data-bind='text: date_created'></span></h4><span data-bind='text: number_of_rounds'></span> rounds, groups of <span data-bind='text: max_group_size'></span><br><div class='btn-group'> - <a class='btn' data-bind='click: $root.createNewExperiment'><i class='icon-plus-sign'></i> new</a> - <a class='btn' data-bind='click: $root.modify'><i class='icon-edit'></i> edit</a> + <a class='btn' data-bind='click: $root.createNewExperiment.bind($data)'><i class='icon-plus-sign'></i> start new experiment</a> + <a class='btn' data-bind='click: $root.modify'><i class='icon-edit'></i> edit configuration</a></div></div> @@ -36,6 +35,13 @@ <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a><a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> + <span data-bind='if: $root.canRegisterParticipants($data)'> + <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> + <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + class='btn' href='{{e.controller_url}}/register-test-participants'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> + </span> {% comment %} FIXME: update to KO logic {% if not e.is_archived %} @@ -75,41 +81,6 @@ </div></div></div> -<hr> -<div id="experiment-list"> - {% for e in experiments %} - <div class='alert alert-message'> - <h4>{{e.status_line}} <span class='pull-right'><span class='badge badge-important'>{{e.participant_set.count}}</span> participants</span></h4> - <div class='btn-group'> - <a class='btn' href='{{e.monitor_url}}'><i class='icon-zoom-in'></i> monitor</a> - <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' - href='{{e.clone_url}}'><i class='icon-copy'></i> clone</a> - {% if not e.is_archived %} - {% if e.participant_set.count == 0 %} - <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> - <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' - class='btn' href='{{e.controller_url}}/register-test-participants'> - <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> - {% else %} - <a data-content='Remove all participants from the experiment, including any data they may have generated.' href='{{e.controller_url}}/clear-participants' class='btn confirm-experiment-action'> - <i class='icon-trash'></i> clear all participants</a> - {% endif %} - {% if e.is_active %} - <a data-action='complete' data-content='Mark this experiment as completed' href='{{e.complete_url}}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> - <a data-action='deactivate' data-content='deactivate this experiment' href='{{e.deactivate_url}}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</a> - {% else %} - <a class='btn' data-content='Configure this experiment' href='{{e.configure_url}}'><i class='icon-wrench'></i> configure</a> - {% endif %} - {% endif %} - </div> - </div> - {% empty %} - <div class='alert alert-error'> - You are not currently running any experiments. - </div> - {% endfor %} -</div> {% endblock page %} {% block javascript %} {{ block.super }} @@ -119,12 +90,28 @@ function DashboardViewModel(dashboardViewModelJson) { var self = this; var model = ko.mapping.fromJS(dashboardViewModelJson); - model.createNewExperiment = function() { + model.createNewExperiment = function(configurationModel) { console.debug("create new experiment from configuration"); + console.debug(configurationModel); + Dajaxice.vcweb.core.create_experiment(function(data) { + if (data.success) { + console.debug("created new experiment: "); + console.debug(data); + } + }, + { experiment_configuration_id: configurationModel.pk() }); + } model.modify = function() { console.debug("modify existing configuration"); } + model.cloneExperiment = function() { + console.debug("cloning experiment"); + } + model.canRegisterParticipants = function(experimentModel) { + return ! experimentModel.isArchived() && experimentModel.participantCount() === 0; + } + return model; } var model = new DashboardViewModel(dashboardViewModelJson); diff -r 5d706e056a0132b89de4b8725b623f73e5015bcf -r c4fb999359c21f17f157d409c05aa0fa676cfc87 vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -92,7 +92,6 @@ for em in ExperimentMetadata.objects.all(): data = em.to_dict(include_configurations=True) experiment_metadata_list.append(data) - logger.debug("experiment metadata list: %s", experiment_metadata_list) running_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.active(experimenter=experimenter)] archived_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.archived(experimenter=experimenter)] data = { Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-07-07 07:18:44
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/5d706e056a01/ Changeset: 5d706e056a01 User: alllee Date: 2013-07-07 09:18:33 Summary: initial stubs for managing running / archived experiments Affected #: 3 files diff -r 8d18dfa337252f71d737114f0b0acceb88a10b55 -r 5d706e056a0132b89de4b8725b623f73e5015bcf vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -335,6 +335,8 @@ class ExperimentQuerySet(models.query.QuerySet): def public(self, **kwargs): return self.filter(experiment_configuration__is_public=True, **kwargs) + def archived(self, **kwargs): + return self.completed(**kwargs) def completed(self, **kwargs): return self.filter(status='COMPLETED', **kwargs) def active(self, **kwargs): diff -r 8d18dfa337252f71d737114f0b0acceb88a10b55 -r 5d706e056a0132b89de4b8725b623f73e5015bcf vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -30,10 +30,48 @@ </div></div><div class='tab-pane' id='manage'> - Manage experiments here + <div data-bind='foreach: runningExperiments'> + <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4> + <div class='btn-group'> + <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> + <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' + data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> + {% comment %} + FIXME: update to KO logic + {% if not e.is_archived %} + {% if e.participant_set.count == 0 %} + <a class='btn' data-content='Register participants for this experiment with actual email addresses' href='{{e.controller_url}}/register-email-list'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a> + <a data-content='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + class='btn' href='{{e.controller_url}}/register-test-participants'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> + {% else %} + <a data-content='Remove all participants from the experiment, including any data they may have generated.' href='{{e.controller_url}}/clear-participants' class='btn confirm-experiment-action'> + <i class='icon-trash'></i> clear all participants</a> + {% endif %} + {% if e.is_active %} + <a data-action='complete' data-content='Mark this experiment as completed' href='{{e.complete_url}}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> + <a data-action='deactivate' data-content='deactivate this experiment' href='{{e.deactivate_url}}' class='btn confirm-experiment-action'><i class='icon-off'></i> deactivate</a> + {% else %} + <a class='btn' data-content='Configure this experiment' href='{{e.configure_url}}'><i class='icon-wrench'></i> configure</a> + {% endif %} + {% endif %} + {% endcomment %} + </div> + </div></div><div class='tab-pane' id='archived'> - Archived experiments go here + <div data-bind='if: archivedExperiments().length == 0'> + No archived experiments. + </div> + <div data-bind='foreach: archivedExperiments'> + <h4><span data-bind='text: status_line'></span><span class='pull-right'><span data-bind='text: participantCount' class='badge badge-important'></span> participants</span></h4> + <div class='btn-group'> + <a class='btn' data-bind='attr: { href: monitor_url }'><i class='icon-zoom-in'></i> monitor</a> + <a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' + data-bind='click: $root.cloneExperiment'><i class='icon-copy'></i> clone</a> + </div> + </div></div></div></div> diff -r 8d18dfa337252f71d737114f0b0acceb88a10b55 -r 5d706e056a0132b89de4b8725b623f73e5015bcf vcweb/core/views.py --- a/vcweb/core/views.py +++ b/vcweb/core/views.py @@ -93,13 +93,16 @@ data = em.to_dict(include_configurations=True) experiment_metadata_list.append(data) logger.debug("experiment metadata list: %s", experiment_metadata_list) + running_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.active(experimenter=experimenter)] + archived_experiments = [e.to_dict(attrs=('monitor_url', 'status_line')) for e in Experiment.objects.archived(experimenter=experimenter)] data = { - 'experimentMetadataList': experiment_metadata_list + 'experimentMetadataList': experiment_metadata_list, + 'runningExperiments': running_experiments, + 'archivedExperiments': archived_experiments, } return dumps(data) - def get_context_data(self, **kwargs): context = super(Dashboard, self).get_context_data(**kwargs) user = self.request.user Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |