virtualcommons-svn Mailing List for Virtual Commons Experiment Software (Page 31)
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: Bitbucket <com...@bi...> - 2011-06-22 08:51:09
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/54c7b116e4e9/ changeset: 54c7b116e4e9 user: alllee date: 2011-06-22 10:39:05 summary: minor ui changes, increasing setTimeout in submit, still has some issues w/ validation affected #: 3 files (59 bytes) --- a/vcweb/core/templates/experimenter/monitor.html Wed Jun 22 00:57:02 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Wed Jun 22 01:39:05 2011 -0700 @@ -104,7 +104,7 @@ {% endblock %} {% block page %} {{block.super}} -<h3>{{experiment.status_line}}</h3> +<h3>{{experiment}}</h3><span>You are logged in as {{request.user.experimenter}}.</span><hr/><div id='experimenter-sidebar'> --- a/vcweb/core/templates/includes/participant.events.html Wed Jun 22 00:57:02 2011 -0700 +++ b/vcweb/core/templates/includes/participant.events.html Wed Jun 22 01:39:05 2011 -0700 @@ -13,7 +13,8 @@ "message_type": event_type, {% if participant_group_relationship %} "participant_group_relationship_id": {{participant_group_relationship.pk}}, -{% else %} +{% endif %} +{% if participant_experiment_relationship %} "participant_experiment_relationship_id": {{participant_experiment_relationship.pk}}, {% endif %} "message": payload --- a/vcweb/forestry/templates/forestry/participate.html Wed Jun 22 00:57:02 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Wed Jun 22 01:39:05 2011 -0700 @@ -49,8 +49,8 @@ s.send(createSubmitEvent(harvestDecisionValue)); setTimeout(function() { $('#forestry-form').submit(); - }, 300); - return true; + }, 800); + return false; }); }); </script> http://bitbucket.org/virtualcommons/vcweb/changeset/e22e9c057372/ changeset: e22e9c057372 user: alllee date: 2011-06-22 10:50:58 summary: only sending the submit event if there's something to send, TODO: work out proper integration with jquery validate, it shouldn't send it out if it fails validation. affected #: 1 file (16 bytes) --- a/vcweb/forestry/templates/forestry/participate.html Wed Jun 22 01:39:05 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Wed Jun 22 01:50:58 2011 -0700 @@ -37,16 +37,18 @@ var harvestDecisionValue = $('#harvest-decision-id').val(); // FIXME: this isn't getting sent through properly before the submit occurs. // Should work out communicating directly from Django to the tornadio process. - $('#submit').attr('disabled', true); - s.send(createSubmitEvent(harvestDecisionValue)); - $('#forestry-form').delay(500).submit(); - return false; + // $('#submit-harvest').attr('disabled', true); + if (harvestDecisionValue) { + s.send(createSubmitEvent(harvestDecisionValue)); + } + return true; }); */ $('#submit-harvest').click(function() { var harvestDecisionValue = $('#harvest-decision-id').val(); - $('#submit-harvest').attr('disabled', true); - s.send(createSubmitEvent(harvestDecisionValue)); + if (harvestDecisionValue) { + s.send(createSubmitEvent(harvestDecisionValue)); + } setTimeout(function() { $('#forestry-form').submit(); }, 800); 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: Bitbucket <com...@bi...> - 2011-06-22 08:04:17
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/70b77991ec16/ changeset: 70b77991ec16 user: alllee date: 2011-06-22 09:57:02 summary: hiding chat div during non-chat rounds. should move to datatables.net implementation, otherwise there's too much data to manage via accordions. affected #: 1 file (82 bytes) --- a/vcweb/core/templates/experimenter/monitor.html Wed Jun 22 00:48:27 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Wed Jun 22 00:57:02 2011 -0700 @@ -250,6 +250,7 @@ <div class='info ui-corner-all'><span class='ui-icon ui-icon-info icon-left'></span>No participant data for this round.</div> {% endif %} {% comment %} participant chats {% endcomment %} + {% if round_data.round_configuration.is_chat_round %} <h4 class='collapsible'>Chat data</h4><div id='chat-div-{{round_data.pk}}' class='chat notice ui-corner-all'> {% for chat_message in round_data.chat_messages.all %} @@ -259,6 +260,7 @@ </div> {% endfor %} </div> + {% endif %} </div> {% empty %} <div class='alert ui-state-highlight ui-corner-all'><span class='ui-icon ui-icon-alert icon-left'></span>Round data is not yet available.</div> 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: Bitbucket <com...@bi...> - 2011-06-22 07:48:37
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/f1d5f9de1513/ changeset: f1d5f9de1513 user: alllee date: 2011-06-22 09:48:27 summary: adding delay to harvest decision submission, minor improvements to logging in preparation for pretest tomorrow affected #: 6 files (346 bytes) --- a/vcweb/core/models.py Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/core/models.py Wed Jun 22 00:48:27 2011 -0700 @@ -694,7 +694,7 @@ return Template(template_string).substitute(kwargs, round_number=self.display_number, participant_id=participant_id) def __unicode__(self): - return u"%s > %s %s" % (self.display_label, self.experiment_configuration, self.sequence_label) + return u"%s (%s)" % (self.display_label, self.sequence_label) @property def display_label(self): @@ -1043,7 +1043,7 @@ next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) - logger.debug("%s (%s)", group_data_value, created) + logger.debug("group data value: %s (%s)", group_data_value, created) if not created: group_data_value.value = value group_data_value.save() --- a/vcweb/core/templates/base-vcweb-form.html Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/core/templates/base-vcweb-form.html Wed Jun 22 00:48:27 2011 -0700 @@ -6,7 +6,7 @@ {% endblock head %} {% block page %} -<form id='vcweb-form' action='.' method='post'> +<form id='vcweb-form' action='' method='post'> {% csrf_token %} {% block form %} --- a/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Wed Jun 22 00:48:27 2011 -0700 @@ -158,8 +158,13 @@ <li><a class='confirm-experiment-action' name='end_round' href='end-round' title='Stops the current round.'><span class='vcweb-icon icon-left ui-icon' style='background: url({{STATIC_URL}}images/famfamfam/control_stop_blue.png);' ></span>end round</a></li> {% else %} + {% comment %} + FIXME: disabling for the time being, I have a suspicion Veronika + might be clicking it accidentally and redoing rounds (thus + applying group harvest + regrowth twice) <li><a class='confirm-experiment-action' name='start_round' href='start-round' title='Starts the round.'><span class='ui-icon vcweb-icon icon-left' style='background:url({{STATIC_URL}}images/famfamfam/control_play_blue.png);' ></span>start round</a></li> + {% endcomment %} {% endif %} <li><a class='confirm-experiment-action' name='advance_to_next_round' href='advance-to-next-round' title='Stops the current round if necessary and advances to the next round.'><span class='vcweb-icon ui-icon icon-left' style='background:url({{STATIC_URL}}images/famfamfam/control_fastforward_blue.png);' ></span>advance to next round</a></li> @@ -252,11 +257,6 @@ <a class='dark-yellow-highlight' name='{{chat_message.pk}}' title='{{chat_message.date_created}} {{chat_message.participant}}'> {{chat_message.date_created|date:"G:i:s"}}</a> | {{chat_message}} </div> - {% empty %} - <div class='ui-state-highlight' style='line-height: 1.5em;'> - <span class='ui-icon ui-icon-info vcweb-icon'></span> - <span class='dark-yellow-highlight'>{% now "G:i:s" %}</span> | No chat messages have been sent yet. - </div> {% endfor %} </div></div> --- a/vcweb/forestry/models.py Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/forestry/models.py Wed Jun 22 00:48:27 2011 -0700 @@ -99,8 +99,8 @@ group.set_data_value(parameter=get_resource_level_parameter(), value=value) def round_setup(experiment, **kwargs): - logger.debug(experiment) round_configuration = experiment.current_round + logger.debug("current round %s", round_configuration) if round_configuration.is_playable_round: # participant parameter harvest_decision_parameter = get_harvest_decision_parameter() @@ -133,8 +133,8 @@ 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. ''' - logger.debug(experiment) current_round_configuration = experiment.current_round + logger.debug("current round: %s", current_round_configuration) max_resource_level = 100 for group in experiment.groups.all(): # FIXME: simplify logic @@ -174,7 +174,6 @@ forestry_sender = 1 @receiver(signals.round_started, sender=forestry_sender) def round_started_handler(sender, experiment=None, **kwargs): - logger.debug("forestry handling round started signal") round_setup(experiment, **kwargs) @receiver(signals.round_ended, sender=forestry_sender) --- a/vcweb/forestry/templates/forestry/participate.html Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Wed Jun 22 00:48:27 2011 -0700 @@ -37,31 +37,26 @@ var harvestDecisionValue = $('#harvest-decision-id').val(); // FIXME: this isn't getting sent through properly before the submit occurs. // Should work out communicating directly from Django to the tornadio process. + $('#submit').attr('disabled', true); s.send(createSubmitEvent(harvestDecisionValue)); - return true; + $('#forestry-form').delay(500).submit(); + return false; }); */ - $('#submit').click(function() { + $('#submit-harvest').click(function() { var harvestDecisionValue = $('#harvest-decision-id').val(); + $('#submit-harvest').attr('disabled', true); s.send(createSubmitEvent(harvestDecisionValue)); - $('#submit').disable(); - $('#submitDialog').dialog({modal: true}); - $(this).fastConfirm({ - questionText: "Submit your harvest decision of " + harvestDecisionValue + "?", - onProceed: function(trigger) { - $('#forestry-form').submit(); - $(trigger).fastConfirm('close'); - }, - onCancel: function(trigger) { - $(trigger).fastConfirm('close'); - } - }); + setTimeout(function() { + $('#forestry-form').submit(); + }, 300); + return true; }); }); </script> {% endblock %} {% block title %} -Participating in {{experiment.status_line}} +{{ experiment.status_line }} {% endblock %} {% block content %} <h3>Forestry round number {{ experiment.current_round.round_number }}</h3> @@ -101,7 +96,7 @@ <input id='harvest-decision-id' style='width: 2em; text-align: right;' type="text" name='harvest_decision' class='required digits' maxlength='1'/></div><div> - <button id='submit' type='submit'>Harvest</button> + <button id='submit-harvest' type='submit'>Harvest</button></div></form> {% else %} --- a/vcweb/forestry/views.py Tue Jun 21 23:55:22 2011 -0700 +++ b/vcweb/forestry/views.py Wed Jun 22 00:48:27 2011 -0700 @@ -72,6 +72,7 @@ for round_data in experiment.playable_round_data: logger.debug("generating participant history for %s", round_data) data = ParticipantRoundData() +# FIXME: this kind of binding is a bit laborious and error-prone, refactor if possible data.round_configuration = round_data.round_configuration data.individual_harvest = get_harvest_decision(participant_group_relationship, round_data=round_data) data.group_harvest = get_group_harvest(group, round_data=round_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: Bitbucket <com...@bi...> - 2011-06-22 06:55:32
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/80c91614de72/ changeset: 80c91614de72 user: alllee date: 2011-06-22 08:55:22 summary: fixing round in progress logic, most recent round is now always the first element in the list since we're using deque.appendleft affected #: 1 file (20 bytes) --- a/vcweb/forestry/views.py Tue Jun 21 23:49:54 2011 -0700 +++ b/vcweb/forestry/views.py Tue Jun 21 23:55:22 2011 -0700 @@ -89,9 +89,8 @@ data.final_number_of_trees = resource_level.value participant_history.appendleft(data) if experiment.is_round_in_progress: - last_round_data = participant_history[-1] - if experiment.current_round == last_round_data.round_configuration: - last_round_data.round_in_progress = True + last_round_data = participant_history[0] + last_round_data.round_in_progress = (experiment.current_round == last_round_data.round_configuration) return participant_history @participant_required 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: Bitbucket <com...@bi...> - 2011-06-22 06:50:01
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/d1eb9508411f/ changeset: d1eb9508411f user: alllee date: 2011-06-22 08:49:54 summary: reversing the order of the participant history wait page and experimenter monitor page so most current is on top affected #: 3 files (297 bytes) --- a/vcweb/core/ajax.py Tue Jun 21 23:23:35 2011 -0700 +++ b/vcweb/core/ajax.py Tue Jun 21 23:49:54 2011 -0700 @@ -111,7 +111,7 @@ 'transition_url': transition_url, 'status': status_block, 'experimentData': data_block, - 'active_round_number': experiment.current_round.sequence_number, + 'active_round_number': experiment.current_round_sequence_number, 'round_data_count': experiment.round_data.count(), 'error_message': error_message, }) --- a/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 23:23:35 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 23:49:54 2011 -0700 @@ -48,9 +48,9 @@ $('#statusDiv [title]').qtip(qtipOptions); $('#statusDiv').show('fast'); $('#experimentData').show('fast'); + console.log("active round number: " + active_round_number); if (round_data_count > 0) { $('#experimentData').accordion({ - active: active_round_number - 1, autoHeight: false }); } @@ -191,7 +191,7 @@ </div><div id='experimentData'> {% block data %} - {% for round_data in experiment.round_data.all %} + {% for round_data in experiment.round_data.all reversed %} <h3><a href='#'>{{ round_data }}</a></h3><div id='round_data_{{forloop.counter}}'> {% if round_data.group_data_values.count > 0 %} @@ -245,7 +245,6 @@ <div class='info ui-corner-all'><span class='ui-icon ui-icon-info icon-left'></span>No participant data for this round.</div> {% endif %} {% comment %} participant chats {% endcomment %} - {% if round_data.chat_messages.count > 0 %} <h4 class='collapsible'>Chat data</h4><div id='chat-div-{{round_data.pk}}' class='chat notice ui-corner-all'> {% for chat_message in round_data.chat_messages.all %} @@ -253,9 +252,13 @@ <a class='dark-yellow-highlight' name='{{chat_message.pk}}' title='{{chat_message.date_created}} {{chat_message.participant}}'> {{chat_message.date_created|date:"G:i:s"}}</a> | {{chat_message}} </div> + {% empty %} + <div class='ui-state-highlight' style='line-height: 1.5em;'> + <span class='ui-icon ui-icon-info vcweb-icon'></span> + <span class='dark-yellow-highlight'>{% now "G:i:s" %}</span> | No chat messages have been sent yet. + </div> {% endfor %} </div> - {% endif %} </div> {% empty %} <div class='alert ui-state-highlight ui-corner-all'><span class='ui-icon ui-icon-alert icon-left'></span>Round data is not yet available.</div> --- a/vcweb/forestry/views.py Tue Jun 21 23:23:35 2011 -0700 +++ b/vcweb/forestry/views.py Tue Jun 21 23:49:54 2011 -0700 @@ -13,6 +13,7 @@ from vcweb.forestry.models import get_resource_level, get_max_harvest_decision, get_forestry_experiment_metadata, set_harvest_decision, get_harvest_decision, get_group_harvest, get_regrowth from vcweb.forestry.forms import HarvestDecisionForm import logging +from collections import deque logger = logging.getLogger(__name__) @@ -67,7 +68,7 @@ def generate_participant_history(participant_group_relationship): group = participant_group_relationship.group experiment = group.experiment - participant_history = [] + participant_history = deque() for round_data in experiment.playable_round_data: logger.debug("generating participant history for %s", round_data) data = ParticipantRoundData() @@ -86,7 +87,7 @@ logger.error("Caught attribute error while trying to calculate original number of trees %s", e) pass data.final_number_of_trees = resource_level.value - participant_history.append(data) + participant_history.appendleft(data) if experiment.is_round_in_progress: last_round_data = participant_history[-1] if experiment.current_round == last_round_data.round_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: Bitbucket <com...@bi...> - 2011-06-22 06:23:43
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/00f94177a24d/ changeset: 00f94177a24d user: alllee date: 2011-06-22 08:23:35 summary: more cleanup affected #: 6 files (652 bytes) --- a/vcweb/core/ajax.py Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/core/ajax.py Tue Jun 21 23:23:35 2011 -0700 @@ -87,35 +87,31 @@ @experimenter_required @dajaxice_register def experiment_controller(request, pk, action=''): - try: - experiment = _get_experiment(request, pk) - error_message = None + experiment = _get_experiment(request, pk) + error_message = None # FIXME: this is convenient but dangerous. # dashes become underscores for function invocation. - try: - experiment_func = getattr(experiment, action.replace('-', '_')) - experiment_func() - except TypeError as e: - logger.warning("action %s wasn't callable on experiment %s (%s)", action, experiment.status_line, e) - error_message = "Couldn't call %s" % action - except AttributeError as e: - logger.warning("no attribute %s on experiment %s (%s)", action, experiment.status_line, e) - error_message = "No such attribute %s on Experiment" % action - status_block = _render_experiment_monitor_block('status', experiment, request) - data_block = _render_experiment_monitor_block('data', experiment, request) - transition_url = None - should_transition = action in ('start_round', 'end_round') - if should_transition: - transition_url = 'wait' if action == 'end_round' else 'participate' - - return simplejson.dumps({ - 'should_transition': should_transition, - 'transition_url': transition_url, - 'status': status_block, - 'experimentData': data_block, - 'active_round_number': experiment.current_round.sequence_number, - 'round_data_count': experiment.round_data.count(), - 'error_message': error_message, - }) - except Experiment.DoesNotExist, error: - return HttpResponse(error) + try: + experiment_func = getattr(experiment, action.replace('-', '_')) + experiment_func() + except TypeError as e: + logger.warning("action %s wasn't callable on experiment %s (%s)", action, experiment.status_line, e) + error_message = "Couldn't call %s" % action + except AttributeError as e: + logger.warning("no attribute %s on experiment %s (%s)", action, experiment.status_line, e) + error_message = "No such attribute %s on Experiment" % action + status_block = _render_experiment_monitor_block('status', experiment, request) + data_block = _render_experiment_monitor_block('data', experiment, request) + transition_url = None + should_transition = action in ('start_round', 'end_round', 'advance_to_next_round') + if should_transition: + transition_url = 'wait' if action == 'end_round' else 'participate' + return simplejson.dumps({ + 'should_transition': should_transition, + 'transition_url': transition_url, + 'status': status_block, + 'experimentData': data_block, + 'active_round_number': experiment.current_round.sequence_number, + 'round_data_count': experiment.round_data.count(), + 'error_message': error_message, + }) --- a/vcweb/core/models.py Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/core/models.py Tue Jun 21 23:23:35 2011 -0700 @@ -139,6 +139,7 @@ @property def final_sequence_number(self): + # FIXME: or max round_configurations.sequence_number (degenerate data) return self.round_configurations.count() @property @@ -255,17 +256,16 @@ return "%s.%s" % (self.namespace, self.pk) @property - def round_status_display(self): - return u"Round %s of %s, %s" % (self.current_round.sequence_number, self.experiment_configuration.final_sequence_number, self.get_status_display()) + def sequence_label(self): + return u"Round %s of %s" % (self.current_round_sequence_number, self.experiment_configuration.final_sequence_number) @property def status_line(self): - return u"%s #%s (%s), %s %s" % ( + return u"%s #%s (%s), %s" % ( self.experiment_metadata.title, self.pk, self.experiment_configuration.name, - self.get_status_display(), - self.current_round.sequence_label) + self.sequence_label) @property def participant_group_relationships(self): @@ -502,7 +502,7 @@ if self.has_next_round: self.current_round_elapsed_time = 0 self.current_round_sequence_number += 1 - self.save() + self.start_round() else: logger.warning("trying to advance past the last round - no-op") @@ -702,7 +702,7 @@ @property def sequence_label(self): - return u"(%d of %d)" % (self.sequence_number, self.experiment_configuration.final_sequence_number) + return u"%d of %d" % (self.sequence_number, self.experiment_configuration.final_sequence_number) class Meta: ordering = [ 'experiment_configuration', 'sequence_number', 'date_created' ] @@ -976,7 +976,7 @@ def get_scalar_data_value(self, parameter=None, parameter_name=None): return self.get_data_value(parameter=parameter, parameter_name=parameter_name).value - def get_data_value(self, parameter=None, parameter_name=None, round_data=None): + def get_data_value(self, parameter=None, parameter_name=None, round_data=None, default=None): if round_data is None: round_data = self.current_round_data criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) @@ -984,7 +984,10 @@ return self.data_values.get(**criteria) except GroupRoundDataValue.DoesNotExist as e: logger.warning("No data value found for criteria %s", criteria) - raise e + if default is None: + raise e + else: + return default def set_data_value(self, parameter_name=None, parameter=None, value=None): ''' @@ -1215,7 +1218,8 @@ def set_data_value(self, parameter=None, value=None): current_round_data = self.current_round_data if parameter is not None and value is not None: - # FIXME: shift to ParticipantGroupRelationship as data arbiter? + # FIXME: make sure this is concurrent-safe or better yet that all participant data values are created at the + # start of a round. participant_data_value, created = current_round_data.participant_data_values.get_or_create(parameter=parameter, participant_group_relationship=self) participant_data_value.value = value # FIXME: parameterize / make explicit? --- a/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 23:23:35 2011 -0700 @@ -57,6 +57,7 @@ } function addExperimentMessage(message) { if (message) { + console.debug("adding message " + message + " to experiment messages div"); $("#experiment-messages").append($("<div style='font-size: 0.8em;line-height:0.9em;padding:3px;' class='ui-state-highlight' />").append(message)); } } @@ -99,7 +100,7 @@ </script> {% endblock %} {% block title %} -{{ experiment.pk }} +#{{ experiment.pk }}: {{ experiment.experiment_metadata }} {% endblock %} {% block page %} {{block.super}} @@ -176,11 +177,11 @@ </ul><br/><ul class='messages'> - <li title='{{experiment.actions_help_text}}'>{{ experiment.round_status_display }}</li> + <li title='{{experiment.actions_help_text}}'>{{ experiment.sequence_label }}, {{experiment.get_status_display}}</li><li>Type: {{ experiment.current_round.get_round_type_display }}</li><li>Round started on {{ experiment.current_round_start_time }}</li><li>Time remaining: {{ experiment.time_remaining }}</li> - <li>Registered participants: <b>{{participant_count}}</b> + <li title='{{experiment.participants.all|join:" "}}'>Registered participants: <b>{{participant_count}}</b></li></ul></fieldset> --- a/vcweb/forestry/models.py Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/forestry/models.py Tue Jun 21 23:23:35 2011 -0700 @@ -13,14 +13,14 @@ def get_resource_level(group, round_data=None): ''' returns the group resource level data parameter ''' - return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) + return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data, default=0) def get_group_harvest(group, round_data=None): ''' returns the collective group harvest data parameter ''' - return group.get_data_value(parameter=get_group_harvest_parameter(), round_data=round_data) + return group.get_data_value(parameter=get_group_harvest_parameter(), round_data=round_data, default=0) def get_regrowth(group, round_data=None): - return group.get_data_value(parameter=get_regrowth_parameter(), round_data=round_data) + return group.get_data_value(parameter=get_regrowth_parameter(), round_data=round_data, default=0) def has_resource_level(group=None): return group.has_data_parameter(parameter=get_resource_level_parameter()) --- a/vcweb/forestry/templates/forestry/wait.html Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/forestry/templates/forestry/wait.html Tue Jun 21 23:23:35 2011 -0700 @@ -33,13 +33,12 @@ {% block page %} <h3>Waiting for the next round to start</h3><p> -Please wait, {{participant_experiment_relationship.participant}}. The facilitator will start the next round when every participant is ready. - +Please wait, the facilitator will start the next round when every participant is ready. +</p><div class='info infoIcon'> - You are <span style='padding: 3px;' class='ui-state-highlight'>Participant #{{participant_group_relationship.participant_number}} ({{participant_experiment_relationship.sequential_participant_identifier}})</span> in + You are <span style='padding: 3px;' class='ui-state-highlight'>Participant #{{participant_group_relationship.participant_number}} ({{participant_experiment_relationship.pk}}, {{participant_experiment_relationship.sequential_participant_identifier}})</span> in <span style='padding: 3px;' class='ui-state-highlight'>{{participant_group_relationship.group}}</span></div> -</p><div class='ui-state-highlight ui-corner-all'><small><span class='float-left vcweb-ui-icon ui-icon ui-icon-circle-arrow-w'></span><a href='participate'>return to participation page</a></small></div> --- a/vcweb/forestry/views.py Tue Jun 21 20:26:04 2011 -0700 +++ b/vcweb/forestry/views.py Tue Jun 21 23:23:35 2011 -0700 @@ -69,6 +69,7 @@ experiment = group.experiment participant_history = [] for round_data in experiment.playable_round_data: + logger.debug("generating participant history for %s", round_data) data = ParticipantRoundData() data.round_configuration = round_data.round_configuration data.individual_harvest = get_harvest_decision(participant_group_relationship, round_data=round_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: Bitbucket <com...@bi...> - 2011-06-22 03:26:12
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/04f1771eeebd/ changeset: 04f1771eeebd user: alllee date: 2011-06-22 05:26:04 summary: working on ajax experiment action invocation and adding error messages if any to the json response affected #: 3 files (684 bytes) --- a/vcweb/core/ajax.py Tue Jun 21 19:05:50 2011 -0700 +++ b/vcweb/core/ajax.py Tue Jun 21 20:26:04 2011 -0700 @@ -89,11 +89,18 @@ def experiment_controller(request, pk, action=''): try: experiment = _get_experiment(request, pk) - experiment_func = getattr(experiment, action.replace('-', '_'), None) - if experiment_func: + error_message = None +# FIXME: this is convenient but dangerous. +# dashes become underscores for function invocation. + try: + experiment_func = getattr(experiment, action.replace('-', '_')) experiment_func() - else: - logger.warning("tried to invoke nonexistent action %s on experiment %s", action, experiment.status_line) + except TypeError as e: + logger.warning("action %s wasn't callable on experiment %s (%s)", action, experiment.status_line, e) + error_message = "Couldn't call %s" % action + except AttributeError as e: + logger.warning("no attribute %s on experiment %s (%s)", action, experiment.status_line, e) + error_message = "No such attribute %s on Experiment" % action status_block = _render_experiment_monitor_block('status', experiment, request) data_block = _render_experiment_monitor_block('data', experiment, request) transition_url = None @@ -102,12 +109,13 @@ transition_url = 'wait' if action == 'end_round' else 'participate' return simplejson.dumps({ - 'should_transition': action in ('start_round', 'end_round'), + 'should_transition': should_transition, 'transition_url': transition_url, 'status': status_block, 'experimentData': data_block, 'active_round_number': experiment.current_round.sequence_number, 'round_data_count': experiment.round_data.count(), + 'error_message': error_message, }) except Experiment.DoesNotExist, error: return HttpResponse(error) --- a/vcweb/core/templates/experimenter/dashboard.html Tue Jun 21 19:05:50 2011 -0700 +++ b/vcweb/core/templates/experimenter/dashboard.html Tue Jun 21 20:26:04 2011 -0700 @@ -47,14 +47,22 @@ {{e.status_line}} <ul class='horizontal experiment-menu'><li><span style='padding: 3px;' class='ui-state-highlight'><b>{{e.participants.count}} registered participants</b></span></li> - <li><a title='Monitor and control this experiment' href='{{e.monitor_url}}'><img src='{{STATIC_URL}}images/famfamfam/zoom.png' alt='General experiment monitoring interface'/> monitor</a></li> - <li><a class='confirm-experiment-action' title='Creates a new copy of this experiment with the exact same configuration but no registered participants.' href='{{e.clone_url}}'><img src='{{STATIC_URL}}images/famfamfam/page_copy.png' alt='Clone experiment'/> clone</a></li> + <li><a title='Monitor and control this experiment' + href='{{e.monitor_url}}'> + <img src='{{STATIC_URL}}images/famfamfam/zoom.png' alt='General experiment monitoring interface'/> monitor</a></li> + <li> + <a class='confirm-experiment-action' title='Creates a new copy of this experiment with the exact same configuration but no registered participants.' + href='{{e.clone_url}}'><img src='{{STATIC_URL}}images/famfamfam/page_copy.png' alt='Clone experiment'/> clone</a></li> {% if e.participants.count == 0 %} + {% comment %} <li><a title='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 by email</a></li> + {% endcomment %} <li> - <a title='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' href='{{e.controller_url}}/register-simple'><img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register example participants</a> + <a title='Register participants for this experiment with fake email addresses by providing an email suffix and the number of participants.' + href='{{e.controller_url}}/register-simple'> + <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> register participants</a></li> {% else %} <li><a title='clear all participants' href='{{e.controller_url}}/clear-participants' class='confirm-experiment-action'><img src='{{STATIC_URL}}images/famfamfam/group_delete.png' alt=''/> clear all participants</a></li> @@ -63,7 +71,7 @@ <li><a title='stop this experiment' href='{{e.stop_url}}' class='confirm-experiment-action'><img src='{{STATIC_URL}}images/famfamfam/stop.png' alt=''/> stop</a></li> {% else %} <li> - <a title='Configure this experiment (currently implemented for sanitation game)' href='{{e.configure_url}}'><img src='{{STATIC_URL}}images/famfamfam/wrench.png' alt='configure' /> configure</a> + <a title='Configure this experiment (sanitation)' href='{{e.configure_url}}'><img src='{{STATIC_URL}}images/famfamfam/wrench.png' alt='configure' /> configure</a></li> {% endif %} </ul> --- a/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 19:05:50 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Tue Jun 21 20:26:04 2011 -0700 @@ -16,6 +16,9 @@ $('#statusDiv').html(json.status); $('#experimentData').accordion("destroy"); $('#experimentData').html(json.experimentData); + if (json.error_message) { + addExperimentMessage(json.error_message) + } registerCallbacks(json.round_data_count, json.active_round_number); } } @@ -53,7 +56,9 @@ } } function addExperimentMessage(message) { - $("#experiment-messages").append($("<div style='font-size: 0.8em;line-height:0.9em;padding:3px;' class='ui-state-highlight' />").append(message)); + if (message) { + $("#experiment-messages").append($("<div style='font-size: 0.8em;line-height:0.9em;padding:3px;' class='ui-state-highlight' />").append(message)); + } } $(function() { var experimentMessageDiv = document.getElementById('experiment-messages'); 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: Bitbucket <com...@bi...> - 2011-06-22 02:06:00
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/c6bfd02de001/ changeset: c6bfd02de001 user: alllee date: 2011-06-22 04:05:50 summary: logic cleanup in tornado socket.io handlers, handling exceptions where appropriate instead of threading Nones back up the call chain. affected #: 4 files (889 bytes) --- a/vcweb/core/templates/experimenter/dashboard.html Mon Jun 20 22:08:47 2011 -0700 +++ b/vcweb/core/templates/experimenter/dashboard.html Tue Jun 21 19:05:50 2011 -0700 @@ -44,8 +44,9 @@ <div id="post"> {% for e in experiments %} <div class='notice ui-corner-all'> - {{e.status_line}} <span style='padding: 3px;' class='ui-state-highlight'><b>{{e.participants.count}} registered participants</b></span> + {{e.status_line}} <ul class='horizontal experiment-menu'> + <li><span style='padding: 3px;' class='ui-state-highlight'><b>{{e.participants.count}} registered participants</b></span></li><li><a title='Monitor and control this experiment' href='{{e.monitor_url}}'><img src='{{STATIC_URL}}images/famfamfam/zoom.png' alt='General experiment monitoring interface'/> monitor</a></li><li><a class='confirm-experiment-action' title='Creates a new copy of this experiment with the exact same configuration but no registered participants.' href='{{e.clone_url}}'><img src='{{STATIC_URL}}images/famfamfam/page_copy.png' alt='Clone experiment'/> clone</a></li> {% if e.participants.count == 0 %} --- a/vcweb/forestry/templates/forestry/chat.html Mon Jun 20 22:08:47 2011 -0700 +++ b/vcweb/forestry/templates/forestry/chat.html Tue Jun 21 19:05:50 2011 -0700 @@ -74,5 +74,3 @@ </div></form> {% endblock %} - - --- a/vcweb/forestry/templates/forestry/wait.html Mon Jun 20 22:08:47 2011 -0700 +++ b/vcweb/forestry/templates/forestry/wait.html Tue Jun 21 19:05:50 2011 -0700 @@ -33,7 +33,12 @@ {% block page %} <h3>Waiting for the next round to start</h3><p> -Please wait, {{request.user.participant}}. The facilitator will start the next round when every participant is ready. +Please wait, {{participant_experiment_relationship.participant}}. The facilitator will start the next round when every participant is ready. + +<div class='info infoIcon'> + You are <span style='padding: 3px;' class='ui-state-highlight'>Participant #{{participant_group_relationship.participant_number}} ({{participant_experiment_relationship.sequential_participant_identifier}})</span> in + <span style='padding: 3px;' class='ui-state-highlight'>{{participant_group_relationship.group}}</span> +</div></p><div class='ui-state-highlight ui-corner-all'><small><span class='float-left vcweb-ui-icon ui-icon ui-icon-circle-arrow-w'></span><a href='participate'>return to participation page</a></small> --- a/vcweb/vcwebio.py Mon Jun 20 22:08:47 2011 -0700 +++ b/vcweb/vcwebio.py Tue Jun 21 19:05:50 2011 -0700 @@ -47,6 +47,9 @@ ''' refresh_json = simplejson.dumps({ 'message_type': 'refresh' }) + def __str__(self): + return u"Participants: %s------Experimenters: %s" % (self.participant_to_connection, self.experimenter_to_connection) + def add_experimenter(self, connection, incoming_experimenter_pk, incoming_experiment_pk): logger.debug("experimenter_to_connection: %s", self.experimenter_to_connection) logger.debug("connection_to_experimenter: %s", self.connection_to_experimenter) @@ -69,12 +72,9 @@ del self.experimenter_to_connection[experimenter_tuple] def get_participant_group_relationship(self, connection): - if connection in self.connection_to_participant: - (participant_pk, experiment_pk) = self.connection_to_participant[connection] - logger.debug("Looking for ParticipantGroupRelationship with tuple (%s, %s)", participant_pk, experiment_pk) - return ParticipantGroupRelationship.objects.get(participant__pk=participant_pk, group__experiment__pk = experiment_pk) - logger.warning("Couldn't find a participant group relationship using connection %s in connection map %s", connection, self.connection_to_participant) - return None + (participant_pk, experiment_pk) = self.connection_to_participant[connection] + logger.debug("Looking for ParticipantGroupRelationship with tuple (%s, %s)", participant_pk, experiment_pk) + return ParticipantGroupRelationship.objects.get(participant__pk=participant_pk, group__experiment__pk = experiment_pk) def get_participant_experiment_tuple(self, connection): return self.connection_to_participant[connection] @@ -89,6 +89,7 @@ self.connection_to_participant[connection] = participant_tuple self.participant_to_connection[participant_tuple] = connection + return participant_tuple def remove_participant(self, connection): try: @@ -212,25 +213,13 @@ try: participant_experiment_relationship = ParticipantExperimentRelationship.objects.select_related(depth=1).get(pk=relationship_id) connection_manager.add_participant(extra, self, participant_experiment_relationship) - participant_group_rel = connection_manager.get_participant_group_relationship(self) - if participant_group_rel is not None: - group = participant_group_rel.group - message = "Participant %s (%s) connected." % (participant_group_rel.participant_number, group) - connection_manager.send_to_group(group, + experiment = participant_experiment_relationship.experiment + experimenter_tuple = (experiment.experimenter.pk, experiment.pk) + connection_manager.send_to_experimenter(experimenter_tuple, simplejson.dumps({ - 'message' : message, + 'message': "Participant %s connected." % participant_experiment_relationship.participant, 'message_type': 'info', })) - else: - experimenter_tuple = (participant_experiment_relationship.experiment.experimenter.pk, - participant_experiment_relationship.experiment.pk) - connection_manager.send_to_experimenter(experimenter_tuple, - simplejson.dumps({ - 'message': "Participant %s connected to experiment." % participant_experiment_relationship, - 'message_type': 'info', - })) - except KeyError as e: - logger.debug("no participant group relationship id %s", e) except ParticipantExperimentRelationship.DoesNotExist as e: logger.debug("no participant experiment relationship with id %s (%s)", relationship_id, e) @@ -238,9 +227,10 @@ logger.debug("received message %s from handler %s", message, self) event = to_event(message) # could handle connection here or in on_open, revisit - if 'connect' in event.message_type: + message_type = event.message_type + if 'connect' in message_type: return - elif event.message_type == 'submit': + elif message_type == 'submit': (participant_pk, experiment_pk) = connection_manager.get_participant_experiment_tuple(self) experiment = Experiment.objects.get(pk=experiment_pk) logger.debug("processing participant submission for participant %s and experiment %s", participant_pk, experiment) @@ -264,22 +254,26 @@ else: logger.debug("No data round in progress, received late submit event: %s", event) - elif event.message_type == 'chat': - participant_group_relationship = connection_manager.get_participant_group_relationship(self) - current_round_data = participant_group_relationship.group.experiment.current_round_data - chat_message = ChatMessage.objects.create(participant_group_relationship=participant_group_relationship, - message=event.message, - round_data=current_round_data - ) - chat_json = simplejson.dumps({ - "pk": chat_message.pk, - 'round_data_pk': current_round_data.pk, - 'participant': unicode(participant_group_relationship.participant), - "date_created": chat_message.date_created.strftime("%H:%M:%S"), - "message" : unicode(chat_message), - "message_type": 'chat', - }) - connection_manager.send_to_group(participant_group_relationship.group, chat_json) + elif message_type == 'chat': + try: + participant_group_relationship = connection_manager.get_participant_group_relationship(self) + current_round_data = participant_group_relationship.group.experiment.current_round_data + chat_message = ChatMessage.objects.create(participant_group_relationship=participant_group_relationship, + message=event.message, + round_data=current_round_data + ) + chat_json = simplejson.dumps({ + "pk": chat_message.pk, + 'round_data_pk': current_round_data.pk, + 'participant': unicode(participant_group_relationship.participant), + "date_created": chat_message.date_created.strftime("%H:%M:%S"), + "message" : unicode(chat_message), + "message_type": 'chat', + }) + connection_manager.send_to_group(participant_group_relationship.group, chat_json) + except: + logger.warning("Couldn't find a participant group relationship using connection %s with connection manager %s", self, self.connection_manager) + def on_close(self): logger.debug("removing participant connection %s", 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: Bitbucket <com...@bi...> - 2011-06-21 05:08:56
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/e7348dfa2c76/ changeset: e7348dfa2c76 user: alllee date: 2011-06-21 00:02:12 summary: updating status_line and adding jquery cookies plugin for csrf token support affected #: 6 files (7.2 KB) --- a/vcweb/core/models.py Fri Jun 17 16:50:26 2011 -0700 +++ b/vcweb/core/models.py Mon Jun 20 15:02:12 2011 -0700 @@ -256,13 +256,14 @@ @property def round_status_display(self): - return "Round %s of %s, %s" % (self.current_round.sequence_number, self.experiment_configuration.final_sequence_number, self.get_status_display()) + return u"Round %s of %s, %s" % (self.current_round.sequence_number, self.experiment_configuration.final_sequence_number, self.get_status_display()) @property def status_line(self): - return "%s #%s, %s %s" % ( + return u"%s #%s (%s), %s %s" % ( self.experiment_metadata.title, self.pk, + self.experiment_configuration.name, self.get_status_display(), self.current_round.sequence_label) --- a/vcweb/core/templates/base-vcweb-form.html Fri Jun 17 16:50:26 2011 -0700 +++ b/vcweb/core/templates/base-vcweb-form.html Mon Jun 20 15:02:12 2011 -0700 @@ -4,3 +4,12 @@ {{ block.super }} {% include "includes/jquery.forms.html" %} {% endblock head %} + +{% block page %} +<form action='.' method='post'> +{% csrf_token %} +{% block form %} + +{% endblock form %} +</form> +{% endblock page %} --- a/vcweb/fabfile.py Fri Jun 17 16:50:26 2011 -0700 +++ b/vcweb/fabfile.py Mon Jun 20 15:02:12 2011 -0700 @@ -44,7 +44,7 @@ def syncdb(**kwargs): with cd(env.project_path): - _virtualenv(run, *syncdb_commands, **kwargs) + _virtualenv(local, *syncdb_commands, **kwargs) def setup_virtualenv(): @@ -153,8 +153,6 @@ if confirm("Deploy to %(hosts)s ?" % env): with cd(env.project_path): sudo('hg pull && hg up', user=env.deploy_user, pty=True) - if confirm("syncdb?"): - syncdb() env.static_root = vcweb_settings.STATIC_ROOT _virtualenv(run,'%(python)s manage.py collectstatic' % env) sudo_chain('chmod -R ug+rw .', --- a/vcweb/forestry/management.py Fri Jun 17 16:50:26 2011 -0700 +++ b/vcweb/forestry/management.py Mon Jun 20 15:02:12 2011 -0700 @@ -25,9 +25,9 @@ def post_syncdb_handler(sender, **kwargs): forestry_dict = { "about_url": "http://commons.asu.edu", - "description": "Web-based version of the forestry field experiments.", + "description": "A web-based version of forestry field experiments (Cardenas, Janssen and Bousquet).", "namespace": "forestry", - "title": "Forestry Web Experiment", + "title": "Forestry Experiment", "date_created": "2011-01-01" } forestry_metadata, created = ExperimentMetadata.objects.get_or_create(**forestry_dict) --- a/vcweb/static/js/common.js Fri Jun 17 16:50:26 2011 -0700 +++ b/vcweb/static/js/common.js Mon Jun 20 15:02:12 2011 -0700 @@ -27,3 +27,39 @@ scrollToBottom(document.getElementById(elementId)); } +$(document).ajaxSend(function(event, xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + function sameOrigin(url) { + // url could be relative or scheme relative or absolute + var host = document.location.host; // host + port + var protocol = document.location.protocol; + var sr_origin = '//' + host; + var origin = protocol + sr_origin; + // Allow absolute or scheme relative URLs to same origin + return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || + (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || + // or any other URL that isn't scheme relative or absolute i.e relative. + !(/^(\/\/|http:|https:).*/.test(url)); + } + function safeMethod(method) { + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + } + + if (!safeMethod(settings.type) && sameOrigin(settings.url)) { + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } +}); --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vcweb/static/js/jquery.cookies.2.2.0.min.js Mon Jun 20 15:02:12 2011 -0700 @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2005 - 2010, James Auldridge + * All rights reserved. + * + * Licensed under the BSD, MIT, and GPL (your choice!) Licenses: + * http://code.google.com/p/cookies/wiki/License + * + */ +var jaaulde=window.jaaulde||{};jaaulde.utils=jaaulde.utils||{};jaaulde.utils.cookies=(function(){var resolveOptions,assembleOptionsString,parseCookies,constructor,defaultOptions={expiresAt:null,path:'/',domain:null,secure:false};resolveOptions=function(options){var returnValue,expireDate;if(typeof options!=='object'||options===null){returnValue=defaultOptions;}else +{returnValue={expiresAt:defaultOptions.expiresAt,path:defaultOptions.path,domain:defaultOptions.domain,secure:defaultOptions.secure};if(typeof options.expiresAt==='object'&&options.expiresAt instanceof Date){returnValue.expiresAt=options.expiresAt;}else if(typeof options.hoursToLive==='number'&&options.hoursToLive!==0){expireDate=new Date();expireDate.setTime(expireDate.getTime()+(options.hoursToLive*60*60*1000));returnValue.expiresAt=expireDate;}if(typeof options.path==='string'&&options.path!==''){returnValue.path=options.path;}if(typeof options.domain==='string'&&options.domain!==''){returnValue.domain=options.domain;}if(options.secure===true){returnValue.secure=options.secure;}}return returnValue;};assembleOptionsString=function(options){options=resolveOptions(options);return((typeof options.expiresAt==='object'&&options.expiresAt instanceof Date?'; expires='+options.expiresAt.toGMTString():'')+'; path='+options.path+(typeof options.domain==='string'?'; domain='+options.domain:'')+(options.secure===true?'; secure':''));};parseCookies=function(){var cookies={},i,pair,name,value,separated=document.cookie.split(';'),unparsedValue;for(i=0;i<separated.length;i=i+1){pair=separated[i].split('=');name=pair[0].replace(/^\s*/,'').replace(/\s*$/,'');try +{value=decodeURIComponent(pair[1]);}catch(e1){value=pair[1];}if(typeof JSON==='object'&&JSON!==null&&typeof JSON.parse==='function'){try +{unparsedValue=value;value=JSON.parse(value);}catch(e2){value=unparsedValue;}}cookies[name]=value;}return cookies;};constructor=function(){};constructor.prototype.get=function(cookieName){var returnValue,item,cookies=parseCookies();if(typeof cookieName==='string'){returnValue=(typeof cookies[cookieName]!=='undefined')?cookies[cookieName]:null;}else if(typeof cookieName==='object'&&cookieName!==null){returnValue={};for(item in cookieName){if(typeof cookies[cookieName[item]]!=='undefined'){returnValue[cookieName[item]]=cookies[cookieName[item]];}else +{returnValue[cookieName[item]]=null;}}}else +{returnValue=cookies;}return returnValue;};constructor.prototype.filter=function(cookieNameRegExp){var cookieName,returnValue={},cookies=parseCookies();if(typeof cookieNameRegExp==='string'){cookieNameRegExp=new RegExp(cookieNameRegExp);}for(cookieName in cookies){if(cookieName.match(cookieNameRegExp)){returnValue[cookieName]=cookies[cookieName];}}return returnValue;};constructor.prototype.set=function(cookieName,value,options){if(typeof options!=='object'||options===null){options={};}if(typeof value==='undefined'||value===null){value='';options.hoursToLive=-8760;}else if(typeof value!=='string'){if(typeof JSON==='object'&&JSON!==null&&typeof JSON.stringify==='function'){value=JSON.stringify(value);}else +{throw new Error('cookies.set() received non-string value and could not serialize.');}}var optionsString=assembleOptionsString(options);document.cookie=cookieName+'='+encodeURIComponent(value)+optionsString;};constructor.prototype.del=function(cookieName,options){var allCookies={},name;if(typeof options!=='object'||options===null){options={};}if(typeof cookieName==='boolean'&&cookieName===true){allCookies=this.get();}else if(typeof cookieName==='string'){allCookies[cookieName]=true;}for(name in allCookies){if(typeof name==='string'&&name!==''){this.set(name,null,options);}}};constructor.prototype.test=function(){var returnValue=false,testName='cT',testValue='data';this.set(testName,testValue);if(this.get(testName)===testValue){this.del(testName);returnValue=true;}return returnValue;};constructor.prototype.setOptions=function(options){if(typeof options!=='object'){options=null;}defaultOptions=resolveOptions(options);};return new constructor();})();(function(){if(window.jQuery){(function($){$.cookies=jaaulde.utils.cookies;var extensions={cookify:function(options){return this.each(function(){var i,nameAttrs=['name','id'],name,$this=$(this),value;for(i in nameAttrs){if(!isNaN(i)){name=$this.attr(nameAttrs[i]);if(typeof name==='string'&&name!==''){if($this.is(':checkbox, :radio')){if($this.attr('checked')){value=$this.val();}}else if($this.is(':input')){value=$this.val();}else +{value=$this.html();}if(typeof value!=='string'||value===''){value=null;}$.cookies.set(name,value,options);break;}}}});},cookieFill:function(){return this.each(function(){var n,getN,nameAttrs=['name','id'],name,$this=$(this),value;getN=function(){n=nameAttrs.pop();return!!n;};while(getN()){name=$this.attr(n);if(typeof name==='string'&&name!==''){value=$.cookies.get(name);if(value!==null){if($this.is(':checkbox, :radio')){if($this.val()===value){$this.attr('checked','checked');}else +{$this.removeAttr('checked');}}else if($this.is(':input')){$this.val(value);}else +{$this.html(value);}}break;}}});},cookieBind:function(options){return this.each(function(){var $this=$(this);$this.cookieFill().change(function(){$this.cookify(options);});});}};$.each(extensions,function(i){$.fn[i]=this;});})(window.jQuery);}})(); \ No newline at end of file http://bitbucket.org/virtualcommons/vcweb/changeset/39d90e3ffdf9/ changeset: 39d90e3ffdf9 user: alllee date: 2011-06-21 07:08:47 summary: converting other forms to include csrf token affected #: 5 files (416 bytes) --- a/vcweb/core/templates/base-vcweb-form.html Mon Jun 20 15:02:12 2011 -0700 +++ b/vcweb/core/templates/base-vcweb-form.html Mon Jun 20 22:08:47 2011 -0700 @@ -6,7 +6,7 @@ {% endblock head %} {% block page %} -<form action='.' method='post'> +<form id='vcweb-form' action='.' method='post'> {% csrf_token %} {% block form %} --- a/vcweb/core/templates/experimenter/monitor.html Mon Jun 20 15:02:12 2011 -0700 +++ b/vcweb/core/templates/experimenter/monitor.html Mon Jun 20 22:08:47 2011 -0700 @@ -94,11 +94,11 @@ </script> {% endblock %} {% block title %} -{{ experiment.status_line }} +{{ experiment.pk }} {% endblock %} {% block page %} {{block.super}} -<h3>{{experiment}}</h3> +<h3>{{experiment.status_line}}</h3><span>You are logged in as {{request.user.experimenter}}.</span><hr/><div id='experimenter-sidebar'> --- a/vcweb/core/templates/registration/login.html Mon Jun 20 15:02:12 2011 -0700 +++ b/vcweb/core/templates/registration/login.html Mon Jun 20 22:08:47 2011 -0700 @@ -8,23 +8,18 @@ <script type='text/javascript'> $(function() { $(':input').addClass('required'); - $('#loginForm').validate(); + $('#vcweb-form').validate(); $('#id_email').focus(); }); </script> {% endblock head %} -{% block page %} - -<form id='loginForm' action='' method='post'> +{% block form %} <h3>Returning User</h3><p> Don't have an account or a first-time user? Please <a href='{% url core:register %}'>register first</a>. </p> - {% include "includes/form-as-div.html" %} <div><button type='submit' class='submit'>Login</button></div> -</form> +{% endblock form %} -{% endblock page %} - --- a/vcweb/core/templates/registration/register.html Mon Jun 20 15:02:12 2011 -0700 +++ b/vcweb/core/templates/registration/register.html Mon Jun 20 22:08:47 2011 -0700 @@ -4,8 +4,8 @@ <script type='text/javascript'> $(document).ready(function() { - $("#registrationForm :input:visible:enabled:first").focus(); - $('#registrationForm').validate(); + $("#vcweb-form :input:visible:enabled:first").focus(); + $('#vcweb-form').validate(); $('#id_password').rules("add", { minlength: 3 }); @@ -16,7 +16,7 @@ equalTo: "Please make sure your passwords match." } }); - $('input:visible, textarea:visible').each(function(i, val) { + $('#vcweb-form').find('input:visible, textarea:visible').each(function(i, val) { var labelTitle = $(val).prev().attr("title"); if (! labelTitle) { return; @@ -33,23 +33,14 @@ {% endblock head %} -{% block title %}Virtual Commons Web Experiment Registration{% endblock %} - -{% block page %} - -<form id='registrationForm' action='' method='post'> - <h3>New User</h3> +{% block title %}Register with vcweb{% endblock %} +{% block form %} +<h3>New User</h3><p> Already have an account? You can <a href='{% url core:login %}'>login here</a> or <a href='{% url password-reset %}'>retrieve your password</a>. </p> - {% include "includes/form-as-div.html" %} - <div><button class='submit'>Register</button></div> - -</form> - -{% endblock %} - +{% endblock form %} --- a/vcweb/forestry/templates/forestry/participate.html Mon Jun 20 15:02:12 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Mon Jun 20 22:08:47 2011 -0700 @@ -32,6 +32,7 @@ break; } }); + /* $('#forestry-form').submit(function() { var harvestDecisionValue = $('#harvest-decision-id').val(); // FIXME: this isn't getting sent through properly before the submit occurs. @@ -39,8 +40,12 @@ s.send(createSubmitEvent(harvestDecisionValue)); return true; }); - /* + */ $('#submit').click(function() { + var harvestDecisionValue = $('#harvest-decision-id').val(); + s.send(createSubmitEvent(harvestDecisionValue)); + $('#submit').disable(); + $('#submitDialog').dialog({modal: true}); $(this).fastConfirm({ questionText: "Submit your harvest decision of " + harvestDecisionValue + "?", onProceed: function(trigger) { @@ -52,7 +57,6 @@ } }); }); - */ }); </script> {% endblock %} @@ -89,6 +93,7 @@ {% if max_harvest_decision > 0 %} <form id='forestry-form' action='' method='post'> + {% csrf_token %} <div class='field'><label for='harvest-decision-id'>How many trees will you harvest this round? <br/><span>Enter a number between 0 and {{ max_harvest_decision }}</span>: 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: Bitbucket <com...@bi...> - 2011-06-17 23:50:36
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/251ebd148bf8/ changeset: 251ebd148bf8 user: alllee date: 2011-06-18 01:18:52 summary: moving json encoding to core views affected #: 3 files (2.8 KB) --- a/vcweb/core/views.py Fri Jun 17 16:09:13 2011 -0700 +++ b/vcweb/core/views.py Fri Jun 17 16:18:52 2011 -0700 @@ -3,10 +3,15 @@ from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied +from django.db.models import Model +from django.db.models.query import QuerySet +from django.core.serializers import serialize from django.http import HttpResponse from django.shortcuts import render_to_response, redirect from django.template.context import RequestContext from django.utils.decorators import method_decorator +from django.utils.functional import curry +from django.utils.simplejson import dumps, loads, JSONEncoder from django.views.generic import ListView, FormView, TemplateView from django.views.generic.base import TemplateResponseMixin from django.views.generic.detail import SingleObjectMixin, DetailView @@ -21,6 +26,33 @@ logger = logging.getLogger(__name__) """ account registration / login / logout / profile views """ +class DjangoJSONEncoder(JSONEncoder): + def default(self, obj): + if isinstance(obj, QuerySet): + return loads(serialize('json', obj)) + elif isinstance(obj, Model): + return loads(serialize('json', [obj]))[0] + else: + return JSONEncoder.default(self, obj) + +dumps = curry(dumps, cls=DjangoJSONEncoder) + +class JSONResponseMixin(object): + def render_to_response(self, context, **kwargs): + "Returns a JSON response containing 'context' as payload" + return self.get_json_response(self.convert_context_to_json(context, **kwargs)) + + def get_json_response(self, content, **httpresponse_kwargs): + "Construct an `HttpResponse` object." + logger.debug("return json response %s", content) + return HttpResponse(content, + content_type='application/json', + **httpresponse_kwargs) + + def convert_context_to_json(self, context, context_key='object_list', **kwargs): + "Convert the context dictionary into a JSON object" + logger.debug("serializing context %s with context_key %s", context, context_key) + return dumps(context[context_key]) class AnonymousMixin(object): @method_decorator(anonymous_required) @@ -345,3 +377,4 @@ logger.warning(error_message) messages.warning(request, error_message) return redirect('core:dashboard') + --- a/vcweb/lighterprints/urls.py Fri Jun 17 16:09:13 2011 -0700 +++ b/vcweb/lighterprints/urls.py Fri Jun 17 16:18:52 2011 -0700 @@ -1,10 +1,11 @@ from django.conf.urls.defaults import url, patterns -from vcweb.lighterprints.views import ActivityDetailView, ActivityListView +from vcweb.lighterprints.views import ActivityDetailView, ActivityListView, DoActivityView urlpatterns = patterns('vcweb.lighterprints.views', url(r'^$', 'index', name='index'), url(r'^(?P<experiment_id>\d+)/configure$', 'configure', name='configure'), - url(r'^activity/list$', ActivityListView.as_view()), - url(r'^activity/(?P<activity_name>\w+)$', ActivityDetailView.as_view()), + url(r'^activity/list/?$', ActivityListView.as_view()), + url(r'^activity/(?P<activity_id>\d+)$', ActivityDetailView.as_view()), + url(r'^activity/(?P<activity_id>\d+)/do$', DoActivityView.as_view()), ) --- a/vcweb/lighterprints/views.py Fri Jun 17 16:09:13 2011 -0700 +++ b/vcweb/lighterprints/views.py Fri Jun 17 16:18:52 2011 -0700 @@ -1,46 +1,14 @@ -from django import http -from django.db.models import Model -from django.db.models.query import QuerySet -from django.core.serializers import serialize +from django.views.generic.detail import BaseDetailView +from django.views.generic.edit import FormView from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin -from django.views.generic.detail import BaseDetailView -from django.utils.functional import curry -from django.utils.simplejson import dumps, loads, JSONEncoder +from vcweb.core.views import JSONResponseMixin from vcweb.lighterprints.models import Activity import collections import logging logger = logging.getLogger(__name__) -class DjangoJSONEncoder(JSONEncoder): - def default(self, obj): - if isinstance(obj, QuerySet): - return loads(serialize('json', obj)) - elif isinstance(obj, Model): - return loads(serialize('json', [obj]))[0] - else: - return JSONEncoder.default(self, obj) - -dumps = curry(dumps, cls=DjangoJSONEncoder) - -class JSONResponseMixin(object): - def render_to_response(self, context, **kwargs): - "Returns a JSON response containing 'context' as payload" - return self.get_json_response(self.convert_context_to_json(context, **kwargs)) - - def get_json_response(self, content, **httpresponse_kwargs): - "Construct an `HttpResponse` object." - logger.debug("return json response %s", content) - return http.HttpResponse(content, - content_type='application/json', - **httpresponse_kwargs) - - def convert_context_to_json(self, context, context_key='object_list', **kwargs): - "Convert the context dictionary into a JSON object" - logger.debug("serializing context %s with context_key %s", context, context_key) - return dumps(context[context_key]) -# return serializers.serialize('json', context[context_key]) class ActivityListView(JSONResponseMixin, BaseListView, MultipleObjectTemplateResponseMixin): # FIXME: replace with dynamic set @@ -67,3 +35,6 @@ class ActivityDetailView(JSONResponseMixin, BaseDetailView): template_name = 'lighterprints/activity_detail.html' + +class DoActivityView(FormView): + pass http://bitbucket.org/virtualcommons/vcweb/changeset/af7697802e3d/ changeset: af7697802e3d user: alllee date: 2011-06-18 01:50:26 summary: more cleanup, using DjangoJSONEncoder instead of JSONEncoder affected #: 3 files (696 bytes) --- a/vcweb/core/templates/includes/nav-menu.html Fri Jun 17 16:18:52 2011 -0700 +++ b/vcweb/core/templates/includes/nav-menu.html Fri Jun 17 16:50:26 2011 -0700 @@ -11,13 +11,12 @@ <div id='menu'><ul> + <li class='{% active request home %}' ><a href='{{ home }}' class='first'>Home</a></li> {% if request.user.is_authenticated %} - <li class='{% active request home %}' ><a href='{{ home }}' class='first'>Home</a></li><li class='{% active_re request dashboard %}'><a href='{{ dashboard }}'>Dashboard</a></li><li class='{% active request profile %}'><a href='{{ profile }}'>Profile</a></li><li class='{% active request logout %}'><a href='{{ logout }}'>Logout</a></li> {% else %} - <li class='{% active request home %}' ><a href='{{ home }}' class='first'>Home</a></li><li class='{% active request about %}'><a href='{{ about }}'>About</a></li><li class='{% active request contact %}'><a href='{{ contact }}'>Contact us</a></li><li class='{% active request login %} {% active request register %}'><a href='{{ login }}'>Login/Register</a></li> --- a/vcweb/core/views.py Fri Jun 17 16:18:52 2011 -0700 +++ b/vcweb/core/views.py Fri Jun 17 16:50:26 2011 -0700 @@ -6,12 +6,13 @@ from django.db.models import Model from django.db.models.query import QuerySet from django.core.serializers import serialize +from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse from django.shortcuts import render_to_response, redirect from django.template.context import RequestContext from django.utils.decorators import method_decorator from django.utils.functional import curry -from django.utils.simplejson import dumps, loads, JSONEncoder +from django.utils.simplejson import loads, dumps from django.views.generic import ListView, FormView, TemplateView from django.views.generic.base import TemplateResponseMixin from django.views.generic.detail import SingleObjectMixin, DetailView @@ -25,17 +26,16 @@ import logging logger = logging.getLogger(__name__) -""" account registration / login / logout / profile views """ -class DjangoJSONEncoder(JSONEncoder): +class VcwebJSONEncoder(DjangoJSONEncoder): def default(self, obj): if isinstance(obj, QuerySet): return loads(serialize('json', obj)) elif isinstance(obj, Model): return loads(serialize('json', [obj]))[0] else: - return JSONEncoder.default(self, obj) + return DjangoJSONEncoder.default(self, obj) -dumps = curry(dumps, cls=DjangoJSONEncoder) +dumps = curry(dumps, cls=VcwebJSONEncoder) class JSONResponseMixin(object): def render_to_response(self, context, **kwargs): @@ -55,11 +55,16 @@ return dumps(context[context_key]) class AnonymousMixin(object): + """ provides the anonymous_required decorator """ @method_decorator(anonymous_required) def dispatch(self, *args, **kwargs): return super(AnonymousMixin, self).dispatch(*args, **kwargs) class Dashboard(ListView, TemplateResponseMixin): + """ + general dashboard for participants or experimenters that displays a list of + experiments to either participate in or configure/manage/monitor, respectively + """ context_object_name = 'experiments' def get_template_names(self): user = self.request.user @@ -70,6 +75,8 @@ return Experiment.objects.filter(experimenter__pk=self.request.user.experimenter.pk) else: # nested dictionary, {ExperimentMetadata -> { status -> [experiments,...] }} +# FIXME: could also use collections.defaultdict or regroup template tag to +# accomplish this.. experiment_dict = {} for experiment in user.participant.experiments.exclude(status__in=(Experiment.INACTIVE, Experiment.PAUSED, Experiment.COMPLETED)): if not experiment.experiment_metadata in experiment_dict: --- a/vcweb/lighterprints/templates/lighterprints/activity_list.html Fri Jun 17 16:18:52 2011 -0700 +++ b/vcweb/lighterprints/templates/lighterprints/activity_list.html Fri Jun 17 16:50:26 2011 -0700 @@ -11,4 +11,16 @@ {% endfor %} </ul> {% endfor %} +{% comment %} +<h3>regroup method</h3> +{% regroup activity_list by level as level_list %} +{% for level in level_list %} +<h3>Level {{level.grouper}} Activities</h3> +<ul> + {% for activity in level.list %} + <li>{{activity}}</li> + {% endfor %} +</ul> +{% endfor %} +{% endcomment %} {% endblock page %} 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: Bitbucket <com...@bi...> - 2011-06-17 23:09:23
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/4cbf112c2d17/ changeset: 4cbf112c2d17 user: alllee date: 2011-06-18 01:09:13 summary: adding more fields to Activity + starting to work out relationships with ActivityAvailability. adding a basic activity list page and adjusting the returned json data to be listed by level, e.g., {1: [...], 2:[...], 3:[...], ...} affected #: 4 files (2.5 KB) 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: Bitbucket <com...@bi...> - 2011-06-16 00:01:09
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/6dd70458668d/ changeset: 6dd70458668d user: alllee date: 2011-06-16 02:00:59 summary: including all fields by default affected #: 1 file (47 bytes) --- a/vcweb/lighterprints/views.py Wed Jun 15 16:47:11 2011 -0700 +++ b/vcweb/lighterprints/views.py Wed Jun 15 17:00:59 2011 -0700 @@ -23,7 +23,7 @@ def convert_context_to_json(self, context): "Convert the context dictionary into a JSON object" logger.debug("serializing context %s", context) - return serializers.serialize('json', context['object_list'], fields=('display_name', 'description', 'url')) + return serializers.serialize('json', context['object_list']) class ActivityListView(JSONResponseMixin, BaseListView, MultipleObjectTemplateResponseMixin): # FIXME: replace with dynamic set 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: Bitbucket <com...@bi...> - 2011-06-15 23:47:24
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/0b1fd3466d15/ changeset: 0b1fd3466d15 user: alllee date: 2011-06-16 01:47:11 summary: adding first pass at lighterprints Activity modeling (not complete, still need to create relationship tables for Activity <-> Availability relationships and get rid of availability_start_time and availability_end_time. affected #: 15 files (7.7 KB) --- a/vcweb/core/models.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/core/models.py Wed Jun 15 16:47:11 2011 -0700 @@ -1,13 +1,14 @@ +from datetime import datetime from django.contrib.auth.models import User from django.core.validators import RegexValidator from django.db import models from django.db.models.aggregates import Max +from django.dispatch import receiver from django.template.defaultfilters import slugify - from string import Template from vcweb.core import signals + import base64 -from datetime import datetime import hashlib import logging import random @@ -18,22 +19,22 @@ """ Contains all data models used in the core as well as a number of helper functions. +FIXME: getting a bit monolithically unwieldy. Consider splitting into models subdirectory +""" -Is getting monolithically unwieldy. Consider splitting into models -subdirectory - -tick handlers. -handles each second tick. Might rethink this and use timed / delayed tasks in celery execute at the end of each round for -controlled experiments and for longer-scale experiments use 1 minute granularity for performance sake. -""" +@receiver(signals.second_tick, sender=None) def second_tick_handler(sender, time=None, **kwargs): + """ + tick handlers. handles each second tick. Might rethink this and use timed / + delayed tasks in celery execute at the end of each round for controlled + experiments and for longer-scale experiments use 1 minute granularity for + performance sake. + """ logger.debug("handling second tick signal at %s", time) logger.debug("kwargs: %s", kwargs) # inspect all active experiments and update their time left Experiment.objects.increment_elapsed_time(status='ROUND_IN_PROGRESS') -signals.second_tick.connect(second_tick_handler, sender=None) - class ExperimentMetadataManager(models.Manager): def get_by_natural_key(self, key): return self.get(namespace=key) @@ -275,6 +276,8 @@ def namespace(self): return self.experiment_metadata.namespace +# The following URL helper properties are generic experiment management URLs +# available to experimenters but not participants @property def management_url(self): return "/%s/experimenter" % self.get_absolute_url() @@ -282,30 +285,8 @@ @property def configure_url(self): return "/%s/configure" % self.get_absolute_url() -# sanitation urls @property - def consent_url(self): - return "/%s/consent" % self.get_absolute_url() - - @property - def survey_url(self): - return "/%s/survey" % self.get_absolute_url() - - @property - def quiz_url(self): - return "/%s/quiz" % self.get_absolute_url() - - @property - def play_url(self): - return "/%s/play" % self.get_absolute_url() - - @property - def instructions_url(self): - return "/%s/instructions" % self.get_absolute_url() - -# - @property def stop_url(self): return "%s/stop" % self.controller_url @@ -1173,6 +1154,9 @@ date_created = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey(User) last_completed_round_sequence_number = models.PositiveIntegerField(default=0) + current_location = models.CharField(max_length=64, null=True, blank=True) +# arbitrary JSON-encoded data + additional_data = models.TextField(null=True, blank=True) def __init__(self, *args, **kwargs): super(ParticipantExperimentRelationship, self).__init__(*args, **kwargs) --- a/vcweb/fabfile.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/fabfile.py Wed Jun 15 16:47:11 2011 -0700 @@ -21,7 +21,7 @@ env.hosts = ['localhost'] env.hg_url = 'https://bitbucket.org/virtualcommons/vcweb' env.apache = 'httpd' -env.applist = ['core', 'forestry'] +env.applist = ['core', 'forestry', 'sanitation', 'lighterprints'] env.apps = ' '.join(env.applist) # django integration for access to settings, etc. --- a/vcweb/forestry/management.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/forestry/management.py Wed Jun 15 16:47:11 2011 -0700 @@ -1,6 +1,5 @@ -#from django.dispatch import receiver - -from django.db.models.signals import post_syncdb +from django.db.models import signals +from django.dispatch import receiver from vcweb.core.models import ExperimentMetadata import vcweb @@ -22,6 +21,7 @@ ''' +@receiver(signals.post_syncdb, sender=vcweb.core.models, dispatch_uid='forestry_metadata_creator') def post_syncdb_handler(sender, **kwargs): forestry_dict = { "about_url": "http://commons.asu.edu", @@ -32,8 +32,3 @@ } forestry_metadata, created = ExperimentMetadata.objects.get_or_create(**forestry_dict) logger.debug("forestry: %s (%s)" % (forestry_metadata, created)) - -post_syncdb.connect(post_syncdb_handler, sender=vcweb.core.models, - dispatch_uid='forestry_metadata_creator') - - --- a/vcweb/forestry/tests.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/forestry/tests.py Wed Jun 15 16:47:11 2011 -0700 @@ -1,6 +1,5 @@ -from vcweb.core.models import (RoundConfiguration, Parameter, - ParticipantRoundDataValue, GroupRoundDataValue, - ParticipantExperimentRelationship, ParticipantGroupRelationship) +from vcweb.core.models import (RoundConfiguration, Parameter, ParticipantRoundDataValue, + GroupRoundDataValue, ParticipantExperimentRelationship, ParticipantGroupRelationship) from vcweb.core.tests import BaseVcwebTest from vcweb.forestry.models import * import logging --- a/vcweb/sanitation/models.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/sanitation/models.py Wed Jun 15 16:47:11 2011 -0700 @@ -1,23 +1,21 @@ from django.db import models -# Create your models here. +import random -# @property -def consent_url(self): - return "/%s/consent" % self.get_absolute_url() +def pollutify(resource_string, pollution_amount, pollution_symbol, group=None): + resource_index = xrange(1,(len(resource_string) + 1)) + pollution_locations = sorted(random.sample(resource_index, pollution_amount)) +# pollution_parameter = Parameter.objects.get(name='sanitation.pollution') + resource_string_list = list(resource_string) + offset = len(pollution_symbol) + for i, location in enumerate(pollution_locations): +# generate a GroupRoundDataValue for this location (this can be shortened) +# grdv = GroupRoundDataValue.objects.create(group=group, round_data=group.current_round_data, parameter=pollution_parameter, value=location) +# logger.debug("grdv is %s", grdv) +# FIXME: since we're inserting more than one character we need to be careful not to insert into a location where +# we appended text.. + resource_string_list.insert(location + (i * offset), pollution_symbol) + return ''.join(resource_string_list) - # @property -def survey_url(self): - return "/%s/survey" % self.get_absolute_url() - #@property -def quiz_url(self): - return "/%s/quiz" % self.get_absolute_url() -# @property -def play_url(self): - return "/%s/play" % self.get_absolute_url() - - # @property -def instructions_url(self): - return "/%s/instructions" % self.get_absolute_url() --- a/vcweb/settings.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/settings.py Wed Jun 15 16:47:11 2011 -0700 @@ -99,6 +99,7 @@ 'vcweb.core', 'vcweb.forestry', 'vcweb.sanitation', + 'vcweb.lighterprints', 'dajaxice', 'djcelery', 'djkombu', --- a/vcweb/urls.py Mon Jun 13 17:56:34 2011 -0700 +++ b/vcweb/urls.py Wed Jun 15 16:47:11 2011 -0700 @@ -27,6 +27,7 @@ # instances of forestry with ExperimentMetadata.namespace) url(r'^forestry/', include('vcweb.forestry.urls', namespace='forestry', app_name='forestry')), url(r'^sanitation/', include('vcweb.sanitation.urls', namespace='sanitation', app_name='sanitation')), + url(r'^lighterprints/', include('vcweb.lighterprints.urls', namespace='lighterprints', app_name='lighterprints')), url(r'^admin/', include(admin.site.urls)), # core catches everything else url(r'', include('vcweb.core.urls', namespace='core', app_name='core')), 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: Allen L. <All...@as...> - 2011-06-15 22:07:17
|
I think this should work as a more efficient version of pollutify (doesn't scan the entire length of the string and uses list insertion instead of string concatenation). I've stubbed out the group round data value and parameter stuff so it can be tested independently. def pollutify(resource_string, pollution_amount, pollution_symbol, group=None): resource_index = xrange(1,(len(resource_string) + 1)) pollution_locations = sorted(random.sample(resource_index, pollution_amount)) # pollution_parameter = Parameter.objects.get(name='sanitation.pollution') resource_string_list = list(resource_string) offset = len(pollution_symbol) for i, location in enumerate(pollution_locations): # generate a GroupRoundDataValue for this location (this can be shortened) # grdv = GroupRoundDataValue.objects.create(group=group, round_data=group.current_round_data, parameter=pollution_parameter, value=location) # logger.debug("grdv is %s", grdv) # FIXME: since we're inserting more than one character we need to be careful not to insert into a location where # we appended text.. resource_string_list.insert(location + (i * offset), pollution_symbol) return ''.join(resource_string_list) On Wed, Jun 15, 2011 at 2:50 PM, Allen Lee <All...@as...> wrote: > Oh wait, nevermind about the inserting strings into other strings. > The solution I had in mind (iterating over the locations and inserting > them into the string) has that problem, but yours doesn't. > > On Wed, Jun 15, 2011 at 2:42 PM, Allen Lee <All...@as...> wrote: >> We can discuss the custom template tag and other issues the next time >> you come in - are you available on Friday? A few comments: >> >> Is your filename really called 'initial_jason.py'? That won't work >> the way you want it to, it needs to be named 'initial_data.json'. >> You're really close with the pollution symbol parameter, let me try to >> fully explain how this should work. Since we want the pollution >> symbols to be a group parameter we need to give a scope of "group", >> not "participant". For example, in >> forestry/fixtures/initial_data.json >> >> { >> "fields": { >> "name":"resource_level", >> "experiment_metadata": 1, >> "creator": 1, >> "type": "int", >> "date_created": "2011-01-01 15:13:07", >> "scope": "group" >> }, >> "model": "core.parameter", >> "pk": 2 >> }, >> >> You'll need at least two Parameters, the first is the Group parameter >> for pollution (and I'd name it "pollution" instead of "symbol") and >> the second is a Participant parameter (i.e., "scope": "participant"). >> You use these Parameters to construct and save GroupRoundDataValues >> and ParticipantDataValues whose data values are the location of a unit >> of pollution and the id of the pollution removed by a given >> participant participant, respectively. >> >> Also, your pollutify function needs some changes but we can discuss >> that in person. One of the problems is that since you're inserting >> strings longer than 1 character ("<a href=...></a>"), you have a >> chance of inserting that very string into one of the previously >> inserted "<a href=...></a>" strings, which would be bad. So you could >> get something like "<a href='<a href=''>@</a>'>@</a>" >> >> On Wed, Jun 15, 2011 at 5:55 AM, Marcel Hurtado >> <mar...@gm...> wrote: >>> The filter worked, thank you. I am not sure what you mean by custom template >>> tag/ filter can you explain to me why you would do that instead of using a >>> method? >>> Here is what I have so far: >>> views.py: >>> from vcweb.sanitation.models import pollutify_randomly >>> ... >>> resource_string = "Sanitation is vital for ... by more than 40 percent." >>> pollution_amount = random.randint(1,200) >>> symbol_url = str("<a href=''>" + symbol + "</a>") >>> game_state = pollutify_randomly(resource_string, pollution_amount, >>> symbol_url) >>> models.py >>> >>> import random >>> ... >>> def pollutify_randomly(resource_string, pollution_amount, symbol_url): >>> resource_index = range(1,(len(resource_string) + 1)) >>> pollution_location = random.sample(resource_index, pollution_amount) >>> game_state = "" >>> for i, char in enumerate(resource_string): >>> if i in pollution_location: >>> game_state = game_state + symbol_url >>> game_state = game_state + char >>> return game_state >>> Also, I have been looking at a way to track the symbols like you suggested. >>> This is what I have so far: >>> initial_jason.py >>> { >>> "fields": { >>> "name":"symbol", >>> "experiment_metadata": 1, >>> "creator": 1, >>> "type": "int", >>> "active": True, >>> "date_created": "2011-01-01 15:13:03", >>> "date_inactive": "2011-01-01 15:13:03", >>> "scope": "participant" >>> }, >>> "model": "core.parameter", >>> "pk": 1 >>> }, >>> I'm also attempting to create the "current_location" >>> { >>> "fields": { >>> "name":"", >>> "current_location":"consent" >>> }, >>> "model": "core.participantexperimentrelationship", >>> "pk": 1 >>> }, >>> >>> >>> >>> On Tue, Jun 14, 2011 at 11:04 AM, Allen Lee <All...@as...> wrote: >>>> >>>> Ah ok. That's much more useful information. You should probably also >>>> add href='#' to your a tags; some browsers don't like it when the href >>>> is blank. >>>> >>>> Next, there's two ways to get around this - the first is a quick & >>>> easy solution and the second is probably what we would go with >>>> longer-term. >>>> >>>> 1. Turn off autoescaping via {%autoescape off %} ... {% endautoescape >>>> %} or via the safe filter, e.g., {{game_state|safe}} (Try the safe >>>> filter first, it's more concise). >>>> >>>> 2. I think we'll eventually want to create a custom template tag / >>>> filter (pollutify?) that takes two parameters - a string resource >>>> ("Sanitation is blah blah blah ...") and a list of active pollution >>>> (basically character positions) and returns a new string with hrefs >>>> inserted into the text at each position specified by the active >>>> pollution list. I can show you how to do this the next time you come >>>> in. >>>> >>>> On Tue, Jun 14, 2011 at 2:07 AM, Marcel Hurtado >>>> <mar...@gm...> wrote: >>>> > Thank you. >>>> > Here's what I meant. >>>> > The sanitation text is in views.py at the moment saved to a variable >>>> > called >>>> > game_state as a global: >>>> > game_state = "Sanitation is .... by more than 40 percent." >>>> > The symbols are inserted as urls with html tags into that text, like >>>> > this: >>>> > game_state = "Sanitation <a href=''>@</a>is .... by more than <a >>>> > href=''>@</a>40 percent." >>>> > The method for "play" looks like this, still using the globals. >>>> > def play(request, experiment): >>>> > treatment = "In-group" >>>> > logger.debug("handling play") >>>> > return render_to_response('sanitation/play.html', globals(), >>>> > context_instance=RequestContext(request)) >>>> > When play.html loads the sanitation text is shown on the page like this: >>>> > "Sanitation <a href=''>@</a>is .... by more than <a href=''>@</a>40 >>>> > percent." >>>> > but it should be like this: >>>> > "Sanitation @is .... by more than @40 percent." >>>> > >>>> > >>>> > On Mon, Jun 13, 2011 at 10:05 PM, Allen Lee <All...@as...> wrote: >>>> >> >>>> >> current_location should probably be an extra column on >>>> >> ParticipantExperimentRelationship in core, but you'd get & set it in >>>> >> your sanitation game code. I can push that out soon, but am working >>>> >> on some other bugs first. Not sure what you mean by passing strings >>>> >> from views to templates not rendering as html, can you clarify or show >>>> >> code examples that aren't working? >>>> >> >>>> >> On Mon, Jun 13, 2011 at 6:50 PM, Marcel Hurtado >>>> >> <mar...@gm...> wrote: >>>> >> > Allen, >>>> >> > I have some questions. Does the "current_location" variable get >>>> >> > initiallized >>>> >> > in the sanitation directory, or in the core? >>>> >> > Also, when I pass a string from views to template it does not render >>>> >> > it >>>> >> > as >>>> >> > html. That is to say, the string for the resource passes with no >>>> >> > problem >>>> >> > but >>>> >> > the symbols which should be links show up as <a href=''>@</a>. >>>> >> > Marcel >>>> >> >>>> >> >>>> >> >>>> >> -- >>>> >> Allen Lee >>>> >> Center for the Study of Institutional Diversity [http://csid.asu.edu] >>>> >> School of Human Evolution and Social Change [http://shesc.asu.edu] >>>> >> College of Liberal Arts and Sciences >>>> >> Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 >>>> >> 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... >>>> > >>>> > >>>> >>>> >>>> >>>> -- >>>> Allen Lee >>>> Center for the Study of Institutional Diversity [http://csid.asu.edu] >>>> School of Human Evolution and Social Change [http://shesc.asu.edu] >>>> College of Liberal Arts and Sciences >>>> Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 >>>> 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... >>> >>> >> >> >> >> -- >> Allen Lee >> Center for the Study of Institutional Diversity [http://csid.asu.edu] >> School of Human Evolution and Social Change [http://shesc.asu.edu] >> College of Liberal Arts and Sciences >> Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 >> 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... >> > > > > -- > Allen Lee > Center for the Study of Institutional Diversity [http://csid.asu.edu] > School of Human Evolution and Social Change [http://shesc.asu.edu] > College of Liberal Arts and Sciences > Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 > 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... > -- Allen Lee Center for the Study of Institutional Diversity [http://csid.asu.edu] School of Human Evolution and Social Change [http://shesc.asu.edu] College of Liberal Arts and Sciences Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... |
From: Allen L. <All...@as...> - 2011-06-14 18:04:58
|
Ah ok. That's much more useful information. You should probably also add href='#' to your a tags; some browsers don't like it when the href is blank. Next, there's two ways to get around this - the first is a quick & easy solution and the second is probably what we would go with longer-term. 1. Turn off autoescaping via {%autoescape off %} ... {% endautoescape %} or via the safe filter, e.g., {{game_state|safe}} (Try the safe filter first, it's more concise). 2. I think we'll eventually want to create a custom template tag / filter (pollutify?) that takes two parameters - a string resource ("Sanitation is blah blah blah ...") and a list of active pollution (basically character positions) and returns a new string with hrefs inserted into the text at each position specified by the active pollution list. I can show you how to do this the next time you come in. On Tue, Jun 14, 2011 at 2:07 AM, Marcel Hurtado <mar...@gm...> wrote: > Thank you. > Here's what I meant. > The sanitation text is in views.py at the moment saved to a variable called > game_state as a global: > game_state = "Sanitation is .... by more than 40 percent." > The symbols are inserted as urls with html tags into that text, like this: > game_state = "Sanitation <a href=''>@</a>is .... by more than <a > href=''>@</a>40 percent." > The method for "play" looks like this, still using the globals. > def play(request, experiment): > treatment = "In-group" > logger.debug("handling play") > return render_to_response('sanitation/play.html', globals(), > context_instance=RequestContext(request)) > When play.html loads the sanitation text is shown on the page like this: > "Sanitation <a href=''>@</a>is .... by more than <a href=''>@</a>40 > percent." > but it should be like this: > "Sanitation @is .... by more than @40 percent." > > > On Mon, Jun 13, 2011 at 10:05 PM, Allen Lee <All...@as...> wrote: >> >> current_location should probably be an extra column on >> ParticipantExperimentRelationship in core, but you'd get & set it in >> your sanitation game code. I can push that out soon, but am working >> on some other bugs first. Not sure what you mean by passing strings >> from views to templates not rendering as html, can you clarify or show >> code examples that aren't working? >> >> On Mon, Jun 13, 2011 at 6:50 PM, Marcel Hurtado >> <mar...@gm...> wrote: >> > Allen, >> > I have some questions. Does the "current_location" variable get >> > initiallized >> > in the sanitation directory, or in the core? >> > Also, when I pass a string from views to template it does not render it >> > as >> > html. That is to say, the string for the resource passes with no problem >> > but >> > the symbols which should be links show up as <a href=''>@</a>. >> > Marcel >> >> >> >> -- >> Allen Lee >> Center for the Study of Institutional Diversity [http://csid.asu.edu] >> School of Human Evolution and Social Change [http://shesc.asu.edu] >> College of Liberal Arts and Sciences >> Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 >> 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... > > -- Allen Lee Center for the Study of Institutional Diversity [http://csid.asu.edu] School of Human Evolution and Social Change [http://shesc.asu.edu] College of Liberal Arts and Sciences Arizona State University | P.O. Box 872402 | Tempe, Arizona 85287-2402 480.727.0401 | Fax: 480.965.7671 | e-mail: all...@as... |
From: Bitbucket <com...@bi...> - 2011-06-14 00:57:57
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/10a1d7ae39b6/ changeset: 10a1d7ae39b6 user: mhurtad date: 2011-06-14 02:56:34 summary: Clean up code affected #: 5 files (2.0 KB) --- a/vcweb/core/models.py Thu Jun 09 19:39:10 2011 -0700 +++ b/vcweb/core/models.py Mon Jun 13 17:56:34 2011 -0700 @@ -282,8 +282,30 @@ @property def configure_url(self): return "/%s/configure" % self.get_absolute_url() +# sanitation urls @property + def consent_url(self): + return "/%s/consent" % self.get_absolute_url() + + @property + def survey_url(self): + return "/%s/survey" % self.get_absolute_url() + + @property + def quiz_url(self): + return "/%s/quiz" % self.get_absolute_url() + + @property + def play_url(self): + return "/%s/play" % self.get_absolute_url() + + @property + def instructions_url(self): + return "/%s/instructions" % self.get_absolute_url() + +# + @property def stop_url(self): return "%s/stop" % self.controller_url --- a/vcweb/sanitation/models.py Thu Jun 09 19:39:10 2011 -0700 +++ b/vcweb/sanitation/models.py Mon Jun 13 17:56:34 2011 -0700 @@ -1,3 +1,23 @@ from django.db import models # Create your models here. + +# @property +def consent_url(self): + return "/%s/consent" % self.get_absolute_url() + + # @property +def survey_url(self): + return "/%s/survey" % self.get_absolute_url() + + #@property +def quiz_url(self): + return "/%s/quiz" % self.get_absolute_url() + +# @property +def play_url(self): + return "/%s/play" % self.get_absolute_url() + + # @property +def instructions_url(self): + return "/%s/instructions" % self.get_absolute_url() --- a/vcweb/sanitation/templates/sanitation/consent.html Thu Jun 09 19:39:10 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/consent.html Mon Jun 13 17:56:34 2011 -0700 @@ -8,6 +8,11 @@ <fieldset><legend>{{experiment.namespace}} participation consent form. (id: {{ experiment.pk }})</legend><div class='info'> +<p> +Next url = {{next_url}}<br> +</p> + + <p>Experiment runs from {{experiment.current_round_start_time}} to {}</p><p> (General Instructions) @@ -42,7 +47,8 @@ This experiment is being run by {{experiment.experimenter}} who may be contacted for assistance during the experiment. </div><p>By clicking accept you agree to the terms of participation.<br> You must be of legal voting age to participate.</p> - <form id="consentform" action=""> + <form id="consentform" action="{{next_url}}"> + <button type='submit'>Accept</button></form> --- a/vcweb/sanitation/urls.py Thu Jun 09 19:39:10 2011 -0700 +++ b/vcweb/sanitation/urls.py Mon Jun 13 17:56:34 2011 -0700 @@ -7,4 +7,7 @@ url(r'^(?P<experiment_id>\d+)/participate$', 'participate', name='participate'), url(r'^(?P<experiment_id>\d+)/consent$', 'consent', name='consent'), url(r'^(?P<experiment_id>\d+)/survey$', 'survey', name='survey'), + url(r'^(?P<experiment_id>\d+)/quiz$', 'quiz', name='quiz'), + url(r'^(?P<experiment_id>\d+)/play$', 'play', name='play'), + url(r'^(?P<experiment_id>\d+)/instructions$', 'instructions', name='instructions'), ) --- a/vcweb/sanitation/views.py Thu Jun 09 19:39:10 2011 -0700 +++ b/vcweb/sanitation/views.py Mon Jun 13 17:56:34 2011 -0700 @@ -5,35 +5,53 @@ import logging import random +#FIXME come from database(user) +treatment = "In-group" +current_location = "consent" -#Globals +#FIXME comes from type of experiment +sequence = ["consent","survey","instructions","quiz","play"] + +#FIXME Globals... comes from somewhere logger = logging.getLogger(__name__) + +#FIXME comes from values set by experimenter for this experiment symbol = '@' growth_rate = 'hourly' -consent = ['Introduction to Global Health Class', symbol,'one week', growth_rate] +venue = 'Introduction to Global Health Class' -# "consent", "survey", "quiz", "play", "instructions" -current_location = "consent" - -treatment = "In-group" + #FIXME rename object to something like "experimental settings" + #FIXME replace 'one week' with a calculation of duration +consent = [venue, symbol,'one week', growth_rate] resource = "Sanitation is vital for health: Readers of a prestigious medical journal were recently asked to name the greatest medical advance in the last century and a half. The result: better sanitation. In nineteenth-century Europe and North America, diarrhoea, cholera, and typhoid spread through poor sanitation was the leading cause of childhood illness and death; today, such deaths are rare in these regions. In developing countries, however, they are all too common, and recent research suggests that poor sanitation and hygiene are either the chief or the underlying cause in over half of the annual 10 million child deaths. Compelling, evidence-based analysis shows that hygiene and sanitation are among the most cost-effective public health interventions to reduce childhood mortality. Access to a toilet alone can reduce child diarrhoeal deaths by over 30 percent, and hand-washing by more than 40 percent." -r = len(resource) + 1 -resource_index = range(1,r) +#FIXME mode to models, make method "current_game_state" that returns game_state +resource_index = range(1,(len(resource) + 1)) pollution_amount = random.randint(1,200) - pollution = random.sample(resource_index, pollution_amount) - - game_state = "" for i, char in enumerate(resource): if i in pollution: +#FIXME turn pollution symbol into symbol_url = str("" + symbol + "") game_state = game_state + symbol_url game_state = game_state + char +#FIXME list of index out of range -# Additional Classes #FIXME move to external file +def next_url(absolute_url,current_location,sequence): + a = sequence.index(current_location) + a = a + 1 + if a == len(sequence): + a = 0 + next_page = sequence[a] + else: + next_page = sequence[a] + logger.debug("next_page: %s is valid", next_page) + return "/%s/%s" % (absolute_url, next_page) + +#FIXME propose that quiz has a limit of 10 questions +#FIXME Find a better place to put the declaration of questions quiz/survey class QuizQuestion(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -59,10 +77,7 @@ q = str("How do you earn extra-credit points?") q7 = QuizQuestion(type="radio", options=['Polluting the text', 'Keeping the text clean', 'Posting messages to my group'], sequence_number=7, answer="No", question=q) - - - - +#FIXME propose that survey has a limit of 10 questions class SurveyQuestion(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -70,7 +85,11 @@ s2 = SurveyQuestion(type="text", options=['MM-DD-YYYY'], sequence_number=2, size="4", question="What year were you born?") s3 = SurveyQuestion(type="text", options=" ", sequence_number=3, size="18", question="What is your expected Degree?") s3 = SurveyQuestion(type="text", options=['MM-YYYY'], sequence_number=3, size="18", question="What is your expected Graduation Date?") -# view functions + +# View url Methods + +# Experimenter + def configure(request, experiment_id=None): experiment = Experiment.objects.get(pk=experiment_id) return render_to_response('sanitation/configure.html', { @@ -78,9 +97,12 @@ }, context_instance=RequestContext(request)) +#Participant + #Pages in experiment sequence def consent(request, experiment): logger.debug("handling consent") consent = ['Introduction to Global Health Class', symbol,'one week', growth_rate] + next_url = "instructions" return render_to_response('sanitation/consent.html', locals(), context_instance=RequestContext(request)) def survey(request, experiment): @@ -100,20 +122,22 @@ logger.debug("handling play") return render_to_response('sanitation/play.html', globals(), context_instance=RequestContext(request)) + #Launch pad that redirects to sequence urls + def participate(request, experiment_id=None): # lookup participant's current location and then invoke the method named by the location + participant = request.user.participant experiment = Experiment.objects.get(pk=experiment_id) -# FIXME: this isn't implemented -# current_location = participant.current_location # "consent", "survey", "quiz", "play", "instructions" - if current_location in ["consent", "survey", "quiz", "play", "instructions"]: +# absolute_url = experiment.namespace +# n_url = next_url(absolute_url,current_location,sequence) + + if current_location in sequence: logger.debug("current location %s is valid", current_location) -# invoke current_location as a method and pass in the request and the experiment location_method = globals()[current_location] return location_method(request, experiment) logger.debug("Invalid location %s, redirecting to dashboard", current_location) -# return redirect('core:dashboard') - return render_to_response('sanitation/'+ current_location + '.html', { + return render_to_response('sanitation/'+experiment_id+'/'+current_location+ '.html', { 'experiment': experiment, }, context_instance=RequestContext(request)) 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: Bitbucket <iss...@bi...> - 2011-06-13 18:45:55
|
--- you can reply above this line --- New issue 20: set up stub JSON response server for mobile app https://bitbucket.org/virtualcommons/vcweb/issue/20/set-up-stub-json-response-server-for A Lee / alllee on Mon, 13 Jun 2011 20:45:47 +0200: Description: set up a stub service to send some example activity JSON back out to the user (e.g., work out what that JSON looks like) Responsible: alllee -- This is an issue notification from bitbucket.org. You are receiving this either because you are the owner of the issue, or you are following the issue. |
From: Bitbucket <com...@bi...> - 2011-06-10 02:39:08
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/f568bade669a/ changeset: f568bade669a user: alllee date: 2011-06-10 04:39:10 summary: Backed out changeset 9703e5247feb affected #: 15 files (4.9 KB) --- a/vcweb/core/fixtures/test_users_participants.json Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/fixtures/test_users_participants.json Thu Jun 09 19:39:10 2011 -0700 @@ -11,7 +11,7 @@ "pk": 1, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 2, "failed_password_attempts": 0, "institution": 1 @@ -21,7 +21,7 @@ "pk": 2, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 3, "failed_password_attempts": 0, "institution": 1 @@ -31,7 +31,7 @@ "pk": 3, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 4, "failed_password_attempts": 0, "institution": 1 @@ -41,7 +41,7 @@ "pk": 4, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 5, "failed_password_attempts": 0, "institution": 1 @@ -51,7 +51,7 @@ "pk": 5, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 6, "failed_password_attempts": 0, "institution": 1 @@ -61,7 +61,7 @@ "pk": 6, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 7, "failed_password_attempts": 0, "institution": 1 @@ -71,7 +71,7 @@ "pk": 7, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 8, "failed_password_attempts": 0, "institution": 1 @@ -81,7 +81,7 @@ "pk": 8, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 9, "failed_password_attempts": 0, "institution": 1 @@ -91,7 +91,7 @@ "pk": 9, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 10, "failed_password_attempts": 0, "institution": 1 @@ -101,7 +101,7 @@ "pk": 10, "model": "core.participant", "fields": { - "can_receive_invitations": true, "current_location":0, + "can_receive_invitations": true, "user": 11, "failed_password_attempts": 0, "institution": 1 --- a/vcweb/core/models.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/models.py Thu Jun 09 19:39:10 2011 -0700 @@ -266,6 +266,12 @@ self.current_round.sequence_label) @property + def participant_group_relationships(self): + for group in self.groups.all(): + for pgr in group.participant_group_relationships.all(): + yield pgr + + @property def namespace(self): return self.experiment_metadata.namespace @@ -315,7 +321,8 @@ @property def playable_round_data(self): - return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS) + return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS, + round_configuration__sequence_number__lte=self.current_round_sequence_number) @property def all_quiz_questions(self): @@ -435,6 +442,7 @@ def log(self, log_message): if log_message: + logger.debug("%s: %s", self, log_message) self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def data_file_name(self, file_ext='csv'): @@ -682,7 +690,7 @@ return Template(template_string).substitute(kwargs, round_number=self.display_number, participant_id=participant_id) def __unicode__(self): - return u"%s > %s" % (self.display_label, self.experiment_configuration) + return u"%s > %s %s" % (self.display_label, self.experiment_configuration, self.sequence_label) @property def display_label(self): @@ -821,7 +829,7 @@ return value def __unicode__(self): - return u"%s (type:%s, scope:%s, experiment: %s)" % (self.name, self.type, self.scope, self.experiment_metadata) + return u"[name: %s, type:%s, scope:%s]" % (self.name, self.type, self.scope) class Meta: ordering = ['name'] @@ -834,6 +842,7 @@ boolean_value = models.NullBooleanField(null=True, blank=True) date_created = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now=True) + is_active = models.BooleanField(default=True) @property def value(self): @@ -929,32 +938,9 @@ return self.activity_log.filter(round_configuration=self.current_round) def log(self, log_message): - self.activity_log.create(round_configuration=self.current_round, - log_message=log_message) - - ''' - Initializes data parameters for all groups in this round, as necessary. - If this round already has data parameters, is a no-op. - def initialize_data_parameters(self): - if self.current_round.is_playable_round: - round_data = self.current_round_data - if round_data.group_data_values.filter(group=self).count() == 0: - logger.debug("no group data values for the current round %s, creating new ones.", round_data) - self.log("Initializing %s data parameters" % round_data) - for group_data_parameter in self.data_parameters: - self.data_values.create(round_data=round_data, parameter=group_data_parameter) - - ''' - - def set_data_value(self, parameter_name=None, parameter=None, value=None): - ''' - Not as efficient as a simple SQL update because we need to do some type - conversion / processing to put the value into the appropriate field. - ''' - data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) - data_value.value = value - self.log("setting parameter %s = %s" % (parameter, value)) - data_value.save() + if log_message: + logger.debug(log_message) + self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def subtract(self, parameter=None, amount=0): self.add(parameter, -amount) @@ -987,11 +973,25 @@ return self.get_data_value(parameter=parameter, parameter_name=parameter_name).value def get_data_value(self, parameter=None, parameter_name=None, round_data=None): + if round_data is None: + round_data = self.current_round_data criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) - data_value, created = self.data_values.get_or_create(**criteria) - if created: - logger.debug("Created new data value in get_data_value: %s", data_value) - return data_value + try: + return self.data_values.get(**criteria) + except GroupRoundDataValue.DoesNotExist as e: + logger.warning("No data value found for criteria %s", criteria) + raise e + + def set_data_value(self, parameter_name=None, parameter=None, value=None): + ''' + Not as efficient as a simple SQL update because we need to do some type + conversion / processing to put the value into the appropriate field. + ''' + data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) + data_value.value = value + self.log("setting parameter %s = %s" % (parameter, value)) + data_value.save() + def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None): return dict([ @@ -1029,10 +1029,14 @@ self.transfer_parameter(parameter, value) def transfer_parameter(self, parameter, value): + if self.experiment.is_last_round: + logger.error("Trying to transfer parameter (%s: %s) past the last round of the experiment", + parameter, value) + return None next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) - logger.debug("group data value %s (%s)", group_data_value, created) + logger.debug("%s (%s)", group_data_value, created) if not created: group_data_value.value = value group_data_value.save() @@ -1108,7 +1112,6 @@ class Participant(CommonsUser): - current_location = models.IntegerField(default=0) can_receive_invitations = models.BooleanField(default=False) groups = models.ManyToManyField(Group, through='ParticipantGroupRelationship', related_name='participants') experiments = models.ManyToManyField(Experiment, through='ParticipantExperimentRelationship', related_name='participants') --- a/vcweb/core/templates/contact.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/templates/contact.html Thu Jun 09 19:39:10 2011 -0700 @@ -9,9 +9,7 @@ Please feel free to send us feedback, suggestions, or comments via our <a class='external' href='http://commons.asu.edu/contact'>contact form</a>. <br/><br/> - We also maintain an issue tracker at <a class='external' href='https://bitbucket.org/virtualcommons/vcweb/issues'>BitBucket</a> - and <a class='external' href='http://sourceforge.net/apps/trac/virtualcommons/'>SourceForge</a> - (one of these will eventually be retired). + We also maintain an issue tracker at <a class='external' href='https://bitbucket.org/virtualcommons/vcweb/issues'>BitBucket</a>. </div></div> {% endblock page %} --- a/vcweb/core/templates/includes/participant.events.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/templates/includes/participant.events.html Thu Jun 09 19:39:10 2011 -0700 @@ -19,10 +19,10 @@ "message": payload }; } -function createChatEvent(message) { - return createMessageEvent(message); +function createChatEvent(payload) { + return createMessageEvent(payload); } -function createSubmitEvent(message) { - return createMessageEvent(message, "submit"); +function createSubmitEvent(payload) { + return createMessageEvent(payload, "submit"); } </script> --- a/vcweb/core/templates/includes/socket.io.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/templates/includes/socket.io.html Thu Jun 09 19:39:10 2011 -0700 @@ -4,6 +4,7 @@ </script><script type="text/javascript"> +var vcwebSocket = {}; var cachedSocket; var defaultPort = 8881; function connect(resource) { @@ -17,17 +18,21 @@ console.log("using default port: " + defaultPort); port = defaultPort; } - cachedSocket = new io.Socket(host, {"port": port, "resource": resource}); + console.log("Establishing connection to " + host + ":" + port + "/" + resource); + cachedSocket = new io.Socket(host, {port: port, resource: resource}); cachedSocket.connect(); + console.log("connect invoked to " + host + ":" + port + "/" + resource); cachedSocket.on('connect', function() { - console.log("Connecting to " + host + ":" + port + "/" + resource); + console.debug("onconnect to " + host + ":" + port + "/" + resource); cachedSocket.send(createConnectionEvent()); }); + /* cachedSocket.on('disconnect', function(data) { console.log("received disconnect, reconnecting."); cachedSocket.connect(); cachedSocket.send(createReconnectionEvent()); }); + */ return cachedSocket; } function getCachedSocket() { --- a/vcweb/core/templates/participant/base.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/templates/participant/base.html Thu Jun 09 19:39:10 2011 -0700 @@ -8,8 +8,8 @@ {% include "includes/socket.io.html" %} <script type='text/javascript'> var qtipOptions = {position: { corner: {target: 'topMiddle', tooltip: 'bottomMiddle'}}, style: { name: 'green', tip: 'bottomMiddle'} }; - connect('participant/{{participant_experiment_relationship.pk}}'); $(function() { + connect('participant/{{participant_experiment_relationship.pk}}'); $('[title]').qtip(qtipOptions); }); </script> --- a/vcweb/core/templates/participant/dashboard.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/core/templates/participant/dashboard.html Thu Jun 09 19:39:10 2011 -0700 @@ -9,19 +9,17 @@ {% endblock %} {% block page %} <div class='info infoIcon ui-corner-all'> - Welcome back, {{request.user.participant}}. <br> Experiments you're participating in are listed below. + Welcome back, {{request.user.participant}}. Experiments you're participating in are listed below. </div><div id='experiments'> {% for experiment_metadata, experiment_status_dict in experiments.items %} -<!-- <h3> {{ experiment_metadata.title }} </h3> --> + <h3> {{ experiment_metadata.title }} </h3> {% for experiment_status, experiments in experiment_status_dict.items %} {% if experiments %} - <h4>{{ experiment_metadata.title }}: {{experiments.0.get_status_display}}</h4> + <h4>{{experiments.0.get_status_display}}</h4> {% for experiment in experiments %} - <div style='padding: 2px;' class='ui-state-highlight'> -<!-- FIXME display correct time for end of experiment ---> -<!-- FIXME display the given name of the experiment, for example "Introduction to Global Health GH 101" ---> - <a title='This experiment is being run by {{experiment.experimenter}}' href='{{ experiment.participant_url }}'><span class='icon-left ui-icon {{experiment_status|lower}}'></span><u>{{ experiment.namespace }}</u><br> From {{experiment.current_round_start_time}} until {{experiment.current_round_start_time}}</a> + <div style='padding: 10px;' class='ui-state-highlight'> + <a title='This experiment is being run by {{experiment.experimenter}}' href='{{ experiment.participant_url }}'><span class='icon-left ui-icon {{experiment_status|lower}}'></span>{{ experiment.status_line }}, started {{experiment.current_round_start_time}}</a></div> {% endfor %} {% endif %} --- a/vcweb/fabfile.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/fabfile.py Thu Jun 09 19:39:10 2011 -0700 @@ -19,7 +19,7 @@ # default to current working directory env.project_path = os.path.dirname(__file__) env.hosts = ['localhost'] -env.hg_url = 'http://virtualcommons.hg.sourceforge.net:8000/hgroot/virtualcommons/virtualcommons' +env.hg_url = 'https://bitbucket.org/virtualcommons/vcweb' env.apache = 'httpd' env.applist = ['core', 'forestry'] env.apps = ' '.join(env.applist) @@ -30,7 +30,7 @@ """ this currently only works for sqlite3 development database. do it by hand with -postgres a few times to figure out what to automate, probably with south? +postgres a few times to figure out what to automate. """ syncdb_commands = ['(test -f vcweb.db && rm -f vcweb.db) || true', '%(python)s manage.py syncdb --noinput' % env, @@ -44,7 +44,7 @@ def syncdb(**kwargs): with cd(env.project_path): - _virtualenv(*syncdb_commands, **kwargs) + _virtualenv(run, *syncdb_commands, **kwargs) def setup_virtualenv(): @@ -71,14 +71,14 @@ # figure out what the appropriate rabbitmq perms are here. sudo('rabbitmqctl set_permissions -p %s %s ".*" ".*" ".*"' % (vcweb_settings.BROKER_VHOST, vcweb_settings.BROKER_USER), pty=True) -def _virtualenv(*commands, **kwargs): +def _virtualenv(executor, *commands, **kwargs): """ source the virtualenv before executing this command """ env.command = ' && '.join(commands) - return local('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) + return executor('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) def pip(): ''' looks for requirements.pip in the django project directory ''' - _virtualenv('pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) + _virtualenv(run, 'pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) #with cd(env.virtualenv_path): # sudo_chain('chgrp -R %(deploy_group)s .' % env, 'chmod -R g+rw' % env, pty=True) @@ -90,13 +90,13 @@ runs tests on this local codebase, not the deployed codebase ''' with cd(env.project_path): - _virtualenv('%(python)s manage.py test %(apps)s' % env) + _virtualenv(local, '%(python)s manage.py test %(apps)s' % env) def tornadio(ip="127.0.0.1", port=None): from vcweb import settings as vcweb_settings if port is None: port = vcweb_settings.SOCKET_IO_PORT - _virtualenv("{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) + _virtualenv(local, "{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) def server(ip="127.0.0.1", port=8000): local("{python} manage.py runserver {ip}:{port}".format(python=env.python, **locals()), capture=False) @@ -156,7 +156,7 @@ if confirm("syncdb?"): syncdb() env.static_root = vcweb_settings.STATIC_ROOT - _virtualenv('%(python)s manage.py collectstatic' % env) + _virtualenv(run,'%(python)s manage.py collectstatic' % env) sudo_chain('chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', --- a/vcweb/forestry/models.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/models.py Thu Jun 09 19:39:10 2011 -0700 @@ -11,7 +11,7 @@ check all forestry experiments. ''' -def get_resource_level(group=None, round_data=None): +def get_resource_level(group, round_data=None): ''' returns the group resource level data parameter ''' return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) @@ -44,6 +44,12 @@ def set_group_harvest(group, value): group.set_data_value(parameter=get_group_harvest_parameter(), value=value) +def should_reset_resource_level(round_configuration): + return round_configuration.get_parameter_value('reset.resource_level', default=False) + +def get_initial_resource_level(round_configuration): + return round_configuration.get_parameter_value('initial.resource_level', default=100) + def get_max_harvest_decision(resource_level): if resource_level >= 25: return 5 @@ -110,8 +116,9 @@ during a practice or regular round, set up resource levels and participant harvest decision parameters ''' - if round_configuration.get_parameter_value('reset.resource_level', default=False): - initial_resource_level = round_configuration.get_parameter_value('initial.resource_level', default=100) + if should_reset_resource_level(round_configuration): + initial_resource_level = get_initial_resource_level(round_configuration) + logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) for group in experiment.groups.all(): ''' set resource level to initial default ''' group.log("Setting resource level to initial value [%s]" % initial_resource_level) @@ -122,9 +129,11 @@ pass def round_teardown(experiment, **kwargs): - ''' round teardown calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth and transferring''' - logger.debug("%s", experiment) - resource_level_parameter = get_resource_level_parameter() + ''' + 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. + ''' + logger.debug(experiment) current_round_configuration = experiment.current_round max_resource_level = 100 for group in experiment.groups.all(): @@ -148,7 +157,7 @@ if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % get_resource_level(group)) - group.transfer_to_next_round(resource_level_parameter) + group.transfer_parameter(current_resource_level.parameter, current_resource_level.value) ''' FIXME: figure out a better way to tie these signal handlers to a specific --- a/vcweb/forestry/templates/forestry/chat.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/templates/forestry/chat.html Thu Jun 09 19:39:10 2011 -0700 @@ -4,7 +4,6 @@ <script type="text/javascript"> $(function() { var chatDiv = document.getElementById('chat-div'); - // var s = connect(window.location.hostname, 8888, 'participant/{{participant_experiment_relationship.pk}}'); var s = getCachedSocket(); s.on('message', function(json_string) { var json = jQuery.parseJSON(json_string); @@ -45,16 +44,20 @@ </script> {% endblock %} {% block title %} -Forestry Chat Round +Communication Round: {{experiment}} {% endblock %} {% block page %} -<h3>Chat</h3> - <div class='info ui-corner-all'> - <span class='icon-left ui-icon ui-icon-info'></span> - <b> - Hello, {{participant.full_name}}. - You are participant {{ participant_group_relationship.participant_number }} in {{participant_group_relationship.group}}. +<h3 style='margin-top: -30px;'>Communication Round Instructions</h3> + <div class='alert alertIcon ui-corner-all' style='font-size: 0.8em; line-height: 1.0em;'> + You are <b>participant {{ participant_group_relationship.participant_number }}.</b> + You can chat about any aspect of the experiment with the other members of your group with two restrictions: + <ol style='margin-left: 15px;margin-bottom: 0;'> + <li>You cannot promise side payments after the experiment is completed + <li>You cannot threaten any member of your group with any consequences after the experiment is completed + </ol> + We are monitoring the chat traffic and if we detect any violation + of these rules you will be removed from the experiment. </b></div><div id='chat-div' class='chat notice ui-corner-all'> --- a/vcweb/forestry/templates/forestry/participate.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Thu Jun 09 19:39:10 2011 -0700 @@ -1,6 +1,7 @@ {% extends "participant/base.html" %} {% block head %} {{ block.super }} +{% comment %}{% include "includes/jquery.confirm.html" %} {%endcomment%} <script type="text/javascript"> $(function() { $("#forestry-form").validate({ @@ -32,11 +33,26 @@ } }); $('#forestry-form').submit(function() { - $(this).attr('disabled', true); - harvestDecisionValue = $('#harvest-decision-id').val(); + var harvestDecisionValue = $('#harvest-decision-id').val(); + // FIXME: this isn't getting sent through properly before the submit occurs. + // Should work out communicating directly from Django to the tornadio process. s.send(createSubmitEvent(harvestDecisionValue)); return true; }); + /* + $('#submit').click(function() { + $(this).fastConfirm({ + questionText: "Submit your harvest decision of " + harvestDecisionValue + "?", + onProceed: function(trigger) { + $('#forestry-form').submit(); + $(trigger).fastConfirm('close'); + }, + onCancel: function(trigger) { + $(trigger).fastConfirm('close'); + } + }); + }); + */ }); </script> {% endblock %} @@ -80,7 +96,7 @@ <input id='harvest-decision-id' style='width: 2em; text-align: right;' type="text" name='harvest_decision' class='required digits' maxlength='1'/></div><div> - <button type='submit'>Harvest</button> + <button id='submit' type='submit'>Harvest</button></div></form> {% else %} --- a/vcweb/forestry/templates/forestry/wait.html Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/templates/forestry/wait.html Thu Jun 09 19:39:10 2011 -0700 @@ -1,15 +1,12 @@ {% extends "participant/base.html" %} {% block head %} {{ block.super }} -{% include "includes/participant.events.html" %} -{% include "includes/socket.io.html" %} {% include "includes/tablesorter.html" %} <script type="text/javascript"> $(function() { - $('#participant-history').tablesorter({widgets: ['zebra'], - headers: { 0: {sorter: false}, 1: {sorter: false} } - }); - $('[title]').qtip({position: { corner: {target: 'topMiddle', tooltip: 'bottomMiddle'}}, style: { name: 'green', tip: 'bottomMiddle'} }); + $('#participant-history').tablesorter({widgets: ['zebra'], + headers: { 0: {sorter: false}, 1: {sorter: false} } + }); var s = getCachedSocket(); s.on('message', function(json_string) { var json = jQuery.parseJSON(json_string); @@ -36,14 +33,11 @@ {% block page %} <h3>Waiting for the next round to start</h3><p> -The facilitator will start the next round when every participant is ready. -<br/> +Please wait, {{request.user.participant}}. The facilitator will start the next round when every participant is ready. +</p><div class='ui-state-highlight ui-corner-all'><small><span class='float-left vcweb-ui-icon ui-icon ui-icon-circle-arrow-w'></span><a href='participate'>return to participation page</a></small></div> -<br/> -</p> - <h3>Participant History</h3><div id='group-history'> {% if participant_history %} @@ -89,11 +83,4 @@ {% endif %} </div> -<h3>Instructions</h3> -<div class='info ui-corner-all'> - Please wait until the next round begins. - {% comment%} - add ajax spinner and polling - {% endcomment %} -</div> {% endblock %} --- a/vcweb/forestry/tests.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/tests.py Thu Jun 09 19:39:10 2011 -0700 @@ -2,11 +2,7 @@ ParticipantRoundDataValue, GroupRoundDataValue, ParticipantExperimentRelationship, ParticipantGroupRelationship) from vcweb.core.tests import BaseVcwebTest -from vcweb.forestry.models import (get_group_harvest_parameter, - get_regrowth_parameter, round_setup, round_teardown, get_resource_level, - set_resource_level, set_harvest_decision, get_harvest_decision_parameter, - get_harvest_decisions, forestry_sender, get_forestry_experiment_metadata, - get_resource_level_parameter) +from vcweb.forestry.models import * import logging logger = logging.getLogger(__name__) @@ -89,12 +85,43 @@ e.current_round_sequence_number = rc.sequence_number self.assertEqual(e.current_round_template, 'forestry/quiz.html', 'should return default quiz.html') +class TransferParametersTest(BaseVcwebTest): + def test_transfer_parameters(self): + def calculate_expected_resource_level(resource_level, harvested): + after_harvest = max(resource_level - harvested, 0) + return min(100, int(after_harvest + (after_harvest * .10))) -''' -FIXME: several of these can and should be lifted to core/tests.py -''' + e = self.advance_to_data_round() + expected_resource_level = 100 + while True: + e.start_round() + current_round_configuration = e.current_round + if should_reset_resource_level(current_round_configuration): + logger.debug("resetting resource level for round %s, %d", current_round_configuration, + e.current_round_sequence_number) + expected_resource_level = get_initial_resource_level(current_round_configuration) + + max_harvest_decision = get_max_harvest_decision(expected_resource_level) + for pgr in e.participant_group_relationships: + self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) + set_harvest_decision(pgr, max_harvest_decision) + e.end_round() + + if current_round_configuration.is_playable_round: + expected_resource_level = calculate_expected_resource_level(expected_resource_level, max_harvest_decision * 5) + + for group in e.groups.all(): + self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) + + if e.has_next_round: + e.advance_to_next_round() + else: + break + class ForestryParametersTest(BaseVcwebTest): - + ''' + FIXME: several of these can and should be lifted to core/tests.py + ''' def test_initialize_parameters_at_round_start(self): e = self.advance_to_data_round() e.start_round() @@ -165,15 +192,13 @@ for func in caching_funcs: verify_refreshed_data(func) - - def test_get_set_resource_level(self): e = self.advance_to_data_round() - + e.start_round() for group in e.groups.all(): resource_level = get_resource_level(group) self.assertTrue(resource_level.pk > 0) - self.assertFalse(resource_level.value) + self.assertEqual(resource_level.value, 100) resource_level.value = 3 resource_level.save() --- a/vcweb/forestry/views.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/forestry/views.py Thu Jun 09 19:39:10 2011 -0700 @@ -114,10 +114,11 @@ class ParticipateView(SingleObjectTemplateResponseMixin, ParticipantSingleExperimentMixin, View): template_name_field = 'current_round_template' -# FIXME: refactor this ugliness +# FIXME: refactor the conditional logic here. @participant_required def participate(request, experiment_id=None): participant = request.user.participant + logger.debug("handling participate request for %s and experiment %s", participant, experiment_id) try: experiment = Experiment.objects.get(pk=experiment_id) current_round = experiment.current_round @@ -190,13 +191,14 @@ }, context_instance=RequestContext(request)) + +# FIXME: figure out the appropriate place for this trees = { 'deciduous': { 'name': 'deciduous-tree', 'height': 32 }, 'pine': {'name': 'pine-tree', 'height': 79 }, } - - def play(request, experiment, participant): + logger.debug("handling play request for participant %s and experiment %s", participant, experiment) form = HarvestDecisionForm(request.POST or None) participant_group_relationship = participant.get_participant_group_relationship(experiment) participant_experiment_relationship = participant.get_participant_experiment_relationship(experiment) --- a/vcweb/vcwebio.py Thu Jun 09 16:41:40 2011 -0700 +++ b/vcweb/vcwebio.py Thu Jun 09 19:39:10 2011 -0700 @@ -12,6 +12,7 @@ from vcweb.core.models import ParticipantExperimentRelationship, ParticipantGroupRelationship, ChatMessage, Experimenter, Experiment from vcweb import settings +# FIXME: currently tornadio.vcweb to avoid confusion with vcweb loggers logger = logging.getLogger('tornadio.vcweb') def info_json(message): @@ -32,29 +33,37 @@ ''' Manages socket.io connections to tornadio. ''' - # bidi maps for (participant.pk, experiment.pk) -> connection + # bidi maps for (participant.pk, experiment.pk) -> SocketConnection connection_to_participant = {} participant_to_connection = {} - # bidi maps for (experimenter.pk, experiment.pk) -> connection + # bidi maps for (experimenter.pk, experiment.pk) -> SocketConnection connection_to_experimenter = {} experimenter_to_connection = {} + ''' + We use participant_pk + experiment_pk tuples as keys in these bidimaps because + groups may not have formed yet. + FIXME: consider refactoring core so that an "all" group always exists in an + experiment. + ''' refresh_json = simplejson.dumps({ 'message_type': 'refresh' }) def add_experimenter(self, connection, incoming_experimenter_pk, incoming_experiment_pk): + logger.debug("experimenter_to_connection: %s", self.experimenter_to_connection) + logger.debug("connection_to_experimenter: %s", self.connection_to_experimenter) experimenter_pk = int(incoming_experimenter_pk) experiment_id = int(incoming_experiment_pk) experimenter_tuple = (experimenter_pk, experiment_id) logger.debug("registering experimenter %s with connection %s", experimenter_pk, connection) if connection in self.connection_to_experimenter: - logger.debug("experimenter already registered, removing previous mapping") - self.remove_experimenter(connection) + logger.debug("this experimenter has an existing connection (%s <-> %s) ", + self.connection_to_experimenter[connection], experimenter_tuple) self.connection_to_experimenter[connection] = experimenter_tuple self.experimenter_to_connection[experimenter_tuple] = connection def remove_experimenter(self, connection): if connection in self.connection_to_experimenter: experimenter_tuple = self.connection_to_experimenter[connection] - logger.debug("removing experimenter %s", experimenter_tuple[0]) + logger.debug("removing experimenter %s", experimenter_tuple) del self.connection_to_experimenter[connection] if experimenter_tuple in self.experimenter_to_connection: del self.experimenter_to_connection[experimenter_tuple] @@ -64,23 +73,15 @@ (participant_pk, experiment_pk) = self.connection_to_participant[connection] logger.debug("Looking for ParticipantGroupRelationship with tuple (%s, %s)", participant_pk, experiment_pk) return ParticipantGroupRelationship.objects.get(participant__pk=participant_pk, group__experiment__pk = experiment_pk) - logger.debug("Didn't find connection %s in connection map %s.", connection, self.connection_to_participant) + logger.warning("Couldn't find a participant group relationship using connection %s in connection map %s", connection, self.connection_to_participant) return None - def get_experiment(self, connection): - if connection in self.connection_to_participant: - experiment_pk = self.connection_to_participant[connection][1] - return Experiment.objects.get(pk=experiment_pk) - else: - return None - def get_participant_experiment_tuple(self, connection): - if connection in self.connection_to_participant: - return self.connection_to_participant[connection] - else: - return None + return self.connection_to_participant[connection] def add_participant(self, auth_token, connection, participant_experiment_relationship): + logger.debug("connection to participant: %s", self.connection_to_participant) + logger.debug("participant to connection: %s", self.participant_to_connection) participant_tuple = (participant_experiment_relationship.participant.pk, participant_experiment_relationship.experiment.pk) if participant_tuple in self.participant_to_connection: logger.debug("participant already has a connection, removing previous mappings.") @@ -197,6 +198,7 @@ logger.debug("sending all connected participants %s to %s", notified_participants, url) def on_close(self): + logger.debug("removing experimenter connection %s", self) connection_manager.remove_experimenter(self) class ParticipantHandler(SocketConnection): @@ -241,6 +243,7 @@ elif event.message_type == 'submit': (participant_pk, experiment_pk) = connection_manager.get_participant_experiment_tuple(self) experiment = Experiment.objects.get(pk=experiment_pk) + logger.debug("processing participant submission for participant %s and experiment %s", participant_pk, experiment) # sanity check, make sure this is a data round. if experiment.is_data_round_in_progress: experimenter_tuple = (experiment.experimenter.pk, experiment.pk) @@ -252,7 +255,7 @@ event.participant_number = participant_group_relationship.participant_number event.participant_group = participant_group_relationship.group_number json = simplejson.dumps(event.__dict__) - logger.debug("json is: %s", json) + logger.debug("submit event json: %s", json) connection_manager.send_to_experimenter(experimenter_tuple, json) if experiment.all_participants_have_submitted: connection_manager.send_to_experimenter( 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: Bitbucket <com...@bi...> - 2011-06-09 23:43:54
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/58f4bfdce7da/ changeset: 58f4bfdce7da user: mhurtad date: 2011-06-10 01:20:21 summary: Content from previous version of sanitation game migrated, instructions, quiz, survey, game. affected #: 9 files (9.0 KB) --- a/vcweb/sanitation/templates/sanitation/consent.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/consent.html Thu Jun 09 16:20:21 2011 -0700 @@ -4,16 +4,47 @@ {% block page %} + <fieldset> - <legend>{{experiment.namespace}} (id: {{ experiment.pk }})</legend> + <legend>{{experiment.namespace}} participation consent form. (id: {{ experiment.pk }})</legend><div class='info'><p>Experiment runs from {{experiment.current_round_start_time}} to {}</p> -<p>Dear Participant: <br> generic instructions...</p> -<p>Specific Instructions</p> -This experiment is being run by {{experiment.experimenter}} +<p> +(General Instructions) + +This experiment is part of vcweb designed to conduct research with the goal to evaluate the impact of different policies on collective action in regard to investments in public goods. In this case, the experiment concerns the maintainance of a resource that is subject to pollution, which we refer to as a 'sanitation game'. + +By participaint you will be assigned to a group that shares a virtual resource which you need to maintain free of pollution. The resource is represented by a text-file which containts text discussing public health and sanitation. The pollution is represented by {{consent.1}} symbols. On an {{consent.3}} schedule, more {{consent.1}} symbols are added. The objective to remove the pollution represent by the {{consent.1}} symbols and keep it clean from pollution. Less symbols means a cleaner resource. + +The resource maybe clean or dirty depending on the activity of your group. In order to keep the resource clean you will have to return to this site to check it over the course of the experiment which lasts {{consent.2}} + +We hope our study will increase the understanding of policies that increase the dissemination of sanitation. + +Vcweb maintains your confidentiality during the experiment by vcweb and after the experiment by the experiment proctor. Your entire activity during the sanitation game is confidential. + +If you have any questions concerning the research study, please contact the research team at: (Marco Janssen, Mar...@as...). + +There are no foreseeable risks or discomforts to your participation. + +</p> + +<p> +(Specific Instructions) + +The other participants in this experiment are students enrolled in your {{consent.0}}. The extra credit points you can earn depend on the performance of your group in this task and is explained when you have logged in. + +The first time you Login you will be asked to answer some questions about demographics and comprehension of the instructions for the {{experiment.namespace}} game. + +If you choose not to participate or to withdraw from the study at any time, there will be no penalty to your grade. +</p> + + +This experiment is being run by {{experiment.experimenter}} who may be contacted for assistance during the experiment. </div> -<p>By clicking accept you agree to the terms of participation. (IRB stuff)</p> - <form id="consentform"> +<p>By clicking accept you agree to the terms of participation.<br> You must be of legal voting age to participate.</p> + <form id="consentform" action=""><button type='submit'>Accept</button></form> + +<p>The results of this study may be used in publications or presentations but your name will not used.</p> {% endblock page %} --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vcweb/sanitation/templates/sanitation/includes/instructions.html Thu Jun 09 16:20:21 2011 -0700 @@ -0,0 +1,12 @@ + +{% if treatment == "In-group" %} +<p> +You will see a text in which randomly @ symbols are added. If the @ symbols are removed from the text, it is considered to be clean. The cleaner the text on average the more extra credit points you can earn. All group members see the same text and every member of the group get the same amount of points dependent on the cleanness of the text of your group. Thus removing @ symbols can lead to more extra credit points. Your group is competing to for the least amount of @ over the course of the entire experiment. +</p> +{% endif %} + +{% if treatment == "Out-group" %} +<p> +(Out-group competition)You will see a text in which randomly @ symbols are added. If the @ symbols are removed from the text, it is considered to be clean. The cleaner the text on average the more extra credit points you can earn. All group members see the same text and every member of the group get the same amount of points dependent on the cleanness of the text of your group. Thus removing @ symbols can lead to more extra credit points. Your group is competing to for the least amount of @ over the course of the entire experiment compared to other groups. +</p> +{% endif %} --- a/vcweb/sanitation/templates/sanitation/includes/payoff-table.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/includes/payoff-table.html Thu Jun 09 16:20:21 2011 -0700 @@ -1,3 +1,4 @@ +{% if treatment == "In-group" %} <div class='payoff-absolute'><h3>Payoff table</h3><table> @@ -14,7 +15,9 @@ </tbody></table></div> +{% endif %} +{% if treatment == "Out-group" %} <div class='payoff-ranking'><h3>Payoff table</h3><table> @@ -31,3 +34,6 @@ </tbody></table></div> +{% endif %} + + --- a/vcweb/sanitation/templates/sanitation/instructions.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/instructions.html Thu Jun 09 16:20:21 2011 -0700 @@ -1,13 +1,14 @@ {% extends "participant/base.html" %} -{% block title %} {{experiment.namespace}} Configuration {% endblock %} +{% block title %} {{experiment.namespace}} Instructions {% endblock %} {% block page %} <fieldset> - <legend>{{experiment.namespace}} (id: {{ experiment.pk }})</legend> + <legend>{{experiment.namespace}} Instructions (id: {{ experiment.pk }})</legend><div class='info'> -<p>Instructions printed here</p> +{% include "sanitation/includes/instructions.html" %} + </div><form id="consentform"><button type='submit'>Accept</button> --- a/vcweb/sanitation/templates/sanitation/play.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/play.html Thu Jun 09 16:20:21 2011 -0700 @@ -1,21 +1,34 @@ {% extends "participant/base.html" %} -{% block title %} {{experiment.namespace}} Configuration {% endblock %} +{% block title %} {{experiment.namespace}} experiment {% endblock %} {% block content %} <fieldset> - <legend>{{experiment.namespace}} (id: {{ experiment.pk }})</legend> + <legend>{{experiment.namespace}} {{current_location}} (id: {{ experiment.pk }})</legend> + + +<div class='info'> +{% include "sanitation/includes/instructions.html" %} +</div> + <div class='info'><p> -Dynamic Experiment goes here. +{{game_state}} </p></div> +<div class='info'> +{% include "sanitation/includes/payoff-table.html" %} +</div> {% endblock content %} + + + + {% block sidebar %} {% include "sanitation/includes/chat.html" %} -{% include "sanitation/includes/payoff-table.html" %} + {% endblock sidebar %} --- a/vcweb/sanitation/templates/sanitation/quiz.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/quiz.html Thu Jun 09 16:20:21 2011 -0700 @@ -5,12 +5,51 @@ {% block page %} <fieldset> - <legend>{{experiment.namespace}} (id: {{ experiment.pk }})</legend> + + <legend>{{experiment.namespace}} {{current_location}} (id: {{ experiment.pk }})</legend><div class='info'> - {% for question in quiz_questions %} -<p><b>{{question.0}}</b> {{question.1}} <input id='' type="{{question.2}}" value="{{question.3}}"/></p> - {% endfor %} +<table> +<tr><td>{{ q1.sequence_number }}. {{ q1.question }}</td></tr><tr><td> +{% for option in q1.options %} +<input name="q1_{{ q1.type }}" type="{{ q1.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q2.sequence_number }}. {{ q2.question }}</td></tr><tr><td> +{% for option in q1.options %} +<input name="q2_{{ q2.type }}" type="{{ q2.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q3.sequence_number }}. {{ q3.question }}</td></tr><tr><td> +{% for option in q3.options %} +<input name="q3_{{ q3.type }}" type="{{ q3.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q4.sequence_number }}. {{ q4.question }}</td></tr><tr><td> +{% for option in q4.options %} +<input name="q4_{{ q4.type }}" type="{{ q4.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q5.sequence_number }}. {{ q5.question }}</td></tr><tr><td> +{% for option in q1.options %} +<input name="q5_{{ q5.type }}" type="{{ q5.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q6.sequence_number }}. {{ q6.question }}</td></tr><tr><td> +{% for option in q6.options %} +<input name="q6_{{ q6.type }}" type="{{ q6.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ q7.sequence_number }}. {{ q7.question }}</td></tr><tr><td> +{% for option in q7.options %} +<input name="q7_{{ q7.type }}" type="{{ q7.type }}">{{ option }}<br> +{% endfor %} +</td></tr> + + +</table> + + </div><form id="consentform"> --- a/vcweb/sanitation/templates/sanitation/survey.html Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/templates/sanitation/survey.html Thu Jun 09 16:20:21 2011 -0700 @@ -3,11 +3,26 @@ {% block title %} {{experiment.namespace}} Configuration {% endblock %} {% block page %} - <fieldset> - <legend>{{experiment.namespace}} (id: {{ experiment.pk }})</legend> + <legend>{{experiment.namespace}} {{current_location}} (id: {{ experiment.pk }})</legend><div class='info'> -<p>Survey printed here</p> +<table> +<tr><td>{{ s1.sequence_number }}. {{ s1.question }}</td></tr><tr><td> +{% for option in s1.options %} +<input name="s1_{{ s1.type }}" type="{{ s1.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ s2.sequence_number }}. {{ s2.question }}</td></tr><tr><td> +{% for option in s2.options %} +<input name="s2_{{ s2.type }}" type="{{ s2.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +<tr><td>{{ s3.sequence_number }}. {{ s3.question }}</td></tr><tr><td> +{% for option in s3.options %} +<input name="s3_{{ s3.type }}" type="{{ s3.type }}">{{ option }}<br> +{% endfor %} +</td></tr> +</table></div><form id="consentform"><button type='submit'>Accept</button> --- a/vcweb/sanitation/urls.py Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/urls.py Thu Jun 09 16:20:21 2011 -0700 @@ -5,4 +5,6 @@ url(r'^(?P<experiment_id>\d+)/configure$', 'configure', name='configure'), url(r'^(?P<experiment_id>\d+)/experimenter$', 'monitor_experiment', name='monitor_experiment'), url(r'^(?P<experiment_id>\d+)/participate$', 'participate', name='participate'), + url(r'^(?P<experiment_id>\d+)/consent$', 'consent', name='consent'), + url(r'^(?P<experiment_id>\d+)/survey$', 'survey', name='survey'), ) --- a/vcweb/sanitation/views.py Tue Jun 07 17:05:12 2011 -0700 +++ b/vcweb/sanitation/views.py Thu Jun 09 16:20:21 2011 -0700 @@ -1,17 +1,76 @@ +# importing external methods and classes from vcweb.core.models import Experiment from django.shortcuts import render_to_response, redirect from django.template.context import RequestContext +import logging +import random -import logging + +#Globals logger = logging.getLogger(__name__) +symbol = '@' +growth_rate = 'hourly' +consent = ['Introduction to Global Health Class', symbol,'one week', growth_rate] +# "consent", "survey", "quiz", "play", "instructions" +current_location = "consent" + +treatment = "In-group" +resource = "Sanitation is vital for health: Readers of a prestigious medical journal were recently asked to name the greatest medical advance in the last century and a half. The result: better sanitation. In nineteenth-century Europe and North America, diarrhoea, cholera, and typhoid spread through poor sanitation was the leading cause of childhood illness and death; today, such deaths are rare in these regions. In developing countries, however, they are all too common, and recent research suggests that poor sanitation and hygiene are either the chief or the underlying cause in over half of the annual 10 million child deaths. Compelling, evidence-based analysis shows that hygiene and sanitation are among the most cost-effective public health interventions to reduce childhood mortality. Access to a toilet alone can reduce child diarrhoeal deaths by over 30 percent, and hand-washing by more than 40 percent." + +r = len(resource) + 1 +resource_index = range(1,r) +pollution_amount = random.randint(1,200) + +pollution = random.sample(resource_index, pollution_amount) + + +game_state = "" +for i, char in enumerate(resource): + if i in pollution: + symbol_url = str("" + symbol + "") + game_state = game_state + symbol_url + game_state = game_state + char + + +# Additional Classes #FIXME move to external file class QuizQuestion(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) -q1 = QuizQuestion(type="radio", options=['Yes', 'No', 'Maybe'], sequence_number=1, answer="No") +q = str("Is it better to have more or less " + symbol + " symbols in your text?") +q1 = QuizQuestion(type="radio", options=['More', 'Less'], sequence_number=1, answer="No", question=q) +q = str("" + symbol + "") +q2 = QuizQuestion(type="radio", options=['More', 'Less'], sequence_number=2, answer="Less", question=q) +q = str("When is the text clean?") +q3 = QuizQuestion(type="radio", options=['When there are no symbols in it.', 'It is always clean', 'When there are lots of symbols.'], sequence_number=3, answer="When there are no symbols in it", question=q) + +q = str("How often is more 'pollution' added in the form of "+symbol+" symbols?") +q4 = QuizQuestion(type="radio", options=['Randomly', growth_rate , 'When someone logs on'], sequence_number=4, answer="No", question=q) + +q = str("Does the amount of symbols added "+growth_rate+" determine how much 'pollution is added?'") +q5 = QuizQuestion(type="radio", options=['Yes', 'No'], sequence_number=5, answer="Yes", question=q) + +q = str("What do you do if there is no pollution to clean?") +q6 = QuizQuestion(type="radio", options=['You are done with the experiment', 'Come back later', 'Pollute the text'], sequence_number=6, answer="Come back later", question=q) + +q = str("How do you earn extra-credit points?") +q7 = QuizQuestion(type="radio", options=['Polluting the text', 'Keeping the text clean', 'Posting messages to my group'], sequence_number=7, answer="No", question=q) + + + + + +class SurveyQuestion(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) +s1 = SurveyQuestion(type="radio", options=['Male', 'Female'], sequence_number=1, size="", question="gender") +s2 = SurveyQuestion(type="text", options=['MM-DD-YYYY'], sequence_number=2, size="4", question="What year were you born?") +s3 = SurveyQuestion(type="text", options=" ", sequence_number=3, size="18", question="What is your expected Degree?") +s3 = SurveyQuestion(type="text", options=['MM-YYYY'], sequence_number=3, size="18", question="What is your expected Graduation Date?") +# view functions def configure(request, experiment_id=None): experiment = Experiment.objects.get(pk=experiment_id) return render_to_response('sanitation/configure.html', { @@ -21,31 +80,31 @@ def consent(request, experiment): logger.debug("handling consent") + consent = ['Introduction to Global Health Class', symbol,'one week', growth_rate] return render_to_response('sanitation/consent.html', locals(), context_instance=RequestContext(request)) def survey(request, experiment): logger.debug("handling survey") - return render_to_response('sanitation/survey.html', locals(), context_instance=RequestContext(request)) + return render_to_response('sanitation/survey.html', globals(), context_instance=RequestContext(request)) def quiz(request, experiment): - quiz_questions = [('q1', 'This is question #1?','text','answer1'), ('q2', 'This is question #2?','text','answer2')] logger.debug("handling quiz") - return render_to_response('sanitation/quiz.html', locals(), context_instance=RequestContext(request)) + return render_to_response('sanitation/quiz.html', globals(), context_instance=RequestContext(request)) def instructions(request, experiment): logger.debug("handling instructions") - return render_to_response('sanitation/instructions.html', locals(), context_instance=RequestContext(request)) + return render_to_response('sanitation/instructions.html', globals(), context_instance=RequestContext(request)) def play(request, experiment): + treatment = "In-group" logger.debug("handling play") - return render_to_response('sanitation/play.html', locals(), context_instance=RequestContext(request)) + return render_to_response('sanitation/play.html', globals(), context_instance=RequestContext(request)) def participate(request, experiment_id=None): # lookup participant's current location and then invoke the method named by the location participant = request.user.participant experiment = Experiment.objects.get(pk=experiment_id) # FIXME: this isn't implemented - current_location = "survey" # current_location = participant.current_location # "consent", "survey", "quiz", "play", "instructions" if current_location in ["consent", "survey", "quiz", "play", "instructions"]: logger.debug("current location %s is valid", current_location) http://bitbucket.org/virtualcommons/vcweb/changeset/9703e5247feb/ changeset: 9703e5247feb user: mhurtad date: 2011-06-10 01:41:40 summary: Sanitation experiment content migrated from original experiment. Templates added and modified for participant sequence. affected #: 15 files (4.9 KB) --- a/vcweb/core/fixtures/test_users_participants.json Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/fixtures/test_users_participants.json Thu Jun 09 16:41:40 2011 -0700 @@ -11,7 +11,7 @@ "pk": 1, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 2, "failed_password_attempts": 0, "institution": 1 @@ -21,7 +21,7 @@ "pk": 2, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 3, "failed_password_attempts": 0, "institution": 1 @@ -31,7 +31,7 @@ "pk": 3, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 4, "failed_password_attempts": 0, "institution": 1 @@ -41,7 +41,7 @@ "pk": 4, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 5, "failed_password_attempts": 0, "institution": 1 @@ -51,7 +51,7 @@ "pk": 5, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 6, "failed_password_attempts": 0, "institution": 1 @@ -61,7 +61,7 @@ "pk": 6, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 7, "failed_password_attempts": 0, "institution": 1 @@ -71,7 +71,7 @@ "pk": 7, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 8, "failed_password_attempts": 0, "institution": 1 @@ -81,7 +81,7 @@ "pk": 8, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 9, "failed_password_attempts": 0, "institution": 1 @@ -91,7 +91,7 @@ "pk": 9, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 10, "failed_password_attempts": 0, "institution": 1 @@ -101,7 +101,7 @@ "pk": 10, "model": "core.participant", "fields": { - "can_receive_invitations": true, + "can_receive_invitations": true, "current_location":0, "user": 11, "failed_password_attempts": 0, "institution": 1 --- a/vcweb/core/models.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/models.py Thu Jun 09 16:41:40 2011 -0700 @@ -266,12 +266,6 @@ self.current_round.sequence_label) @property - def participant_group_relationships(self): - for group in self.groups.all(): - for pgr in group.participant_group_relationships.all(): - yield pgr - - @property def namespace(self): return self.experiment_metadata.namespace @@ -321,8 +315,7 @@ @property def playable_round_data(self): - return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS, - round_configuration__sequence_number__lte=self.current_round_sequence_number) + return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS) @property def all_quiz_questions(self): @@ -442,7 +435,6 @@ def log(self, log_message): if log_message: - logger.debug("%s: %s", self, log_message) self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def data_file_name(self, file_ext='csv'): @@ -690,7 +682,7 @@ return Template(template_string).substitute(kwargs, round_number=self.display_number, participant_id=participant_id) def __unicode__(self): - return u"%s > %s %s" % (self.display_label, self.experiment_configuration, self.sequence_label) + return u"%s > %s" % (self.display_label, self.experiment_configuration) @property def display_label(self): @@ -829,7 +821,7 @@ return value def __unicode__(self): - return u"[name: %s, type:%s, scope:%s]" % (self.name, self.type, self.scope) + return u"%s (type:%s, scope:%s, experiment: %s)" % (self.name, self.type, self.scope, self.experiment_metadata) class Meta: ordering = ['name'] @@ -842,7 +834,6 @@ boolean_value = models.NullBooleanField(null=True, blank=True) date_created = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now=True) - is_active = models.BooleanField(default=True) @property def value(self): @@ -938,9 +929,32 @@ return self.activity_log.filter(round_configuration=self.current_round) def log(self, log_message): - if log_message: - logger.debug(log_message) - self.activity_log.create(round_configuration=self.current_round, log_message=log_message) + self.activity_log.create(round_configuration=self.current_round, + log_message=log_message) + + ''' + Initializes data parameters for all groups in this round, as necessary. + If this round already has data parameters, is a no-op. + def initialize_data_parameters(self): + if self.current_round.is_playable_round: + round_data = self.current_round_data + if round_data.group_data_values.filter(group=self).count() == 0: + logger.debug("no group data values for the current round %s, creating new ones.", round_data) + self.log("Initializing %s data parameters" % round_data) + for group_data_parameter in self.data_parameters: + self.data_values.create(round_data=round_data, parameter=group_data_parameter) + + ''' + + def set_data_value(self, parameter_name=None, parameter=None, value=None): + ''' + Not as efficient as a simple SQL update because we need to do some type + conversion / processing to put the value into the appropriate field. + ''' + data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) + data_value.value = value + self.log("setting parameter %s = %s" % (parameter, value)) + data_value.save() def subtract(self, parameter=None, amount=0): self.add(parameter, -amount) @@ -973,25 +987,11 @@ return self.get_data_value(parameter=parameter, parameter_name=parameter_name).value def get_data_value(self, parameter=None, parameter_name=None, round_data=None): - if round_data is None: - round_data = self.current_round_data criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) - try: - return self.data_values.get(**criteria) - except GroupRoundDataValue.DoesNotExist as e: - logger.warning("No data value found for criteria %s", criteria) - raise e - - def set_data_value(self, parameter_name=None, parameter=None, value=None): - ''' - Not as efficient as a simple SQL update because we need to do some type - conversion / processing to put the value into the appropriate field. - ''' - data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) - data_value.value = value - self.log("setting parameter %s = %s" % (parameter, value)) - data_value.save() - + data_value, created = self.data_values.get_or_create(**criteria) + if created: + logger.debug("Created new data value in get_data_value: %s", data_value) + return data_value def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None): return dict([ @@ -1029,14 +1029,10 @@ self.transfer_parameter(parameter, value) def transfer_parameter(self, parameter, value): - if self.experiment.is_last_round: - logger.error("Trying to transfer parameter (%s: %s) past the last round of the experiment", - parameter, value) - return None next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) - logger.debug("%s (%s)", group_data_value, created) + logger.debug("group data value %s (%s)", group_data_value, created) if not created: group_data_value.value = value group_data_value.save() @@ -1112,6 +1108,7 @@ class Participant(CommonsUser): + current_location = models.IntegerField(default=0) can_receive_invitations = models.BooleanField(default=False) groups = models.ManyToManyField(Group, through='ParticipantGroupRelationship', related_name='participants') experiments = models.ManyToManyField(Experiment, through='ParticipantExperimentRelationship', related_name='participants') --- a/vcweb/core/templates/contact.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/templates/contact.html Thu Jun 09 16:41:40 2011 -0700 @@ -9,7 +9,9 @@ Please feel free to send us feedback, suggestions, or comments via our <a class='external' href='http://commons.asu.edu/contact'>contact form</a>. <br/><br/> - We also maintain an issue tracker at <a class='external' href='https://bitbucket.org/virtualcommons/vcweb/issues'>BitBucket</a>. + We also maintain an issue tracker at <a class='external' href='https://bitbucket.org/virtualcommons/vcweb/issues'>BitBucket</a> + and <a class='external' href='http://sourceforge.net/apps/trac/virtualcommons/'>SourceForge</a> + (one of these will eventually be retired). </div></div> {% endblock page %} --- a/vcweb/core/templates/includes/participant.events.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/templates/includes/participant.events.html Thu Jun 09 16:41:40 2011 -0700 @@ -19,10 +19,10 @@ "message": payload }; } -function createChatEvent(payload) { - return createMessageEvent(payload); +function createChatEvent(message) { + return createMessageEvent(message); } -function createSubmitEvent(payload) { - return createMessageEvent(payload, "submit"); +function createSubmitEvent(message) { + return createMessageEvent(message, "submit"); } </script> --- a/vcweb/core/templates/includes/socket.io.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/templates/includes/socket.io.html Thu Jun 09 16:41:40 2011 -0700 @@ -4,7 +4,6 @@ </script><script type="text/javascript"> -var vcwebSocket = {}; var cachedSocket; var defaultPort = 8881; function connect(resource) { @@ -18,21 +17,17 @@ console.log("using default port: " + defaultPort); port = defaultPort; } - console.log("Establishing connection to " + host + ":" + port + "/" + resource); - cachedSocket = new io.Socket(host, {port: port, resource: resource}); + cachedSocket = new io.Socket(host, {"port": port, "resource": resource}); cachedSocket.connect(); - console.log("connect invoked to " + host + ":" + port + "/" + resource); cachedSocket.on('connect', function() { - console.debug("onconnect to " + host + ":" + port + "/" + resource); + console.log("Connecting to " + host + ":" + port + "/" + resource); cachedSocket.send(createConnectionEvent()); }); - /* cachedSocket.on('disconnect', function(data) { console.log("received disconnect, reconnecting."); cachedSocket.connect(); cachedSocket.send(createReconnectionEvent()); }); - */ return cachedSocket; } function getCachedSocket() { --- a/vcweb/core/templates/participant/base.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/templates/participant/base.html Thu Jun 09 16:41:40 2011 -0700 @@ -8,8 +8,8 @@ {% include "includes/socket.io.html" %} <script type='text/javascript'> var qtipOptions = {position: { corner: {target: 'topMiddle', tooltip: 'bottomMiddle'}}, style: { name: 'green', tip: 'bottomMiddle'} }; + connect('participant/{{participant_experiment_relationship.pk}}'); $(function() { - connect('participant/{{participant_experiment_relationship.pk}}'); $('[title]').qtip(qtipOptions); }); </script> --- a/vcweb/core/templates/participant/dashboard.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/core/templates/participant/dashboard.html Thu Jun 09 16:41:40 2011 -0700 @@ -9,17 +9,19 @@ {% endblock %} {% block page %} <div class='info infoIcon ui-corner-all'> - Welcome back, {{request.user.participant}}. Experiments you're participating in are listed below. + Welcome back, {{request.user.participant}}. <br> Experiments you're participating in are listed below. </div><div id='experiments'> {% for experiment_metadata, experiment_status_dict in experiments.items %} - <h3> {{ experiment_metadata.title }} </h3> +<!-- <h3> {{ experiment_metadata.title }} </h3> --> {% for experiment_status, experiments in experiment_status_dict.items %} {% if experiments %} - <h4>{{experiments.0.get_status_display}}</h4> + <h4>{{ experiment_metadata.title }}: {{experiments.0.get_status_display}}</h4> {% for experiment in experiments %} - <div style='padding: 10px;' class='ui-state-highlight'> - <a title='This experiment is being run by {{experiment.experimenter}}' href='{{ experiment.participant_url }}'><span class='icon-left ui-icon {{experiment_status|lower}}'></span>{{ experiment.status_line }}, started {{experiment.current_round_start_time}}</a> + <div style='padding: 2px;' class='ui-state-highlight'> +<!-- FIXME display correct time for end of experiment ---> +<!-- FIXME display the given name of the experiment, for example "Introduction to Global Health GH 101" ---> + <a title='This experiment is being run by {{experiment.experimenter}}' href='{{ experiment.participant_url }}'><span class='icon-left ui-icon {{experiment_status|lower}}'></span><u>{{ experiment.namespace }}</u><br> From {{experiment.current_round_start_time}} until {{experiment.current_round_start_time}}</a></div> {% endfor %} {% endif %} --- a/vcweb/fabfile.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/fabfile.py Thu Jun 09 16:41:40 2011 -0700 @@ -19,7 +19,7 @@ # default to current working directory env.project_path = os.path.dirname(__file__) env.hosts = ['localhost'] -env.hg_url = 'https://bitbucket.org/virtualcommons/vcweb' +env.hg_url = 'http://virtualcommons.hg.sourceforge.net:8000/hgroot/virtualcommons/virtualcommons' env.apache = 'httpd' env.applist = ['core', 'forestry'] env.apps = ' '.join(env.applist) @@ -30,7 +30,7 @@ """ this currently only works for sqlite3 development database. do it by hand with -postgres a few times to figure out what to automate. +postgres a few times to figure out what to automate, probably with south? """ syncdb_commands = ['(test -f vcweb.db && rm -f vcweb.db) || true', '%(python)s manage.py syncdb --noinput' % env, @@ -44,7 +44,7 @@ def syncdb(**kwargs): with cd(env.project_path): - _virtualenv(run, *syncdb_commands, **kwargs) + _virtualenv(*syncdb_commands, **kwargs) def setup_virtualenv(): @@ -71,14 +71,14 @@ # figure out what the appropriate rabbitmq perms are here. sudo('rabbitmqctl set_permissions -p %s %s ".*" ".*" ".*"' % (vcweb_settings.BROKER_VHOST, vcweb_settings.BROKER_USER), pty=True) -def _virtualenv(executor, *commands, **kwargs): +def _virtualenv(*commands, **kwargs): """ source the virtualenv before executing this command """ env.command = ' && '.join(commands) - return executor('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) + return local('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) def pip(): ''' looks for requirements.pip in the django project directory ''' - _virtualenv(run, 'pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) + _virtualenv('pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) #with cd(env.virtualenv_path): # sudo_chain('chgrp -R %(deploy_group)s .' % env, 'chmod -R g+rw' % env, pty=True) @@ -90,13 +90,13 @@ runs tests on this local codebase, not the deployed codebase ''' with cd(env.project_path): - _virtualenv(local, '%(python)s manage.py test %(apps)s' % env) + _virtualenv('%(python)s manage.py test %(apps)s' % env) def tornadio(ip="127.0.0.1", port=None): from vcweb import settings as vcweb_settings if port is None: port = vcweb_settings.SOCKET_IO_PORT - _virtualenv(local, "{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) + _virtualenv("{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) def server(ip="127.0.0.1", port=8000): local("{python} manage.py runserver {ip}:{port}".format(python=env.python, **locals()), capture=False) @@ -156,7 +156,7 @@ if confirm("syncdb?"): syncdb() env.static_root = vcweb_settings.STATIC_ROOT - _virtualenv(run,'%(python)s manage.py collectstatic' % env) + _virtualenv('%(python)s manage.py collectstatic' % env) sudo_chain('chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', --- a/vcweb/forestry/models.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/models.py Thu Jun 09 16:41:40 2011 -0700 @@ -11,7 +11,7 @@ check all forestry experiments. ''' -def get_resource_level(group, round_data=None): +def get_resource_level(group=None, round_data=None): ''' returns the group resource level data parameter ''' return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) @@ -44,12 +44,6 @@ def set_group_harvest(group, value): group.set_data_value(parameter=get_group_harvest_parameter(), value=value) -def should_reset_resource_level(round_configuration): - return round_configuration.get_parameter_value('reset.resource_level', default=False) - -def get_initial_resource_level(round_configuration): - return round_configuration.get_parameter_value('initial.resource_level', default=100) - def get_max_harvest_decision(resource_level): if resource_level >= 25: return 5 @@ -116,9 +110,8 @@ during a practice or regular round, set up resource levels and participant harvest decision parameters ''' - if should_reset_resource_level(round_configuration): - initial_resource_level = get_initial_resource_level(round_configuration) - logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) + if round_configuration.get_parameter_value('reset.resource_level', default=False): + initial_resource_level = round_configuration.get_parameter_value('initial.resource_level', default=100) for group in experiment.groups.all(): ''' set resource level to initial default ''' group.log("Setting resource level to initial value [%s]" % initial_resource_level) @@ -129,11 +122,9 @@ pass def round_teardown(experiment, **kwargs): - ''' - 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. - ''' - logger.debug(experiment) + ''' round teardown calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth and transferring''' + logger.debug("%s", experiment) + resource_level_parameter = get_resource_level_parameter() current_round_configuration = experiment.current_round max_resource_level = 100 for group in experiment.groups.all(): @@ -157,7 +148,7 @@ if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % get_resource_level(group)) - group.transfer_parameter(current_resource_level.parameter, current_resource_level.value) + group.transfer_to_next_round(resource_level_parameter) ''' FIXME: figure out a better way to tie these signal handlers to a specific --- a/vcweb/forestry/templates/forestry/chat.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/templates/forestry/chat.html Thu Jun 09 16:41:40 2011 -0700 @@ -4,6 +4,7 @@ <script type="text/javascript"> $(function() { var chatDiv = document.getElementById('chat-div'); + // var s = connect(window.location.hostname, 8888, 'participant/{{participant_experiment_relationship.pk}}'); var s = getCachedSocket(); s.on('message', function(json_string) { var json = jQuery.parseJSON(json_string); @@ -44,20 +45,16 @@ </script> {% endblock %} {% block title %} -Communication Round: {{experiment}} +Forestry Chat Round {% endblock %} {% block page %} -<h3 style='margin-top: -30px;'>Communication Round Instructions</h3> - <div class='alert alertIcon ui-corner-all' style='font-size: 0.8em; line-height: 1.0em;'> - You are <b>participant {{ participant_group_relationship.participant_number }}.</b> - You can chat about any aspect of the experiment with the other members of your group with two restrictions: - <ol style='margin-left: 15px;margin-bottom: 0;'> - <li>You cannot promise side payments after the experiment is completed - <li>You cannot threaten any member of your group with any consequences after the experiment is completed - </ol> - We are monitoring the chat traffic and if we detect any violation - of these rules you will be removed from the experiment. +<h3>Chat</h3> + <div class='info ui-corner-all'> + <span class='icon-left ui-icon ui-icon-info'></span> + <b> + Hello, {{participant.full_name}}. + You are participant {{ participant_group_relationship.participant_number }} in {{participant_group_relationship.group}}. </b></div><div id='chat-div' class='chat notice ui-corner-all'> --- a/vcweb/forestry/templates/forestry/participate.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/templates/forestry/participate.html Thu Jun 09 16:41:40 2011 -0700 @@ -1,7 +1,6 @@ {% extends "participant/base.html" %} {% block head %} {{ block.super }} -{% comment %}{% include "includes/jquery.confirm.html" %} {%endcomment%} <script type="text/javascript"> $(function() { $("#forestry-form").validate({ @@ -33,26 +32,11 @@ } }); $('#forestry-form').submit(function() { - var harvestDecisionValue = $('#harvest-decision-id').val(); - // FIXME: this isn't getting sent through properly before the submit occurs. - // Should work out communicating directly from Django to the tornadio process. + $(this).attr('disabled', true); + harvestDecisionValue = $('#harvest-decision-id').val(); s.send(createSubmitEvent(harvestDecisionValue)); return true; }); - /* - $('#submit').click(function() { - $(this).fastConfirm({ - questionText: "Submit your harvest decision of " + harvestDecisionValue + "?", - onProceed: function(trigger) { - $('#forestry-form').submit(); - $(trigger).fastConfirm('close'); - }, - onCancel: function(trigger) { - $(trigger).fastConfirm('close'); - } - }); - }); - */ }); </script> {% endblock %} @@ -96,7 +80,7 @@ <input id='harvest-decision-id' style='width: 2em; text-align: right;' type="text" name='harvest_decision' class='required digits' maxlength='1'/></div><div> - <button id='submit' type='submit'>Harvest</button> + <button type='submit'>Harvest</button></div></form> {% else %} --- a/vcweb/forestry/templates/forestry/wait.html Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/templates/forestry/wait.html Thu Jun 09 16:41:40 2011 -0700 @@ -1,12 +1,15 @@ {% extends "participant/base.html" %} {% block head %} {{ block.super }} +{% include "includes/participant.events.html" %} +{% include "includes/socket.io.html" %} {% include "includes/tablesorter.html" %} <script type="text/javascript"> $(function() { - $('#participant-history').tablesorter({widgets: ['zebra'], - headers: { 0: {sorter: false}, 1: {sorter: false} } - }); + $('#participant-history').tablesorter({widgets: ['zebra'], + headers: { 0: {sorter: false}, 1: {sorter: false} } + }); + $('[title]').qtip({position: { corner: {target: 'topMiddle', tooltip: 'bottomMiddle'}}, style: { name: 'green', tip: 'bottomMiddle'} }); var s = getCachedSocket(); s.on('message', function(json_string) { var json = jQuery.parseJSON(json_string); @@ -33,11 +36,14 @@ {% block page %} <h3>Waiting for the next round to start</h3><p> -Please wait, {{request.user.participant}}. The facilitator will start the next round when every participant is ready. -</p> +The facilitator will start the next round when every participant is ready. +<br/><div class='ui-state-highlight ui-corner-all'><small><span class='float-left vcweb-ui-icon ui-icon ui-icon-circle-arrow-w'></span><a href='participate'>return to participation page</a></small></div> +<br/> +</p> + <h3>Participant History</h3><div id='group-history'> {% if participant_history %} @@ -83,4 +89,11 @@ {% endif %} </div> +<h3>Instructions</h3> +<div class='info ui-corner-all'> + Please wait until the next round begins. + {% comment%} + add ajax spinner and polling + {% endcomment %} +</div> {% endblock %} --- a/vcweb/forestry/tests.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/tests.py Thu Jun 09 16:41:40 2011 -0700 @@ -2,7 +2,11 @@ ParticipantRoundDataValue, GroupRoundDataValue, ParticipantExperimentRelationship, ParticipantGroupRelationship) from vcweb.core.tests import BaseVcwebTest -from vcweb.forestry.models import * +from vcweb.forestry.models import (get_group_harvest_parameter, + get_regrowth_parameter, round_setup, round_teardown, get_resource_level, + set_resource_level, set_harvest_decision, get_harvest_decision_parameter, + get_harvest_decisions, forestry_sender, get_forestry_experiment_metadata, + get_resource_level_parameter) import logging logger = logging.getLogger(__name__) @@ -85,43 +89,12 @@ e.current_round_sequence_number = rc.sequence_number self.assertEqual(e.current_round_template, 'forestry/quiz.html', 'should return default quiz.html') -class TransferParametersTest(BaseVcwebTest): - def test_transfer_parameters(self): - def calculate_expected_resource_level(resource_level, harvested): - after_harvest = max(resource_level - harvested, 0) - return min(100, int(after_harvest + (after_harvest * .10))) - e = self.advance_to_data_round() - expected_resource_level = 100 - while True: - e.start_round() - current_round_configuration = e.current_round - if should_reset_resource_level(current_round_configuration): - logger.debug("resetting resource level for round %s, %d", current_round_configuration, - e.current_round_sequence_number) - expected_resource_level = get_initial_resource_level(current_round_configuration) +''' +FIXME: several of these can and should be lifted to core/tests.py +''' +class ForestryParametersTest(BaseVcwebTest): - max_harvest_decision = get_max_harvest_decision(expected_resource_level) - for pgr in e.participant_group_relationships: - self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) - set_harvest_decision(pgr, max_harvest_decision) - e.end_round() - - if current_round_configuration.is_playable_round: - expected_resource_level = calculate_expected_resource_level(expected_resource_level, max_harvest_decision * 5) - - for group in e.groups.all(): - self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) - - if e.has_next_round: - e.advance_to_next_round() - else: - break - -class ForestryParametersTest(BaseVcwebTest): - ''' - FIXME: several of these can and should be lifted to core/tests.py - ''' def test_initialize_parameters_at_round_start(self): e = self.advance_to_data_round() e.start_round() @@ -192,13 +165,15 @@ for func in caching_funcs: verify_refreshed_data(func) + + def test_get_set_resource_level(self): e = self.advance_to_data_round() - e.start_round() + for group in e.groups.all(): resource_level = get_resource_level(group) self.assertTrue(resource_level.pk > 0) - self.assertEqual(resource_level.value, 100) + self.assertFalse(resource_level.value) resource_level.value = 3 resource_level.save() --- a/vcweb/forestry/views.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/forestry/views.py Thu Jun 09 16:41:40 2011 -0700 @@ -114,11 +114,10 @@ class ParticipateView(SingleObjectTemplateResponseMixin, ParticipantSingleExperimentMixin, View): template_name_field = 'current_round_template' -# FIXME: refactor the conditional logic here. +# FIXME: refactor this ugliness @participant_required def participate(request, experiment_id=None): participant = request.user.participant - logger.debug("handling participate request for %s and experiment %s", participant, experiment_id) try: experiment = Experiment.objects.get(pk=experiment_id) current_round = experiment.current_round @@ -191,14 +190,13 @@ }, context_instance=RequestContext(request)) - -# FIXME: figure out the appropriate place for this trees = { 'deciduous': { 'name': 'deciduous-tree', 'height': 32 }, 'pine': {'name': 'pine-tree', 'height': 79 }, } + + def play(request, experiment, participant): - logger.debug("handling play request for participant %s and experiment %s", participant, experiment) form = HarvestDecisionForm(request.POST or None) participant_group_relationship = participant.get_participant_group_relationship(experiment) participant_experiment_relationship = participant.get_participant_experiment_relationship(experiment) --- a/vcweb/vcwebio.py Thu Jun 09 16:20:21 2011 -0700 +++ b/vcweb/vcwebio.py Thu Jun 09 16:41:40 2011 -0700 @@ -12,7 +12,6 @@ from vcweb.core.models import ParticipantExperimentRelationship, ParticipantGroupRelationship, ChatMessage, Experimenter, Experiment from vcweb import settings -# FIXME: currently tornadio.vcweb to avoid confusion with vcweb loggers logger = logging.getLogger('tornadio.vcweb') def info_json(message): @@ -33,37 +32,29 @@ ''' Manages socket.io connections to tornadio. ''' - # bidi maps for (participant.pk, experiment.pk) -> SocketConnection + # bidi maps for (participant.pk, experiment.pk) -> connection connection_to_participant = {} participant_to_connection = {} - # bidi maps for (experimenter.pk, experiment.pk) -> SocketConnection + # bidi maps for (experimenter.pk, experiment.pk) -> connection connection_to_experimenter = {} experimenter_to_connection = {} - ''' - We use participant_pk + experiment_pk tuples as keys in these bidimaps because - groups may not have formed yet. - FIXME: consider refactoring core so that an "all" group always exists in an - experiment. - ''' refresh_json = simplejson.dumps({ 'message_type': 'refresh' }) def add_experimenter(self, connection, incoming_experimenter_pk, incoming_experiment_pk): - logger.debug("experimenter_to_connection: %s", self.experimenter_to_connection) - logger.debug("connection_to_experimenter: %s", self.connection_to_experimenter) experimenter_pk = int(incoming_experimenter_pk) experiment_id = int(incoming_experiment_pk) experimenter_tuple = (experimenter_pk, experiment_id) logger.debug("registering experimenter %s with connection %s", experimenter_pk, connection) if connection in self.connection_to_experimenter: - logger.debug("this experimenter has an existing connection (%s <-> %s) ", - self.connection_to_experimenter[connection], experimenter_tuple) + logger.debug("experimenter already registered, removing previous mapping") + self.remove_experimenter(connection) self.connection_to_experimenter[connection] = experimenter_tuple self.experimenter_to_connection[experimenter_tuple] = connection def remove_experimenter(self, connection): if connection in self.connection_to_experimenter: experimenter_tuple = self.connection_to_experimenter[connection] - logger.debug("removing experimenter %s", experimenter_tuple) + logger.debug("removing experimenter %s", experimenter_tuple[0]) del self.connection_to_experimenter[connection] if experimenter_tuple in self.experimenter_to_connection: del self.experimenter_to_connection[experimenter_tuple] @@ -73,15 +64,23 @@ (participant_pk, experiment_pk) = self.connection_to_participant[connection] logger.debug("Looking for ParticipantGroupRelationship with tuple (%s, %s)", participant_pk, experiment_pk) return ParticipantGroupRelationship.objects.get(participant__pk=participant_pk, group__experiment__pk = experiment_pk) - logger.warning("Couldn't find a participant group relationship using connection %s in connection map %s", connection, self.connection_to_participant) + logger.debug("Didn't find connection %s in connection map %s.", connection, self.connection_to_participant) return None + def get_experiment(self, connection): + if connection in self.connection_to_participant: + experiment_pk = self.connection_to_participant[connection][1] + return Experiment.objects.get(pk=experiment_pk) + else: + return None + def get_participant_experiment_tuple(self, connection): - return self.connection_to_participant[connection] + if connection in self.connection_to_participant: + return self.connection_to_participant[connection] + else: + return None def add_participant(self, auth_token, connection, participant_experiment_relationship): - logger.debug("connection to participant: %s", self.connection_to_participant) - logger.debug("participant to connection: %s", self.participant_to_connection) participant_tuple = (participant_experiment_relationship.participant.pk, participant_experiment_relationship.experiment.pk) if participant_tuple in self.participant_to_connection: logger.debug("participant already has a connection, removing previous mappings.") @@ -198,7 +197,6 @@ logger.debug("sending all connected participants %s to %s", notified_participants, url) def on_close(self): - logger.debug("removing experimenter connection %s", self) connection_manager.remove_experimenter(self) class ParticipantHandler(SocketConnection): @@ -243,7 +241,6 @@ elif event.message_type == 'submit': (participant_pk, experiment_pk) = connection_manager.get_participant_experiment_tuple(self) experiment = Experiment.objects.get(pk=experiment_pk) - logger.debug("processing participant submission for participant %s and experiment %s", participant_pk, experiment) # sanity check, make sure this is a data round. if experiment.is_data_round_in_progress: experimenter_tuple = (experiment.experimenter.pk, experiment.pk) @@ -255,7 +252,7 @@ event.participant_number = participant_group_relationship.participant_number event.participant_group = participant_group_relationship.group_number json = simplejson.dumps(event.__dict__) - logger.debug("submit event json: %s", json) + logger.debug("json is: %s", json) connection_manager.send_to_experimenter(experimenter_tuple, json) if experiment.all_participants_have_submitted: connection_manager.send_to_experimenter( 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: Bitbucket <iss...@bi...> - 2011-06-08 15:50:02
|
--- you can reply above this line --- New issue 19: pretest issues https://bitbucket.org/virtualcommons/vcweb/issue/19/pretest-issues A Lee / alllee on Wed, 8 Jun 2011 17:49:56 +0200: Description: # during the chat, some participants identity has been stolen, meaning that one participant was for a while number 4 and then after while s/he was already number 1, also sometimes there were 3 people with number 5 .... # the chat was not working properly, meaning some participants can chat all the time, others only sometimes # problem with harvesting, it looked that some students already harvested (actually the did not) # one student noticed that although he harvested always 5 or 0, in the table he had harvesting of 2 or 3 # the problems with calculation (the table ive sent you) # they have to refresh the page sometimes, in order to move on # one group noticed that the resource is gone, although round before the forest was still quite ok Responsible: alllee -- This is an issue notification from bitbucket.org. You are receiving this either because you are the owner of the issue, or you are following the issue. |
From: Bitbucket <com...@bi...> - 2011-06-08 00:05:13
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/0a4fb82a06ff/ changeset: 0a4fb82a06ff user: alllee date: 2011-06-08 01:52:48 summary: Adding test for issue 17, still unable to reproduce. affected #: 3 files (268 bytes) --- a/vcweb/core/models.py Tue Jun 07 16:10:33 2011 -0700 +++ b/vcweb/core/models.py Tue Jun 07 16:52:48 2011 -0700 @@ -442,7 +442,7 @@ def log(self, log_message): if log_message: - logger.debug(log_message) + logger.debug("%s: %s", self, log_message) self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def data_file_name(self, file_ext='csv'): @@ -690,7 +690,7 @@ return Template(template_string).substitute(kwargs, round_number=self.display_number, participant_id=participant_id) def __unicode__(self): - return u"%s > %s" % (self.display_label, self.experiment_configuration) + return u"%s > %s %s" % (self.display_label, self.experiment_configuration, self.sequence_label) @property def display_label(self): --- a/vcweb/forestry/models.py Tue Jun 07 16:10:33 2011 -0700 +++ b/vcweb/forestry/models.py Tue Jun 07 16:52:48 2011 -0700 @@ -118,6 +118,7 @@ ''' if should_reset_resource_level(round_configuration): initial_resource_level = get_initial_resource_level(round_configuration) + logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) for group in experiment.groups.all(): ''' set resource level to initial default ''' group.log("Setting resource level to initial value [%s]" % initial_resource_level) --- a/vcweb/forestry/tests.py Tue Jun 07 16:10:33 2011 -0700 +++ b/vcweb/forestry/tests.py Tue Jun 07 16:52:48 2011 -0700 @@ -2,12 +2,7 @@ ParticipantRoundDataValue, GroupRoundDataValue, ParticipantExperimentRelationship, ParticipantGroupRelationship) from vcweb.core.tests import BaseVcwebTest -from vcweb.forestry.models import (get_group_harvest_parameter, - get_regrowth_parameter, round_setup, round_teardown, get_resource_level, - set_resource_level, set_harvest_decision, get_harvest_decision_parameter, - get_harvest_decisions, forestry_sender, get_forestry_experiment_metadata, - get_resource_level_parameter, should_reset_resource_level, - get_initial_resource_level) +from vcweb.forestry.models import * import logging logger = logging.getLogger(__name__) @@ -90,11 +85,7 @@ e.current_round_sequence_number = rc.sequence_number self.assertEqual(e.current_round_template, 'forestry/quiz.html', 'should return default quiz.html') -''' -FIXME: several of these can and should be lifted to core/tests.py -''' -class ForestryParametersTest(BaseVcwebTest): - +class TransferParametersTest(BaseVcwebTest): def test_transfer_parameters(self): def calculate_expected_resource_level(resource_level, harvested): after_harvest = max(resource_level - harvested, 0) @@ -102,20 +93,35 @@ e = self.advance_to_data_round() expected_resource_level = 100 - while (e.has_next_round): + while True: e.start_round() current_round_configuration = e.current_round if should_reset_resource_level(current_round_configuration): + logger.debug("resetting resource level for round %s, %d", current_round_configuration, + e.current_round_sequence_number) expected_resource_level = get_initial_resource_level(current_round_configuration) + + max_harvest_decision = get_max_harvest_decision(expected_resource_level) for pgr in e.participant_group_relationships: self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) - set_harvest_decision(pgr, 5) + set_harvest_decision(pgr, max_harvest_decision) e.end_round() - resource_level = calculate_expected_resource_level(expected_resource_level, 25) + + if current_round_configuration.is_playable_round: + expected_resource_level = calculate_expected_resource_level(expected_resource_level, max_harvest_decision * 5) + for group in e.groups.all(): - self.assertEquals(get_resource_level(pgr.group).value, resource_level) - e.advance_to_next_round() + self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) + if e.has_next_round: + e.advance_to_next_round() + else: + break + +class ForestryParametersTest(BaseVcwebTest): + ''' + FIXME: several of these can and should be lifted to core/tests.py + ''' def test_initialize_parameters_at_round_start(self): e = self.advance_to_data_round() e.start_round() http://bitbucket.org/virtualcommons/vcweb/changeset/f19f88e42fb4/ changeset: f19f88e42fb4 user: alllee date: 2011-06-08 02:05:12 summary: phasing out transfer_to_next_round on forestry round_teardown, fixes issue 17 affected #: 2 files (236 bytes) --- a/vcweb/core/models.py Tue Jun 07 16:52:48 2011 -0700 +++ b/vcweb/core/models.py Tue Jun 07 17:05:12 2011 -0700 @@ -1029,6 +1029,10 @@ self.transfer_parameter(parameter, value) def transfer_parameter(self, parameter, value): + if self.experiment.is_last_round: + logger.error("Trying to transfer parameter (%s: %s) past the last round of the experiment", + parameter, value) + return None next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) --- a/vcweb/forestry/models.py Tue Jun 07 16:52:48 2011 -0700 +++ b/vcweb/forestry/models.py Tue Jun 07 17:05:12 2011 -0700 @@ -129,9 +129,11 @@ pass def round_teardown(experiment, **kwargs): - ''' round teardown calculates new resource levels for practice or regular rounds based on the group harvest and resultant regrowth and transferring''' - logger.debug("%s", experiment) - resource_level_parameter = get_resource_level_parameter() + ''' + 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. + ''' + logger.debug(experiment) current_round_configuration = experiment.current_round max_resource_level = 100 for group in experiment.groups.all(): @@ -155,7 +157,7 @@ if experiment.has_next_round: ''' set group round data resource_level for each group + regrowth ''' group.log("Transferring resource level %s to next round" % get_resource_level(group)) - group.transfer_to_next_round(resource_level_parameter) + group.transfer_parameter(current_resource_level.parameter, current_resource_level.value) ''' FIXME: figure out a better way to tie these signal handlers to a specific 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: Bitbucket <com...@bi...> - 2011-06-07 23:10:42
|
2 new changesets in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/e9119c40aac9/ changeset: e9119c40aac9 user: alllee date: 2011-06-08 01:10:21 summary: bugfixes for duplicate entities - this was caused by generate_participant_history which can issue a get_or_create. Changed Group.get_data_value to only get, never get_or_create. fixes issue 13 affected #: 4 files (1.7 KB) --- a/vcweb/core/models.py Tue Jun 07 01:09:20 2011 -0700 +++ b/vcweb/core/models.py Tue Jun 07 16:10:21 2011 -0700 @@ -266,6 +266,12 @@ self.current_round.sequence_label) @property + def participant_group_relationships(self): + for group in self.groups.all(): + for pgr in group.participant_group_relationships.all(): + yield pgr + + @property def namespace(self): return self.experiment_metadata.namespace @@ -315,7 +321,8 @@ @property def playable_round_data(self): - return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS) + return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS, + round_configuration__sequence_number__lte=self.current_round_sequence_number) @property def all_quiz_questions(self): @@ -435,6 +442,7 @@ def log(self, log_message): if log_message: + logger.debug(log_message) self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def data_file_name(self, file_ext='csv'): @@ -821,7 +829,7 @@ return value def __unicode__(self): - return u"%s (type:%s, scope:%s, experiment: %s)" % (self.name, self.type, self.scope, self.experiment_metadata) + return u"[name: %s, type:%s, scope:%s]" % (self.name, self.type, self.scope) class Meta: ordering = ['name'] @@ -930,32 +938,9 @@ return self.activity_log.filter(round_configuration=self.current_round) def log(self, log_message): - self.activity_log.create(round_configuration=self.current_round, - log_message=log_message) - - ''' - Initializes data parameters for all groups in this round, as necessary. - If this round already has data parameters, is a no-op. - def initialize_data_parameters(self): - if self.current_round.is_playable_round: - round_data = self.current_round_data - if round_data.group_data_values.filter(group=self).count() == 0: - logger.debug("no group data values for the current round %s, creating new ones.", round_data) - self.log("Initializing %s data parameters" % round_data) - for group_data_parameter in self.data_parameters: - self.data_values.create(round_data=round_data, parameter=group_data_parameter) - - ''' - - def set_data_value(self, parameter_name=None, parameter=None, value=None): - ''' - Not as efficient as a simple SQL update because we need to do some type - conversion / processing to put the value into the appropriate field. - ''' - data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) - data_value.value = value - self.log("setting parameter %s = %s" % (parameter, value)) - data_value.save() + if log_message: + logger.debug(log_message) + self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def subtract(self, parameter=None, amount=0): self.add(parameter, -amount) @@ -988,11 +973,25 @@ return self.get_data_value(parameter=parameter, parameter_name=parameter_name).value def get_data_value(self, parameter=None, parameter_name=None, round_data=None): + if round_data is None: + round_data = self.current_round_data criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) - data_value, created = self.data_values.get_or_create(**criteria) - if created: - logger.debug("Created new data value in get_data_value: %s", data_value) - return data_value + try: + return self.data_values.get(**criteria) + except GroupRoundDataValue.DoesNotExist as e: + logger.warning("No data value found for criteria %s", criteria) + raise e + + def set_data_value(self, parameter_name=None, parameter=None, value=None): + ''' + Not as efficient as a simple SQL update because we need to do some type + conversion / processing to put the value into the appropriate field. + ''' + data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) + data_value.value = value + self.log("setting parameter %s = %s" % (parameter, value)) + data_value.save() + def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None): return dict([ @@ -1033,7 +1032,7 @@ next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) - logger.debug("group data value %s (%s)", group_data_value, created) + logger.debug("%s (%s)", group_data_value, created) if not created: group_data_value.value = value group_data_value.save() --- a/vcweb/fabfile.py Tue Jun 07 01:09:20 2011 -0700 +++ b/vcweb/fabfile.py Tue Jun 07 16:10:21 2011 -0700 @@ -30,7 +30,7 @@ """ this currently only works for sqlite3 development database. do it by hand with -postgres a few times to figure out what to automate, probably with south? +postgres a few times to figure out what to automate. """ syncdb_commands = ['(test -f vcweb.db && rm -f vcweb.db) || true', '%(python)s manage.py syncdb --noinput' % env, @@ -44,7 +44,7 @@ def syncdb(**kwargs): with cd(env.project_path): - _virtualenv(*syncdb_commands, **kwargs) + _virtualenv(run, *syncdb_commands, **kwargs) def setup_virtualenv(): @@ -71,14 +71,14 @@ # figure out what the appropriate rabbitmq perms are here. sudo('rabbitmqctl set_permissions -p %s %s ".*" ".*" ".*"' % (vcweb_settings.BROKER_VHOST, vcweb_settings.BROKER_USER), pty=True) -def _virtualenv(*commands, **kwargs): +def _virtualenv(executor, *commands, **kwargs): """ source the virtualenv before executing this command """ env.command = ' && '.join(commands) - return run('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) + return executor('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) def pip(): ''' looks for requirements.pip in the django project directory ''' - _virtualenv('pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) + _virtualenv(run, 'pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) #with cd(env.virtualenv_path): # sudo_chain('chgrp -R %(deploy_group)s .' % env, 'chmod -R g+rw' % env, pty=True) @@ -90,15 +90,15 @@ runs tests on this local codebase, not the deployed codebase ''' with cd(env.project_path): - _virtualenv('%(python)s manage.py test %(apps)s' % env) + _virtualenv(local, '%(python)s manage.py test %(apps)s' % env) def tornadio(ip="127.0.0.1", port=None): from vcweb import settings as vcweb_settings if port is None: port = vcweb_settings.SOCKET_IO_PORT - _virtualenv("{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) + _virtualenv(local, "{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) -def server(ip="149.169.203.115", port=8000): +def server(ip="127.0.0.1", port=8000): local("{python} manage.py runserver {ip}:{port}".format(python=env.python, **locals()), capture=False) def celeryd(): @@ -156,7 +156,7 @@ if confirm("syncdb?"): syncdb() env.static_root = vcweb_settings.STATIC_ROOT - _virtualenv('%(python)s manage.py collectstatic' % env) + _virtualenv(run,'%(python)s manage.py collectstatic' % env) sudo_chain('chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', --- a/vcweb/forestry/models.py Tue Jun 07 01:09:20 2011 -0700 +++ b/vcweb/forestry/models.py Tue Jun 07 16:10:21 2011 -0700 @@ -11,7 +11,7 @@ check all forestry experiments. ''' -def get_resource_level(group=None, round_data=None): +def get_resource_level(group, round_data=None): ''' returns the group resource level data parameter ''' return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) @@ -44,6 +44,12 @@ def set_group_harvest(group, value): group.set_data_value(parameter=get_group_harvest_parameter(), value=value) +def should_reset_resource_level(round_configuration): + return round_configuration.get_parameter_value('reset.resource_level', default=False) + +def get_initial_resource_level(round_configuration): + return round_configuration.get_parameter_value('initial.resource_level', default=100) + def get_max_harvest_decision(resource_level): if resource_level >= 25: return 5 @@ -110,8 +116,8 @@ during a practice or regular round, set up resource levels and participant harvest decision parameters ''' - if round_configuration.get_parameter_value('reset.resource_level', default=False): - initial_resource_level = round_configuration.get_parameter_value('initial.resource_level', default=100) + if should_reset_resource_level(round_configuration): + initial_resource_level = get_initial_resource_level(round_configuration) for group in experiment.groups.all(): ''' set resource level to initial default ''' group.log("Setting resource level to initial value [%s]" % initial_resource_level) --- a/vcweb/forestry/tests.py Tue Jun 07 01:09:20 2011 -0700 +++ b/vcweb/forestry/tests.py Tue Jun 07 16:10:21 2011 -0700 @@ -6,7 +6,8 @@ get_regrowth_parameter, round_setup, round_teardown, get_resource_level, set_resource_level, set_harvest_decision, get_harvest_decision_parameter, get_harvest_decisions, forestry_sender, get_forestry_experiment_metadata, - get_resource_level_parameter) + get_resource_level_parameter, should_reset_resource_level, + get_initial_resource_level) import logging logger = logging.getLogger(__name__) @@ -89,12 +90,32 @@ e.current_round_sequence_number = rc.sequence_number self.assertEqual(e.current_round_template, 'forestry/quiz.html', 'should return default quiz.html') - ''' FIXME: several of these can and should be lifted to core/tests.py ''' class ForestryParametersTest(BaseVcwebTest): + def test_transfer_parameters(self): + def calculate_expected_resource_level(resource_level, harvested): + after_harvest = max(resource_level - harvested, 0) + return min(100, int(after_harvest + (after_harvest * .10))) + + e = self.advance_to_data_round() + expected_resource_level = 100 + while (e.has_next_round): + e.start_round() + current_round_configuration = e.current_round + if should_reset_resource_level(current_round_configuration): + expected_resource_level = get_initial_resource_level(current_round_configuration) + for pgr in e.participant_group_relationships: + self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) + set_harvest_decision(pgr, 5) + e.end_round() + resource_level = calculate_expected_resource_level(expected_resource_level, 25) + for group in e.groups.all(): + self.assertEquals(get_resource_level(pgr.group).value, resource_level) + e.advance_to_next_round() + def test_initialize_parameters_at_round_start(self): e = self.advance_to_data_round() e.start_round() @@ -165,15 +186,13 @@ for func in caching_funcs: verify_refreshed_data(func) - - def test_get_set_resource_level(self): e = self.advance_to_data_round() - + e.start_round() for group in e.groups.all(): resource_level = get_resource_level(group) self.assertTrue(resource_level.pk > 0) - self.assertFalse(resource_level.value) + self.assertEqual(resource_level.value, 100) resource_level.value = 3 resource_level.save() http://bitbucket.org/virtualcommons/vcweb/changeset/52720d5d60db/ changeset: 52720d5d60db user: alllee date: 2011-06-08 01:10:33 summary: Automated merge with ssh://bitbucket.org/virtualcommons/vcweb affected #: 4 files (1.7 KB) --- a/vcweb/core/models.py Tue Jun 07 15:48:26 2011 -0700 +++ b/vcweb/core/models.py Tue Jun 07 16:10:33 2011 -0700 @@ -266,6 +266,12 @@ self.current_round.sequence_label) @property + def participant_group_relationships(self): + for group in self.groups.all(): + for pgr in group.participant_group_relationships.all(): + yield pgr + + @property def namespace(self): return self.experiment_metadata.namespace @@ -315,7 +321,8 @@ @property def playable_round_data(self): - return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS) + return self.round_data.select_related(depth=1).filter(round_configuration__round_type__in=RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS, + round_configuration__sequence_number__lte=self.current_round_sequence_number) @property def all_quiz_questions(self): @@ -435,6 +442,7 @@ def log(self, log_message): if log_message: + logger.debug(log_message) self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def data_file_name(self, file_ext='csv'): @@ -821,7 +829,7 @@ return value def __unicode__(self): - return u"%s (type:%s, scope:%s, experiment: %s)" % (self.name, self.type, self.scope, self.experiment_metadata) + return u"[name: %s, type:%s, scope:%s]" % (self.name, self.type, self.scope) class Meta: ordering = ['name'] @@ -930,32 +938,9 @@ return self.activity_log.filter(round_configuration=self.current_round) def log(self, log_message): - self.activity_log.create(round_configuration=self.current_round, - log_message=log_message) - - ''' - Initializes data parameters for all groups in this round, as necessary. - If this round already has data parameters, is a no-op. - def initialize_data_parameters(self): - if self.current_round.is_playable_round: - round_data = self.current_round_data - if round_data.group_data_values.filter(group=self).count() == 0: - logger.debug("no group data values for the current round %s, creating new ones.", round_data) - self.log("Initializing %s data parameters" % round_data) - for group_data_parameter in self.data_parameters: - self.data_values.create(round_data=round_data, parameter=group_data_parameter) - - ''' - - def set_data_value(self, parameter_name=None, parameter=None, value=None): - ''' - Not as efficient as a simple SQL update because we need to do some type - conversion / processing to put the value into the appropriate field. - ''' - data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) - data_value.value = value - self.log("setting parameter %s = %s" % (parameter, value)) - data_value.save() + if log_message: + logger.debug(log_message) + self.activity_log.create(round_configuration=self.current_round, log_message=log_message) def subtract(self, parameter=None, amount=0): self.add(parameter, -amount) @@ -988,11 +973,25 @@ return self.get_data_value(parameter=parameter, parameter_name=parameter_name).value def get_data_value(self, parameter=None, parameter_name=None, round_data=None): + if round_data is None: + round_data = self.current_round_data criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) - data_value, created = self.data_values.get_or_create(**criteria) - if created: - logger.debug("Created new data value in get_data_value: %s", data_value) - return data_value + try: + return self.data_values.get(**criteria) + except GroupRoundDataValue.DoesNotExist as e: + logger.warning("No data value found for criteria %s", criteria) + raise e + + def set_data_value(self, parameter_name=None, parameter=None, value=None): + ''' + Not as efficient as a simple SQL update because we need to do some type + conversion / processing to put the value into the appropriate field. + ''' + data_value = self.get_data_value(parameter_name=parameter_name, parameter=parameter) + data_value.value = value + self.log("setting parameter %s = %s" % (parameter, value)) + data_value.save() + def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None): return dict([ @@ -1033,7 +1032,7 @@ next_round_data, created = self.experiment.round_data.get_or_create(round_configuration=self.experiment.next_round) logger.debug("next round data: %s (%s)", next_round_data, created) group_data_value, created = next_round_data.group_data_values.get_or_create(group=self, parameter=parameter, defaults={'value': value}) - logger.debug("group data value %s (%s)", group_data_value, created) + logger.debug("%s (%s)", group_data_value, created) if not created: group_data_value.value = value group_data_value.save() --- a/vcweb/fabfile.py Tue Jun 07 15:48:26 2011 -0700 +++ b/vcweb/fabfile.py Tue Jun 07 16:10:33 2011 -0700 @@ -30,7 +30,7 @@ """ this currently only works for sqlite3 development database. do it by hand with -postgres a few times to figure out what to automate, probably with south? +postgres a few times to figure out what to automate. """ syncdb_commands = ['(test -f vcweb.db && rm -f vcweb.db) || true', '%(python)s manage.py syncdb --noinput' % env, @@ -44,7 +44,7 @@ def syncdb(**kwargs): with cd(env.project_path): - _virtualenv(*syncdb_commands, **kwargs) + _virtualenv(run, *syncdb_commands, **kwargs) def setup_virtualenv(): @@ -71,14 +71,14 @@ # figure out what the appropriate rabbitmq perms are here. sudo('rabbitmqctl set_permissions -p %s %s ".*" ".*" ".*"' % (vcweb_settings.BROKER_VHOST, vcweb_settings.BROKER_USER), pty=True) -def _virtualenv(*commands, **kwargs): +def _virtualenv(executor, *commands, **kwargs): """ source the virtualenv before executing this command """ env.command = ' && '.join(commands) - return run('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) + return executor('. %(virtualenv_path)s/bin/activate && %(command)s' % env, **kwargs) def pip(): ''' looks for requirements.pip in the django project directory ''' - _virtualenv('pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) + _virtualenv(run, 'pip install -E %(virtualenv_path)s -r %(project_path)s/requirements.pip' % env) #with cd(env.virtualenv_path): # sudo_chain('chgrp -R %(deploy_group)s .' % env, 'chmod -R g+rw' % env, pty=True) @@ -90,15 +90,15 @@ runs tests on this local codebase, not the deployed codebase ''' with cd(env.project_path): - _virtualenv('%(python)s manage.py test %(apps)s' % env) + _virtualenv(local, '%(python)s manage.py test %(apps)s' % env) def tornadio(ip="127.0.0.1", port=None): from vcweb import settings as vcweb_settings if port is None: port = vcweb_settings.SOCKET_IO_PORT - _virtualenv("{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) + _virtualenv(local, "{python} vcwebio.py {port}".format(python=env.python, **locals()), capture=False) -def server(ip="149.169.203.115", port=8000): +def server(ip="127.0.0.1", port=8000): local("{python} manage.py runserver {ip}:{port}".format(python=env.python, **locals()), capture=False) def celeryd(): @@ -156,7 +156,7 @@ if confirm("syncdb?"): syncdb() env.static_root = vcweb_settings.STATIC_ROOT - _virtualenv('%(python)s manage.py collectstatic' % env) + _virtualenv(run,'%(python)s manage.py collectstatic' % env) sudo_chain('chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', --- a/vcweb/forestry/models.py Tue Jun 07 15:48:26 2011 -0700 +++ b/vcweb/forestry/models.py Tue Jun 07 16:10:33 2011 -0700 @@ -11,7 +11,7 @@ check all forestry experiments. ''' -def get_resource_level(group=None, round_data=None): +def get_resource_level(group, round_data=None): ''' returns the group resource level data parameter ''' return group.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) @@ -44,6 +44,12 @@ def set_group_harvest(group, value): group.set_data_value(parameter=get_group_harvest_parameter(), value=value) +def should_reset_resource_level(round_configuration): + return round_configuration.get_parameter_value('reset.resource_level', default=False) + +def get_initial_resource_level(round_configuration): + return round_configuration.get_parameter_value('initial.resource_level', default=100) + def get_max_harvest_decision(resource_level): if resource_level >= 25: return 5 @@ -110,8 +116,8 @@ during a practice or regular round, set up resource levels and participant harvest decision parameters ''' - if round_configuration.get_parameter_value('reset.resource_level', default=False): - initial_resource_level = round_configuration.get_parameter_value('initial.resource_level', default=100) + if should_reset_resource_level(round_configuration): + initial_resource_level = get_initial_resource_level(round_configuration) for group in experiment.groups.all(): ''' set resource level to initial default ''' group.log("Setting resource level to initial value [%s]" % initial_resource_level) --- a/vcweb/forestry/tests.py Tue Jun 07 15:48:26 2011 -0700 +++ b/vcweb/forestry/tests.py Tue Jun 07 16:10:33 2011 -0700 @@ -6,7 +6,8 @@ get_regrowth_parameter, round_setup, round_teardown, get_resource_level, set_resource_level, set_harvest_decision, get_harvest_decision_parameter, get_harvest_decisions, forestry_sender, get_forestry_experiment_metadata, - get_resource_level_parameter) + get_resource_level_parameter, should_reset_resource_level, + get_initial_resource_level) import logging logger = logging.getLogger(__name__) @@ -89,12 +90,32 @@ e.current_round_sequence_number = rc.sequence_number self.assertEqual(e.current_round_template, 'forestry/quiz.html', 'should return default quiz.html') - ''' FIXME: several of these can and should be lifted to core/tests.py ''' class ForestryParametersTest(BaseVcwebTest): + def test_transfer_parameters(self): + def calculate_expected_resource_level(resource_level, harvested): + after_harvest = max(resource_level - harvested, 0) + return min(100, int(after_harvest + (after_harvest * .10))) + + e = self.advance_to_data_round() + expected_resource_level = 100 + while (e.has_next_round): + e.start_round() + current_round_configuration = e.current_round + if should_reset_resource_level(current_round_configuration): + expected_resource_level = get_initial_resource_level(current_round_configuration) + for pgr in e.participant_group_relationships: + self.assertEquals(get_resource_level(pgr.group).value, expected_resource_level) + set_harvest_decision(pgr, 5) + e.end_round() + resource_level = calculate_expected_resource_level(expected_resource_level, 25) + for group in e.groups.all(): + self.assertEquals(get_resource_level(pgr.group).value, resource_level) + e.advance_to_next_round() + def test_initialize_parameters_at_round_start(self): e = self.advance_to_data_round() e.start_round() @@ -165,15 +186,13 @@ for func in caching_funcs: verify_refreshed_data(func) - - def test_get_set_resource_level(self): e = self.advance_to_data_round() - + e.start_round() for group in e.groups.all(): resource_level = get_resource_level(group) self.assertTrue(resource_level.pk > 0) - self.assertFalse(resource_level.value) + self.assertEqual(resource_level.value, 100) resource_level.value = 3 resource_level.save() 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: Bitbucket <iss...@bi...> - 2011-06-07 23:08:39
|
--- you can reply above this line --- New issue 18: figure out implementation of experiment end_date https://bitbucket.org/virtualcommons/vcweb/issue/18/figure-out-implementation-of-experiment A Lee / alllee on Wed, 8 Jun 2011 01:08:33 +0200: Description: Either calculated via duration + start_date or have an explicit end_date. Or both. Responsible: alllee -- This is an issue notification from bitbucket.org. You are receiving this either because you are the owner of the issue, or you are following the issue. |
From: Bitbucket <com...@bi...> - 2011-06-07 22:50:59
|
1 new changeset in vcweb: http://bitbucket.org/virtualcommons/vcweb/changeset/25d0681337a4/ changeset: 25d0681337a4 user: mhurtad date: 2011-06-08 00:48:26 summary: Fixed bug in participate getattr based on current_location and adding some initial template stubs for the sanitation game affected #: 10 files (12.9 KB) --- a/vcweb/sanitation/views.py Tue Jun 07 01:09:20 2011 -0700 +++ b/vcweb/sanitation/views.py Tue Jun 07 15:48:26 2011 -0700 @@ -5,6 +5,13 @@ import logging logger = logging.getLogger(__name__) +class QuizQuestion(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + +q1 = QuizQuestion(type="radio", options=['Yes', 'No', 'Maybe'], sequence_number=1, answer="No") + + def configure(request, experiment_id=None): experiment = Experiment.objects.get(pk=experiment_id) return render_to_response('sanitation/configure.html', { @@ -13,31 +20,41 @@ context_instance=RequestContext(request)) def consent(request, experiment): - pass + logger.debug("handling consent") + return render_to_response('sanitation/consent.html', locals(), context_instance=RequestContext(request)) def survey(request, experiment): - pass + logger.debug("handling survey") + return render_to_response('sanitation/survey.html', locals(), context_instance=RequestContext(request)) def quiz(request, experiment): - pass + quiz_questions = [('q1', 'This is question #1?','text','answer1'), ('q2', 'This is question #2?','text','answer2')] + logger.debug("handling quiz") + return render_to_response('sanitation/quiz.html', locals(), context_instance=RequestContext(request)) def instructions(request, experiment): - pass + logger.debug("handling instructions") + return render_to_response('sanitation/instructions.html', locals(), context_instance=RequestContext(request)) def play(request, experiment): - pass + logger.debug("handling play") + return render_to_response('sanitation/play.html', locals(), context_instance=RequestContext(request)) def participate(request, experiment_id=None): # lookup participant's current location and then invoke the method named by the location participant = request.user.participant + experiment = Experiment.objects.get(pk=experiment_id) # FIXME: this isn't implemented - experiment = Experiment.objects.get(pk=experiment_id) - current_location = participant.current_location # "consent", "survey", "quiz", "play", "instructions" + current_location = "survey" +# current_location = participant.current_location # "consent", "survey", "quiz", "play", "instructions" if current_location in ["consent", "survey", "quiz", "play", "instructions"]: logger.debug("current location %s is valid", current_location) # invoke current_location as a method and pass in the request and the experiment - location_method = getattr(__name__, current_location) + location_method = globals()[current_location] return location_method(request, experiment) logger.debug("Invalid location %s, redirecting to dashboard", current_location) - return redirect('core:dashboard') - +# return redirect('core:dashboard') + return render_to_response('sanitation/'+ current_location + '.html', { + 'experiment': experiment, + }, + context_instance=RequestContext(request)) 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: Bitbucket <iss...@bi...> - 2011-06-07 19:42:08
|
--- you can reply above this line --- New issue 17: forestry: resource level not transferring properly through chat rounds https://bitbucket.org/virtualcommons/vcweb/issue/17/forestry-resource-level-not-transferring A Lee / alllee on Tue, 7 Jun 2011 21:42:03 +0200: Description: The resource_level group data value isn't getting transferred properly across chat rounds, Responsible: alllee -- This is an issue notification from bitbucket.org. You are receiving this either because you are the owner of the issue, or you are following the issue. |