[virtualcommons-developer] commit/vcweb: alllee: convertin e.current_round_elapsed_time into a calc
Status: Beta
Brought to you by:
alllee
From: <com...@bi...> - 2013-03-27 08:44:13
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/6853702867f4/ Changeset: 6853702867f4 User: alllee Date: 2013-03-27 09:43:56 Summary: convertin e.current_round_elapsed_time into a calculated field and refining experiment monitor interface Affected #: 8 files diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -204,6 +204,7 @@ # reset all player statuses to True ParticipantRoundDataValue.objects.for_group(group, parameter=get_player_status_parameter(), round_data=round_data).update(boolean_value=True) + # FIXME: start server side timer def get_total_harvest(group, round_data): diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -21,10 +21,11 @@ {% endblock content %} {% block sidebar %} +<div data-bind='ifnot: instructionsRound'><h3>Chat</h3><div data-bind='ifnot: chatEnabled'><div class='alert alert-info'> - <i class='icon-info-sign'></i> Chat is disabled until the next round begins. + <i class='icon-warning-sign'></i> Chat is currently disabled. </div></div><form id="chat-form" class='form-inline'> @@ -44,6 +45,7 @@ </div></div></div> +</div> {% endblock sidebar %} {% block javascript %} {{ block.super }} @@ -233,25 +235,27 @@ <div class='row'><div class='span5'><h3>My Status</h3> - <div data-bind='if: isTimerRunning'> - <div class='alert alert-error'> - <b><i class='icon-time'></i> Time left:</b> - <span data-bind='text: secondsLeft'></span>s - </div> - </div><div class='row'> - <div class='alert boundaries-status-dashboard span2'> - <h4>Last harvest <i class='icon-info-sign' data-content="The number of trees you harvested last round."></i></h4> - <p> - <strong class='text-success'><span data-bind='text:lastHarvestDecision'></span><i class='icon-leaf'></i></strong> - </p> - </div> - <div class='alert boundaries-status-dashboard span2'> - <h4>Storage <i class='icon-info-sign' data-bind='attr: {"data-content": "You need at least " + costOfLiving() + " trees to survive each round."}'></i></h4> - <p> - <strong data-bind='css: { "text-error": storage() < costOfLiving(), "text-success": storage() > costOfLiving()}'><span data-bind='text: storage'></span><i class='icon-leaf'></i></strong> - </p> - </div> + <div class='alert boundaries-status-dashboard span2'> + <h4>Last harvest <i class='icon-info-sign' data-content="The number of trees you harvested last round."></i></h4> + <p> + <strong class='text-success'><span data-bind='text:lastHarvestDecision'></span><i class='icon-leaf'></i></strong> + </p> + </div> + <div class='alert boundaries-status-dashboard span1'> + <h4>Storage <i class='icon-info-sign' data-bind='attr: {"data-content": "You need at least " + costOfLiving() + " trees to survive each round."}'></i></h4> + <p> + <strong data-bind='css: { "text-error": storage() < costOfLiving(), "text-success": storage() > costOfLiving()}'><span data-bind='text: storage'></span><i class='icon-leaf'></i></strong> + </p> + </div> + <div class='alert boundaries-status-dashboard span1'> + <h4><i class='icon-time'></i> Time</h4> + <p> + <strong data-bind='css: { "text-error": secondsLeft() < warningCountdownTime(), "text-info": secondsLeft() > warningCountdownTime()}'> + <span data-bind='text: secondsLeft'></span> s + </strong> + </p> + </div></div></div><div class='span4'> @@ -303,7 +307,7 @@ </div><div data-bind='if: submitted'><div class='alert alert-error'> - You have already submitted a harvest decision for <span class='badge badge-success' data-bind='text: harvestDecision'></span> trees. + You have submitted a harvest decision for <span class='badge badge-success' data-bind='text: harvestDecision'></span> trees. </div></div> @@ -440,8 +444,9 @@ }); model.resourceImageHeight = ko.observable("79px"); model.startRound = function() { + console.debug("starting round"); model.enableChat(); - model.secondsLeft(model.roundDuration()); + model.secondsLeft(model.timeRemaining()); model.setCurrentInterval( setInterval(function() { model.tick(); @@ -503,12 +508,16 @@ model.setFormDisabled("#chat-form", true); } model.enableChat = function() { + if (! model.chatEnabled()) { + return; + } // $('#content').removeClass('span9').addClass('span6'); // $('#sidebar').removeClass('span3').addClass('span6'); model.setFormDisabled("#vcweb-form", true); model.setFormDisabled("#chat-form", false); } model.afterRenderTemplate = function(elements) { + model.setFormDisabled("#chat-form", ! model.chatEnabled()); if (model.submitted() || ! model.alive()) { model.setFormDisabled("#vcweb-form", true); model.setFormDisabled("#chat-form", true); @@ -516,7 +525,7 @@ else { model.setFormDisabled("#vcweb-form", false); } - if (model.roundType() === "REGULAR") { + if (model.templateName() === "REGULAR") { model.startRound() } } diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/boundaries/views.py --- a/vcweb/boundaries/views.py +++ b/vcweb/boundaries/views.py @@ -64,6 +64,7 @@ 'warningCountdownTime': 10, 'harvestDecision': 0, 'chatMessages': [], + 'instructionsRound': False, } # FIXME: need to distinguish between instructions / welcome rounds and practice/regular rounds def get_view_model_json(experiment, participant_group_relationship, **kwargs): @@ -76,12 +77,14 @@ # round / experiment configuration data experiment_model_dict['roundDuration'] = current_round.duration + experiment_model_dict['timeRemaining'] = experiment.time_remaining regrowth_rate = get_regrowth_rate(current_round) cost_of_living = get_cost_of_living(current_round) experiment_model_dict['costOfLiving'] = cost_of_living experiment_model_dict['maxHarvestDecision'] = get_max_allowed_harvest_decision(participant_group_relationship, current_round_data, ec) # instructions round parameters if current_round.is_instructions_round: + experiment_model_dict['instructionsRound'] = True experiment_model_dict['participantsPerGroup'] = ec.max_group_size experiment_model_dict['regrowthRate'] = regrowth_rate experiment_model_dict['initialResourceLevel'] = get_initial_resource_level(current_round) @@ -116,8 +119,8 @@ experiment_model_dict['submitted'] = harvest_decision.submitted if harvest_decision.submitted: # user has already submit a harvest decision this round - logger.debug("already submitted, setting harvest decision to %s", harvest_decision.int_value) experiment_model_dict['harvestDecision'] = harvest_decision.int_value + logger.debug("already submitted, setting harvest decision to %s", experiment_model_dict['harvestDecision']) experiment_model_dict['chatMessages'] = [{ 'pk': cm.pk, @@ -125,7 +128,6 @@ 'message': cm.string_value, 'date_created': cm.date_created.strftime("%I:%M:%S") } for cm in ChatMessage.objects.for_group(own_group)] - logger.debug("chat messages: %s", experiment_model_dict['chatMessages']) experiment_model_dict['canObserveOtherGroup'] = can_observe_other_group(current_round) if not current_round.is_practice_round and experiment_model_dict['canObserveOtherGroup']: gr = GroupRelationship.objects.select_related('cluster').get(group=own_group) @@ -143,5 +145,4 @@ # FIXME: defaults hard coded in for now experiment_model_dict['instructions'] = current_round.get_custom_instructions() - experiment_model_dict.update(**kwargs) return dumps(experiment_model_dict) diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/core/cron.py --- a/vcweb/core/cron.py +++ b/vcweb/core/cron.py @@ -3,6 +3,10 @@ from vcweb.core import signals from vcweb.core.services import fetch_foursquare_categories +@register('@minute') +def every_minute(): + signals.minute_tick.send(sender=None, time=datetime.now()) + @register('@hourly') def every_hour(): signals.hour_tick.send(sender=None, time=datetime.now()) diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/core/migrations/0002_auto__del_field_experiment_current_round_elapsed_time.py --- /dev/null +++ b/vcweb/core/migrations/0002_auto__del_field_experiment_current_round_elapsed_time.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Experiment.current_round_elapsed_time' + db.delete_column(u'core_experiment', 'current_round_elapsed_time') + + + def backwards(self, orm): + # Adding field 'Experiment.current_round_elapsed_time' + db.add_column(u'core_experiment', 'current_round_elapsed_time', + self.gf('django.db.models.fields.PositiveIntegerField')(default=0), + keep_default=False) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'core.activitylog': { + 'Meta': {'object_name': 'ActivityLog'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_message': ('django.db.models.fields.TextField', [], {}) + }, + u'core.address': { + 'Meta': {'object_name': 'Address'}, + 'city': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'street1': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'street2': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'zipcode': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}) + }, + u'core.chatmessage': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'ChatMessage', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}), + 'target_participant': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'target_participant_chat_message_set'", 'null': 'True', 'to': u"orm['core.ParticipantGroupRelationship']"}) + }, + u'core.comment': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'Comment', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'core.experiment': { + 'Meta': {'ordering': "['date_created', 'status']", 'object_name': 'Experiment'}, + 'amqp_exchange_name': ('django.db.models.fields.CharField', [], {'default': "'vcweb.default.exchange'", 'max_length': '64'}), + 'authentication_code': ('django.db.models.fields.CharField', [], {'default': "'vcweb.auth.code'", 'max_length': '32'}), + 'current_repeated_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'current_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'current_round_start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'duration': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentConfiguration']"}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentMetadata']"}), + 'experimenter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experimenter']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'start_date_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'INACTIVE'", 'max_length': '32'}), + 'tick_duration': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'total_elapsed_time': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + u'core.experimentactivitylog': { + 'Meta': {'object_name': 'ExperimentActivityLog', '_ormbases': [u'core.ActivityLog']}, + u'activitylog_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ActivityLog']", 'unique': 'True', 'primary_key': 'True'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log_set'", 'to': u"orm['core.Experiment']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.experimentconfiguration': { + 'Meta': {'ordering': "['experiment_metadata', 'creator', 'date_created']", 'object_name': 'ExperimentConfiguration'}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_configuration_set'", 'to': u"orm['core.Experimenter']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'exchange_rate': ('django.db.models.fields.DecimalField', [], {'default': '0.2', 'null': 'True', 'max_digits': '6', 'decimal_places': '2', 'blank': 'True'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_configuration_set'", 'to': u"orm['core.ExperimentMetadata']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation_subject': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'invitation_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'is_experimenter_driven': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'max_group_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5'}), + 'max_number_of_participants': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'treatment_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}) + }, + u'core.experimenter': { + 'Meta': {'ordering': "['user']", 'object_name': 'Experimenter'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'authentication_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'failed_password_attempts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'institution': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Institution']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'experimenter'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'core.experimenterrequest': { + 'Meta': {'object_name': 'ExperimenterRequest'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) + }, + u'core.experimentmetadata': { + 'Meta': {'ordering': "['namespace', 'date_created']", 'object_name': 'ExperimentMetadata'}, + 'about_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentConfiguration']", 'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'namespace': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'short_name': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'core.experimentparametervalue': { + 'Meta': {'object_name': 'ExperimentParameterValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_parameter_value_set'", 'to': u"orm['core.ExperimentConfiguration']"}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.experimentsession': { + 'Meta': {'object_name': 'ExperimentSession'}, + 'capacity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '20'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_session_set'", 'to': u"orm['auth.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_session_set'", 'to': u"orm['core.ExperimentMetadata']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'scheduled_date': ('django.db.models.fields.DateTimeField', [], {}), + 'scheduled_end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.group': { + 'Meta': {'ordering': "['experiment', 'number']", 'object_name': 'Group'}, + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5'}), + 'number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'session_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}) + }, + u'core.groupactivitylog': { + 'Meta': {'object_name': 'GroupActivityLog', '_ormbases': [u'core.ActivityLog']}, + u'activitylog_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ActivityLog']", 'unique': 'True', 'primary_key': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_log_set'", 'to': u"orm['core.Group']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.groupcluster': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'GroupCluster'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_cluster_set'", 'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'session_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}) + }, + u'core.groupclusterdatavalue': { + 'Meta': {'object_name': 'GroupClusterDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'group_cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.GroupCluster']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_cluster_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.grouprelationship': { + 'Meta': {'ordering': "['date_created']", 'object_name': 'GroupRelationship'}, + 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_relationship_set'", 'to': u"orm['core.GroupCluster']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'core.grouprounddatavalue': { + 'Meta': {'ordering': "['round_data', 'group', 'parameter']", 'object_name': 'GroupRoundDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.institution': { + 'Meta': {'object_name': 'Institution'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + u'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment_session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentSession']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Participant']"}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'core.like': { + 'Meta': {'ordering': "['-date_created', 'round_data', 'participant_group_relationship', 'parameter']", 'object_name': 'Like', '_ormbases': [u'core.ParticipantRoundDataValue']}, + u'participantrounddatavalue_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['core.ParticipantRoundDataValue']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'core.parameter': { + 'Meta': {'ordering': "['name']", 'unique_together': "(('name', 'experiment_metadata', 'scope'),)", 'object_name': 'Parameter'}, + 'class_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Experimenter']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_value_string': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'display_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'enum_choices': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'experiment_metadata': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.ExperimentMetadata']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'scope': ('django.db.models.fields.CharField', [], {'default': "'round'", 'max_length': '32'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + u'core.participant': { + 'Meta': {'ordering': "['user']", 'object_name': 'Participant'}, + 'address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Address']", 'null': 'True', 'blank': 'True'}), + 'authentication_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'birthdate': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'can_receive_invitations': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'experiments': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'participant_set'", 'symmetrical': 'False', 'through': u"orm['core.ParticipantExperimentRelationship']", 'to': u"orm['core.Experiment']"}), + 'failed_password_attempts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'gender': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'participant_set'", 'symmetrical': 'False', 'through': u"orm['core.ParticipantGroupRelationship']", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'institution': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Institution']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'participant'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'core.participantexperimentrelationship': { + 'Meta': {'object_name': 'ParticipantExperimentRelationship'}, + 'additional_data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'current_location': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_relationship_set'", 'to': u"orm['core.Experiment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_completed_round_sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'experiment_relationship_set'", 'to': u"orm['core.Participant']"}), + 'participant_identifier': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'sequential_participant_identifier': ('django.db.models.fields.PositiveIntegerField', [], {}) + }, + u'core.participantgrouprelationship': { + 'Meta': {'ordering': "['group', 'participant_number']", 'object_name': 'ParticipantGroupRelationship'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'first_visit': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_group_relationship_set'", 'to': u"orm['core.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notifications_since': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_group_relationship_set'", 'to': u"orm['core.Participant']"}), + 'participant_number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'round_joined': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.participantrounddatavalue': { + 'Meta': {'ordering': "['-date_created', 'round_data', 'participant_group_relationship', 'parameter']", 'object_name': 'ParticipantRoundDataValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': u"orm['core.ParticipantGroupRelationship']"}), + 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': u"orm['core.RoundData']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'submitted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'target_data_value': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'target_data_value_set'", 'null': 'True', 'to': u"orm['core.ParticipantRoundDataValue']"}) + }, + u'core.participantsignup': { + 'Meta': {'object_name': 'ParticipantSignup'}, + 'attendance': ('django.db.models.fields.PositiveIntegerField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signup_set'", 'to': u"orm['core.Invitation']"}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signup_set'", 'to': u"orm['core.Participant']"}) + }, + u'core.quizquestion': { + 'Meta': {'object_name': 'QuizQuestion'}, + 'answer': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_quiz_question_set'", 'null': 'True', 'to': u"orm['core.Experiment']"}), + 'explanation': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'input_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'quiz_question_set'", 'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.roundconfiguration': { + 'Meta': {'ordering': "['experiment_configuration', 'sequence_number', 'date_created']", 'object_name': 'RoundConfiguration'}, + 'chat_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'create_group_clusters': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'debriefing': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'display_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'duration': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_configuration_set'", 'to': u"orm['core.ExperimentConfiguration']"}), + 'group_cluster_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instructions': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'preserve_existing_groups': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'randomize_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'repeat': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'round_type': ('django.db.models.fields.CharField', [], {'default': "'REGULAR'", 'max_length': '32'}), + 'sequence_number': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'session_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), + 'survey_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'template_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}) + }, + u'core.rounddata': { + 'Meta': {'ordering': "['round_configuration']", 'unique_together': "(('round_configuration', 'experiment'),)", 'object_name': 'RoundData'}, + 'elapsed_time': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'experiment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_data_set'", 'to': u"orm['core.Experiment']"}), + 'experimenter_notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_data_set'", 'to': u"orm['core.RoundConfiguration']"}) + }, + u'core.roundparametervalue': { + 'Meta': {'object_name': 'RoundParameterValue'}, + 'boolean_value': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_modified': ('vcweb.core.models.AutoDateTimeField', [], {'default': 'datetime.datetime.now'}), + 'parameter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['core.Parameter']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_parameter_value_set'", 'to': u"orm['core.RoundConfiguration']"}), + 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'core.spoolparticipantstatistics': { + 'Meta': {'object_name': 'SpoolParticipantStatistics'}, + 'absences': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'discharges': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invitations': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'participant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'spool_statistics_set'", 'to': u"orm['core.Participant']"}), + 'participations': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + } + } + + complete_apps = ['core'] \ No newline at end of file diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -274,17 +274,6 @@ return self.filter(status='COMPLETED', **kwargs) def active(self, **kwargs): return self.filter(status__in=('ACTIVE', 'ROUND_IN_PROGRESS'), **kwargs) - def increment_elapsed_time(self, status='ROUND_IN_PROGRESS', amount=60): - logger.debug("filtering on status %s", status) - if status is not None: - es = self.filter(status=status) - es.update(current_round_elapsed_time=models.F('current_round_elapsed_time') + amount, - total_elapsed_time=models.F('total_elapsed_time') + amount) - # check each experiment's total_elapsed_time against the total allotted time and - # issue round_stopped signals to experiments that need to be stopped. - for experiment in es.all(): - logger.debug("checking elapsed time on experiment %s", experiment) - experiment.check_elapsed_time() class Experiment(models.Model): """ @@ -336,8 +325,6 @@ started, incremented by the heartbeat monitor. """ current_round_start_time = models.DateTimeField(null=True, blank=True) - """ current round start time """ - current_round_elapsed_time = models.PositiveIntegerField(default=0) """ elapsed time in seconds for the current round. """ amqp_exchange_name = models.CharField(max_length=64, default="vcweb.default.exchange") @@ -359,8 +346,17 @@ return self.is_round_in_progress and self.current_round.is_playable_round @property + def current_round_elapsed_time(self): + if self.current_round_start_time: + return datetime.now() - self.current_round_start_time + return timedelta(0) + + @property def time_remaining(self): - return self.current_round.duration - self.current_round_elapsed_time + tr = self.current_round.duration - self.current_round_elapsed_time.seconds + if tr < 0: + return u"Expired %s seconds ago" % abs(tr) + return tr @property def is_timed_round(self): @@ -795,8 +791,10 @@ self.current_round_sequence_number = max(self.current_round_sequence_number - 1, 1) self.save() + ACCEPTABLE_ACTIONS = ('advance_to_next_round', 'end_round', 'start_round', 'move_to_previous_round', 'activate', + 'deactivate', 'complete', 'restart_round', 'restart') def invoke(self, action_name): - if action_name in ('advance_to_next_round', 'end_round', 'start_round', 'move_to_previous_round', 'activate', 'deactivate', 'complete'): + if action_name in Experiment.ACCEPTABLE_ACTIONS: getattr(self, action_name)() else: raise AttributeError("Invalid experiment action %s requested of experiment %s" % (action_name, self)) @@ -805,7 +803,6 @@ if self.is_round_in_progress: self.end_round() if self.has_next_round: - self.current_round_elapsed_time = 0 self.current_round_sequence_number += 1 self.start_round() else: @@ -827,7 +824,6 @@ logger.debug("%s STARTING ROUND (sender: %s)", self, sender) self.status = Experiment.Status.ROUND_IN_PROGRESS self.create_round_data() - self.current_round_elapsed_time = 0 self.current_round_start_time = datetime.now() self.save() self.log('Starting round') @@ -841,9 +837,11 @@ logger.debug("About to send round started signal with sender %s", sender) return signals.round_started.send_robust(sender, experiment=self, time=datetime.now(), round_configuration=current_round_configuration) + def stop_round(self, sender=None, **kwargs): + self.end_round() + def end_round(self, sender=None): self.status = Experiment.Status.ACTIVE - self.current_round_elapsed_time = max(self.current_round_elapsed_time, self.current_round.duration) self.save() self.log('Ending round with elapsed time %s' % self.current_round_elapsed_time) sender = intern(self.experiment_metadata.namespace.encode('utf8')) if sender is None else sender @@ -861,6 +859,17 @@ self.save() return self + def restart(self): + self.log("Restarting experiment entirely from the first round.") + self.deactivate() + self.current_round_sequence_number = 1 + self.activate() + self.start_round() + + def restart_round(self): + self.stop_round() + self.start_round() + def complete(self): self.log("Marking as COMPLETED") self.status = Experiment.Status.COMPLETED diff -r be61831f5354003e211ca1bec17819b94e61c97a -r 6853702867f4cdf88ca84e36c5372366c0437783 vcweb/core/templates/experimenter/monitor.html --- a/vcweb/core/templates/experimenter/monitor.html +++ b/vcweb/core/templates/experimenter/monitor.html @@ -7,28 +7,34 @@ <h3>{{experiment}}</h3><div class='row-fluid'><div class='span6'> - <ul class='nav nav-list' data-bind="if: isActive"> - <li class='nav-header'>round management</li> - <li> + <div class='alert alert-white' data-bind='if: isActive'> + <h4>ROUND MANAGEMENT</h4><div class='btn-toolbar'><div class='btn-group'> - {% if DEBUG %} <a class='btn btn-success' data-bind="click: confirmExperimentControllerAction.bind($data, true)" data-action='move_to_previous_round' data-content='Go back to the previous round.'><i class='icon-step-backward'></i> back</a> - {% endif %} - <a class='btn btn-success' data-bind="click: confirmExperimentControllerAction.bind($data, true), css: { disabled: isRoundInProgress }" data-action='start_round' data-content='Starts the round.'><i class='icon-play'></i> start</a> + <a class='btn btn-success' data-bind="visible: isRoundInProgress, click: confirmExperimentControllerAction.bind($data, true)" data-action='end_round' data-content='Ends the round.'><i class='icon-stop'></i> end round</a> + <a class='btn btn-success' data-bind="visible: ! isRoundInProgress(), click: confirmExperimentControllerAction.bind($data, true)" data-action='start_round' data-content='Starts the round.'><i class='icon-play'></i> start</a><a class='btn btn-success' data-bind='click: confirmExperimentControllerAction.bind($data, true)' data-action='advance_to_next_round' data-content='Advances to and starts the next round.'><i class='icon-step-forward'></i> next round</a></div></div><div class='btn-toolbar'><div class='btn-group'> - <a class='btn btn-success' data-bind='click: updateParticipants' data-content='Updates all connected participants.' ><i class='icon-exchange'></i> update participants</a> - <a class='btn btn-primary' data-bind='click: update' data-content='Update this page and pull the latest data from the server.' ><i class='icon-refresh'></i> refresh data</a> + <a class='btn btn-primary' data-bind='click: updateParticipants' data-content='Updates all connected participants.' ><i class='icon-exchange'></i> update participants</a> + <a class='btn btn-primary' data-bind='click: update' data-content='Updates this pagedata.' ><i class='icon-refresh'></i> refresh this page</a> + </div> + </div> + <div class='btn-toolbar'> + <div class='btn-group'> + <a class='btn btn-danger' data-bind="click: confirmExperimentControllerAction.bind($data, true)" + data-action='restart_round' data-content='Restart the round. This will reset timers and perform any additional round started setup logic.'> + <i class='icon-circle-arrow-left'></i> restart round</a> + <a class='btn btn-danger' data-bind="click: confirmExperimentControllerAction.bind($data, true)" + data-action='restart_round' data-content='Restart the round. This will reset timers and perform any additional round started setup logic.'> + <i class='icon-circle-arrow-up'></i> restart experiment</a><a class='btn btn-danger' data-bind='click: confirmExperimentControllerAction.bind($data, false)' data-action='deactivate' data-content='Deactivates this experiment.'><i class='icon-off'></i> deactivate</a></div></div> - </li> - </ul> - {% comment %} should offer different actions (start + reset) to experiments that has already been activated / started {% endcomment %} + </div><div data-bind='if: isArchived()'><div class='alert alert-success'><i class='icon-save'></i> This experiment has been archived. @@ -47,23 +53,49 @@ <h4>Inactive Experiment</h4><p> There are <span data-bind='text:participantCount' class='badge badge-info'></span> registered participants. - You can activate the experiment whenever you are ready to collect data. + Activate the experiment whenever you are ready to collect data. </p> - <p class='text-error'>NOTE: activating an experiment <b>will delete all existing data</b> + <p class='text-error'>NOTE: activating an experiment <b>deletes all existing data</b></p><a data-content='Starts this experiment, assigns participants to groups, etc.' class='btn btn-success' data-bind='click: confirmExperimentControllerAction.bind($data, false)' data-action='activate'><i class='icon-off'></i> activate</a></div></div></div> - <ul class='nav nav-list'> - <li class='nav-header'>round status</li> - <li><b class='text-info' data-bind='text:roundSequenceLabel'></b></li> - <li><span data-bind='text:roundStatusLabel'></span></li> - <li>Experiment started on: <span data-bind='text:currentRoundStartTime'></span></li> - <li>Time remaining: <span data-bind='text:timeRemaining'></span></li> - <li>Registered participants: <span data-bind='text:participantCount' class='badge badge-info'></span></li> - </ul> + <div class='well' style='padding-top: 5px; padding-bottom: 5px; margin-bottom: 0px;'> + <h5>ROUND STATUS</h5> + <div><b class='text-info' data-bind='text:roundSequenceLabel'></b></div> + <div><span data-bind='text:roundStatusLabel'></span></div> + <div>Round started on: <span data-bind='text:currentRoundStartTime'></span></div> + <div>Time remaining: <span data-bind='text:timeRemaining'></span></div> + </div> + <div class='accordion-group'> + <div class='accordion-heading'> + <a class='accordion-toggle' data-toggle='collapse' + data-parent='#registeredParticipantsAccordion' href='#registeredParticipants'> + Registered participants: <span id='participantCountId' data-bind='text:participantCount' class='badge badge-info'></span> + </a> + </div> + <div id='registeredParticipants' class='accordion-body collapse'> + <div class='accordion-inner'> + <table class='table table-compact table-striped table-bordered'> + <thead><tr><th>Group</th><th>PGR PK</th><th>Participant</th></tr></thead> + <tbody> + {% regroup registeredParticipants by group as group_list %} + {% for group in group_list %} + {% for item in group.list %} + <tr> + <td>{{ item.group }}</td> + <td>{{ item.participant_group_relationship.pk }}</td> + <td>{{ item.participant_group_relationship.participant.email }}, {{ item.participant_group_relationship.participant_handle}} </td> + </tr> + {% endfor %} + {% endfor %} + </tbody> + </table> + </div> + </div> + </div><div class='alert alert-message alert-block'><h4><i class='icon-download'></i> Download Data</h4><ul class='inline'> @@ -213,6 +245,12 @@ }, {pk: {{experiment.pk}}, 'action':action}); }); } + model.startOrStopExperimentAction = ko.computed(function() { + return model.isRoundInProgress() ? "stop_round" : "start_round"; + }); + model.startOrStopExperimentActionText = ko.computed(function() { + return model.isRoundInProgress() ? "stop" : "start"; + }); model.updateParticipants = function(m, evt) { confirmAction(evt.target, function(confirmed, action) { if (confirmed) { @@ -254,6 +292,8 @@ experimentModel.addMessage(jQuery.parseJSON(evt.data)); }; return experimentModel; + // FIXME: this won't quite work + // $('#participan... [truncated message content] |