virtualcommons-svn Mailing List for Virtual Commons Experiment Software (Page 9)
Status: Beta
Brought to you by:
alllee
You can subscribe to this list here.
2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
(21) |
Aug
(31) |
Sep
(6) |
Oct
(15) |
Nov
(2) |
Dec
(9) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2009 |
Jan
(4) |
Feb
(6) |
Mar
(12) |
Apr
(52) |
May
(14) |
Jun
(19) |
Jul
(81) |
Aug
(115) |
Sep
(36) |
Oct
(88) |
Nov
(46) |
Dec
(58) |
2010 |
Jan
(52) |
Feb
(55) |
Mar
(48) |
Apr
(15) |
May
(5) |
Jun
(38) |
Jul
(27) |
Aug
(24) |
Sep
(28) |
Oct
(1) |
Nov
(2) |
Dec
(29) |
2011 |
Jan
(87) |
Feb
(39) |
Mar
(63) |
Apr
(42) |
May
(26) |
Jun
(53) |
Jul
(23) |
Aug
(43) |
Sep
(37) |
Oct
(25) |
Nov
(4) |
Dec
(7) |
2012 |
Jan
(73) |
Feb
(79) |
Mar
(62) |
Apr
(28) |
May
(12) |
Jun
(2) |
Jul
(9) |
Aug
(1) |
Sep
(8) |
Oct
|
Nov
(3) |
Dec
(3) |
2013 |
Jan
(8) |
Feb
(16) |
Mar
(38) |
Apr
(74) |
May
(62) |
Jun
(15) |
Jul
(49) |
Aug
(19) |
Sep
(9) |
Oct
|
Nov
|
Dec
|
2014 |
Jan
|
Feb
|
Mar
|
Apr
(2) |
May
(25) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <com...@bi...> - 2013-04-22 08:54:25
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/0aa66ba02800/ Changeset: 0aa66ba02800 User: alllee Date: 2013-04-22 10:53:59 Summary: renaming boundaries experiment app name for consistency, fixes issue 103 Affected #: 33 files diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/fixtures/bound_experiment_metadata.json --- /dev/null +++ b/vcweb/bound/fixtures/bound_experiment_metadata.json @@ -0,0 +1,16 @@ +[ + { + "pk": 6, + "model": "core.experimentmetadata", + "fields": { + "description": "The boundary effect experiment manipulates the resource flows and information connections between social groups and ecological resources.", + "short_name": null, + "title": "Boundary Effects Experiment", + "namespace": "bound", + "last_modified": "2012-10-29T16:42:20.880", + "date_created": "2012-10-29T16:42:20.874", + "logo_url": null, + "about_url": "http://commons.asu.edu" + } + } +] diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/fixtures/bound_parameters.json --- /dev/null +++ b/vcweb/bound/fixtures/bound_parameters.json @@ -0,0 +1,67 @@ +[ + { + "fields": { + "name":"player_status", + "experiment_metadata": 1, + "creator": 1, + "type": "int", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "participant" + }, + "model": "core.parameter", + "pk": 1111 + }, + { + "fields": { + "name":"regrowth_rate", + "experiment_metadata": 1, + "creator": 1, + "type": "float", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1112 + }, + { + "fields": { + "name":"cost_of_living", + "experiment_metadata": 1, + "creator": 1, + "type": "float", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1113 + }, + { + "fields": { + "name":"shared_resource", + "experiment_metadata": 1, + "creator": 1, + "type": "boolean", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1114 + }, + { + "fields": { + "name":"storage", + "experiment_metadata": 1, + "creator": 1, + "type": "int", + "date_created": "2011-01-01 15:13:07", + "last_modified": "2011-01-01 15:15:05", + "scope": "participant" + }, + "model": "core.parameter", + "pk": 1115 + } +] diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/forms.py --- /dev/null +++ b/vcweb/bound/forms.py @@ -0,0 +1,8 @@ +from vcweb.core.forms import ParticipantGroupIdForm, SingleIntegerDecisionForm +from vcweb.forestry import forms + + +class HarvestDecisionForm(forms.HarvestDecisionForm): +# XXX: only used to export HarvestDecisionForm in its own namespace at the moment + pass + diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/migrations/0001_initial.py --- /dev/null +++ b/vcweb/bound/migrations/0001_initial.py @@ -0,0 +1,21 @@ +# -*- 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): + from django.core.management import call_command + call_command("loaddata", "bound_experiment_metadata.json") + + def backwards(self, orm): + pass + + models = { + + } + + complete_apps = ['bound'] diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/migrations/0002_max_harvest_decision.py --- /dev/null +++ b/vcweb/bound/migrations/0002_max_harvest_decision.py @@ -0,0 +1,405 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..." + Parameter = orm['core.Parameter'] + Experimenter = orm['core.Experimenter'] + Parameter.objects.create(name='max_harvest_decision', + creator=Experimenter.objects.get(pk=1), + type='int', + scope='experiment', + default_value_string='10', + description='Maximum harvest decision a player can make in a typical harvesting experiment') + + + def backwards(self, orm): + "Write your backwards methods here." + + 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_elapsed_time': ('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': "'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': "'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': "'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', 'bound'] + symmetrical = True diff -r 5408d2dc620855130cbaed113ab17774ecd380bb -r 0aa66ba028003619ad69610ba68d54fc056e7a8c vcweb/bound/models.py --- /dev/null +++ b/vcweb/bound/models.py @@ -0,0 +1,426 @@ +from django.db.models import Sum +from django.dispatch import receiver +from vcweb.core import signals, simplecache +from vcweb.core.models import (ExperimentMetadata, Parameter, ParticipantRoundDataValue, GroupRelationship, + GroupCluster, GroupClusterDataValue, RoundData, RoundConfiguration) +from vcweb.forestry.models import (get_harvest_decision_parameter, get_harvest_decision, get_harvest_decision_dv, get_regrowth_rate_parameter, + get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level as get_unshared_resource_level, + get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, set_resource_level, + get_resource_level_parameter, get_resource_level_dv as get_unshared_resource_level_dv, + set_group_harvest, set_regrowth, set_harvest_decision) + +import logging + +logger = logging.getLogger(__name__) + +# FIXME: hacky, should figure out a better way to bind this module with the ExperimentMetadata instance that it is +# dependent on +EXPERIMENT_METADATA_NAME = intern('bound') +# constants that should live in configuration as well +MAX_RESOURCE_LEVEL = 240 +MAX_SHARED_RESOURCE_LEVEL = 480 + +INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND = 3 + +''' +Experiment Parameters and Metadata Accessors +''' + + +@simplecache +def get_experiment_metadata(): + return ExperimentMetadata.objects.get(namespace=EXPERIMENT_METADATA_NAME) + + +@simplecache +def get_player_status_parameter(): + return Parameter.objects.for_participant(name='player_status') + + +@simplecache +def get_storage_parameter(): + return Parameter.objects.for_participant(name='storage') + + +@simplecache +def get_survival_cost_parameter(): + return Parameter.objects.for_round(name='survival_cost') + + +@simplecache +def get_max_harvest_decision_parameter(): + return Parameter.objects.for_experiment(name='max_harvest_decision') + + +@simplecache +def get_cost_of_living_parameter(): + return Parameter.objects.for_round(name='cost_of_living') + + +@simplecache +def get_observe_other_group_parameter(): + return Parameter.objects.for_round(name='observe_other_group') + + +@simplecache +def get_shared_resource_enabled_parameter(): + return Parameter.objects.for_round(name='shared_resource') + + +''' value accessors ''' + +''' round and experiment configuration accessors ''' +def get_regrowth_rate(round_configuration): + return round_configuration.get_parameter_value(parameter=get_regrowth_rate_parameter(), default=0.40).float_value + + +def can_observe_other_group(round_configuration): + return round_configuration.get_parameter_value(parameter=get_observe_other_group_parameter(), + default=False).boolean_value + + +def is_shared_resource_enabled(round_configuration): + return round_configuration.get_parameter_value(parameter=get_shared_resource_enabled_parameter(), + default=False).boolean_value + +def get_max_resource_level(round_configuration): + ec = round_configuration.experiment_configuration +# FIXME: number of rounds currently hard coded to be 20 for regular rounds, 10 for practice rounds + number_of_rounds = 20 if round_configuration.is_regular_round else 10 + return INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND * ec.max_group_size * number_of_rounds + +def get_initial_resource_level(round_configuration, default=None): + return get_max_resource_level(round_configuration) + +def should_reset_resource_level(round_configuration): + return round_configuration.get_parameter_value(parameter=get_reset_resource_level_parameter(), + default=False).boolean_value + +def get_cost_of_living(round_configuration): + return round_configuration.get_parameter_value(get_cost_of_living_parameter(), default=5).int_value + + +def get_max_harvest_decision(experiment_configuration): + return experiment_configuration.get_parameter_value(parameter=get_max_harvest_decision_parameter(), default=10).int_value + +def get_max_allowed_harvest_decision(participant_group_relationship, round_data=None, experiment_configuration=None): + return get_max_harvest_decision(experiment_configuration) + +def get_resource_level(group, round_data=None, round_configuration=None, cluster=None): + return get_resource_level_dv(group, round_data, round_configuration, cluster).int_value + +''' group data accessors ''' +def get_average_harvest(group, round_data): + return get_total_group_harvest(group, round_data) / float(group.size) + +def get_average_storage(group, round_data): + return get_total_storage(group, round_data) / float(group.size) + +def get_resource_level_dv(group, round_data=None, round_configuration=None, cluster=None): + ''' + Returns either the GroupClusterDataValue (shared resource condition) or the GroupRoundDataValue (standard + resource per group condition) for the given group + ''' + if round_data is None: + round_data = group.current_round_data + if round_configuration is None: + round_configuration = round_data.round_configuration + if is_shared_resource_enabled(round_configuration): + return get_shared_resource_level_dv(group, round_data, cluster) + else: + return get_unshared_resource_level_dv(group, round_data) + + +def get_shared_resource_level(group, round_data=None, cluster=None): + return get_shared_resource_level_dv(group, round_data, cluster).int_value + + +def get_shared_resource_level_dv(group=None, round_data=None, cluster=None): + if round_data is None: + round_data = group.current_round_data + if cluster is None: + group_relationship = GroupRelationship.objects.select_related('group_cluster').get(group=group) + cluster = group_relationship.cluster + return cluster.get_data_value(parameter=get_resource_level_parameter(), round_data=round_data) + +''' participant data value accessors ''' + +def get_storage_dv(participant_group_relationship, round_data=None, default=None): + return participant_group_relationship.get_data_value(parameter=get_storage_parameter(), round_data=round_data, default=default) + + +def get_storage(participant_group_relationship, round_data=None, default=0): + dv = get_storage_dv(participant_group_relationship, round_data, default) + return max(default if dv.int_value is None else dv.int_value, 0) + +def get_all_session_storages(experiment, participant_group_relationship): + debriefing_session_round_data = RoundData.objects.filter(experiment=experiment, + round_configuration__round_type=RoundConfiguration.RoundType.DEBRIEFING, + round_configuration__session_id__isnull=False) + return ParticipantRoundDataValue.objects.filter( + participant_group_relationship=participant_group_relationship, + parameter=get_storage_parameter(), + round_data__in=debriefing_session_round_data).order_by('date_created') + + +def _zero_if_none(value): + return 0 if value is None else value + +def get_total_group_harvest(group, round_data): + q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), round_data=round_data).aggregate(total_harvest=Sum('int_value')) + return _zero_if_none(q['total_harvest']) + + +def get_total_harvest(participant_group_relationship, session_id): + q = ParticipantRoundDataValue.objects.for_participant(participant_group_relationship, parameter=get_harvest_decision_parameter(), + participant_group_relationship__group__session_id=session_id).aggregate(total_harvest=Sum('int_value')) + return _zero_if_none(q['total_harvest']) + +# returns the sum of all stored resources for each member in the group +def get_total_storage(group, round_data): + q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_storage_parameter(), round_data=round_data).aggregate(total_storage=Sum('int_value')) + return _zero_if_none(q['total_storage']) + +def set_storage(participant_group_relationship, round_data, value): + storage_dv = get_storage_dv(participant_group_relationship, round_data) + storage_dv.int_value = value + storage_dv.save() + return storage_dv + +def get_player_status_dv(participant_group_relationship, round_data, default=True): + return participant_group_relationship.get_data_value(parameter=get_player_status_parameter(), + round_data=round_data, default=default) + +def get_player_status(participant_group_relationship, round_data, default=True): + return get_player_status_dv(participant_group_relationship, round_data, default).boolean_value + +def set_player_status(participant_group_relationship, round_data, value): + status_dv = get_player_status_dv(participant_group_relationship, round_data) + status_dv.boolean_value = value + status_dv.save() + return status_dv + +def get_last_harvest_decision(participant_group_relationship, round_data=None): + return participant_group_relationship.get_data_value(parameter=get_harvest_decision_parameter(), + round_data=round_data, default=0).int_value + + +@receiver(signals.round_started, sender=EXPERIMENT_METADATA_NAME) +def round_started_handler(sender, experiment=None, **kwargs): + if experiment is None: + logger.error("Received round started signal with no experiment: %s", sender) + raise ValueError("Received round started signal with no experiment") + round_configuration = experiment.current_round + round_data = experiment.get_round_data(round_configuration) + logger.debug("setting up round %s", round_configuration) + # initialize group and participant data values + if round_configuration.is_playable_round: + experiment.initialize_data_values( + group_cluster_parameters=(get_regrowth_parameter(), get_resource_level_parameter(),), + group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), + participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) + ) +# check for dead participants and set their ready and harvest decision flags + deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), + round_data=round_data, boolean_value=False) + for prdv in deceased_participants: + pgr = prdv.participant_group_relationship + set_harvest_decision(pgr, 0, round_data, submitted=True) + ''' + during a practice or regular round, set up resource levels, participant harvest decision parameters, and group + formation + ''' + if should_reset_resource_level(round_configuration): + initial_resource_level = get_max_resource_level(round_configuration) + logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) + for group in experiment.group_set.filter(session_id=round_configuration.session_id): + ''' set resource level to initial default ''' + existing_resource_level = get_resource_level_dv(group, round_data, round_configuration) + group.log( + "Setting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) + existing_resource_level.int_value = initial_resource_level + existing_resource_level.save() + # FIXME: verify that this is expected behavior - if the resource level is reset, reset storage to 0 + ParticipantRoundDataValue.objects.for_group(group, parameter=get_storage_parameter(), + round_data=round_data).update(int_value=0) + # reset all player statuses to alive + ParticipantRoundDataValue.objects.for_group(group, parameter=get_player_status_parameter(), + round_data=round_data).update(boolean_value=True) + + + +def adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest, group_size=0): + if group_size == 0: + group_size = group.size +# pass in the group size to handle group cluster case + average_harvest = current_resource_level / group_size + group.log("GROUP HARVEST ADJUSTMENT - original total harvest: %s, resource level: %s, average harvest: %s" % + (total_harvest, current_resource_level, average_harvest)) + hds = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), + round_data=round_data, int_value__gt=0).order_by('int_value') + total_adjusted_harvest = 0 +# FIXME: should be the same as group.size + total_number_of_decisions = hds.count() + logger.debug("total number of decisions: %s - group size: %s", total_number_of_decisions, group_size) + decisions_allocated = 0 + for hd in hds: + if hd.int_value <= average_harvest: + group.log("preserving %s < average harvest" % hd) + total_adjusted_harvest += hd.int_value + else: +# now to assign the overs, find out how much resource level is remaining + remaining_resource_level = current_resource_level - total_adjusted_harvest + remaining_decisions = total_number_of_decisions - decisions_allocated + average_harvest = remaining_resource_level / remaining_decisions + hd.is_active = False + hd.save() + logger.debug("Assigning %s to hd %s", average_harvest, hd) + ParticipantRoundDataValue.objects.create(participant_group_relationship=hd.participant_group_relationship, + parameter=get_harvest_decision_parameter(), round_data=round_data, int_value=average_harvest, + submitted=True) + total_adjusted_harvest += average_harvest + decisions_allocated += 1 + + logger.debug("harvested total %s", total_adjusted_harvest) + return total_adjusted_harvest + + +def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=None): + if max_resource_level is None: + max_resource_level = get_max_resource_level(round_data.round_configuration) + current_resource_level_dv = get_resource_level_dv(group, round_data) + current_resource_level = current_resource_level_dv.int_value +# FIXME: would be nicer to extend Group behavior and have group.get_total_harvest() instead of +# get_total_group_harvest(group, ...), see if we can enable this dynamically + total_harvest = get_total_group_harvest(group, round_data) + logger.debug("Harvest: total group harvest for playable round: %s", total_harvest) + if current_resource_level > 0: + if total_harvest > current_resource_level: + adjusted_harvest = adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest) + total_harvest = adjusted_harvest + + group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) + set_group_harvest(group, total_harvest, round_data) + current_resource_level = current_resource_level - total_harvest + resource_regrowth = calculate_regrowth(current_resource_level, regrowth_rate, max_resource_level) + group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) + set_regrowth(group, resource_regrowth, round_data) + # clamp resource + current_resource_level_dv.int_value = min(current_resource_level + resource_regrowth, max_resource_level) + current_resource_level_dv.save() + else: + group.log("current resource level is 0, no one can harvest") + set_group_harvest(group, 0, round_data) + ParticipantRoundDataValue.objects.for_group(group, parameter=get_harvest_decision_parameter(), + round_data=round_data).update(is_active=False) + for pgr in group.participant_group_relationship_set.all(): + # Create adjusted data values + ParticipantRoundDataValue.objects.create(participant_group_relationship=pgr, + round_data=round_data, parameter=get_harvest_decision_parameter(), + int_value=0) + + + ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' + if experiment.has_next_round: + ''' set group round data resource_level for each group + regrowth ''' + group.log("Transferring resource level %s to next round" % current_resource_level_dv.int_value) + group.copy_to_next_round(current_resource_level_dv) + + +# FIXME: reduce duplication between this and update_resource_level +def update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate, max_resource_level=None): + logger.debug("updating shared resource level") + if max_resource_level is None: + max_resource_level = get_max_resource_level(round_data.round_configuration) + max_resource_level = max_resource_level * group_cluster.size + shared_resource_level_dv = get_shared_resource_level_dv(cluster=group_cluster, round_data=round_data) + shared_resource_level = shared_resource_level_dv.int_value + shared_group_harvest = 0 + group_cluster_size = 0 + group_harvest_dict = {} + for group_relationship in group_cluster.group_relationship_set.all(): + group = group_relationship.group + group_cluster_size += group.size + group_harvest = get_total_group_harvest(group, round_data) + group_harvest_dict[group] = group_harvest + shared_group_harvest += group_harvest + group.log("total group harvest: %s" % group_harvest) + for group, group_harvest in group_harvest_dict.items(): + if shared_group_harvest > shared_resource_level: + # adjust each individual harvest for each group in this cluster + group_harvest = adjust_harvest_decisions(shared_resource_level, group, round_data, group_harvest, group_size=group_cluster_size) + set_group_harvest(group, group_harvest, round_data)... [truncated message content] |
From: <com...@bi...> - 2013-04-22 06:55:31
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/5408d2dc6208/ Changeset: 5408d2dc6208 User: alllee Date: 2013-04-20 01:37:02 Summary: fixing survey_url construction and last_round debriefing session storage variable logic Affected #: 3 files diff -r a0fa039317220b35b8c1751fd55cd498e0be5248 -r 5408d2dc620855130cbaed113ab17774ecd380bb vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -75,6 +75,7 @@ </p><p> We will now move on to the <strong>paid rounds.</strong> + Before you continue, please <a data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a></p><ul class='pager'><li class='next'> @@ -123,7 +124,7 @@ Thank you for participating. As you recall from the initial instructions, we will pay you for one of the two experimental sessions you just completed. You will select, at random, which session you will be paid for by flipping a coin. The experimenter will be with you shortly to assist you with this process. While you wait, please - <a target='_blank' data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a> + <a data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a></p><h2>Payments</h2><table class='table'> diff -r a0fa039317220b35b8c1751fd55cd498e0be5248 -r 5408d2dc620855130cbaed113ab17774ecd380bb vcweb/boundaries/views.py --- a/vcweb/boundaries/views.py +++ b/vcweb/boundaries/views.py @@ -110,17 +110,22 @@ if current_round.is_debriefing_round: experiment_model_dict['totalHarvest'] = get_total_harvest(participant_group_relationship, current_round.session_id) + if experiment.is_last_round: + (session_one_storage, session_two_storage) = get_all_session_storages(experiment, participant_group_relationship) + experiment_model_dict['sessionOneStorage'] = session_one_storage + experiment_model_dict['sessonTwoStorage'] = session_two_storage - if experiment.is_last_round: + if current_round.is_survey_enabled: # add last round data query_parameters = urlencode({ 'pid': participant_group_relationship.pk, 'eid': experiment.pk }) - experiment_model_dict['surveyUrl'] = "{0}?{1}".format(current_round.survey_url, query_parameters) - (session_one_storage, session_two_storage) = get_all_session_storages(experiment, participant_group_relationship) - experiment_model_dict['sessionOneStorage'] = session_one_storage - experiment_model_dict['sessonTwoStorage'] = session_two_storage + survey_url = current_round.survey_url + delimiter = '?' + if delimiter in survey_url: + delimiter = '&' + experiment_model_dict['surveyUrl'] = "{0}{1}{2}".format(current_round.survey_url, delimiter, query_parameters) # participant data diff -r a0fa039317220b35b8c1751fd55cd498e0be5248 -r 5408d2dc620855130cbaed113ab17774ecd380bb vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -38,6 +38,9 @@ """ class DefaultValue(object): + ''' + Simple object wrapper that returns the wrapped value on any attribute reference + ''' def __init__(self, value): self.value = value def __getattr__(self, name): @@ -1168,11 +1171,10 @@ def is_playable_round(self): return self.round_type in RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS + @property def is_survey_enabled(self): - try: - return self.survey_url is not None - except: - return False + survey_url = getattr(self, 'survey_url', None) + return survey_url is not None and survey_url def get_debriefing(self, participant_id=None, **kwargs): return self.templatize(self.debriefing, participant_id, kwargs) https://bitbucket.org/virtualcommons/vcweb/commits/a0fa03931722/ Changeset: a0fa03931722 User: alllee Date: 2013-04-19 23:13:30 Summary: adding docs fabric deployment target to publish to commons.asu.edu/api/vcweb Affected #: 1 file diff -r 3600bf9a697f8907d7cb823adc39d13c0d8af005 -r a0fa039317220b35b8c1751fd55cd498e0be5248 fabfile.py --- a/fabfile.py +++ b/fabfile.py @@ -1,14 +1,17 @@ -from fabric.api import local, run, sudo, cd, env, hide +from fabric.api import local, run, sudo, cd, env, lcd, hosts +from fabric.context_managers import settings as fab_settings +from fabric.contrib import django from fabric.contrib.console import confirm -from fabric.contrib import django -from fabric.context_managers import settings as fab_settings +from fabric.contrib.project import rsync_project import os, sys, shutil, logging logger = logging.getLogger(__name__) +# default to current working directory +env.project_path = os.path.dirname(__file__) # needed to push vcweb.settings onto the path. -sys.path.append(os.path.abspath(os.path.dirname(__file__))) +sys.path.append(os.path.abspath(env.project_path)) # default env configuration env.python = 'python' @@ -19,12 +22,12 @@ env.database = 'default' #env.virtualenv_path = '/opt/virtualenvs/%(project_name)s' % env env.deploy_path = '/opt/' -# default to current working directory -env.project_path = os.path.dirname(__file__) env.hosts = ['localhost'] env.hg_url = 'https://bitbucket.org/virtualcommons/vcweb' env.apache = 'httpd' env.applist = ['core', 'forestry', 'boundaries', 'lighterprints'] +env.docs_path = os.path.join(env.project_path, 'docs') +env.remote_docs_path = '/home/csid/public_html/api/vcweb' env.apps = ' '.join(env.applist) # django integration for access to settings, etc. @@ -39,6 +42,16 @@ '%(python)s manage.py migrate' % env, ] + +@hosts('cs...@co...') +def docs(): + with lcd(env.docs_path): + local("/usr/bin/make html") + rsync_project(env.remote_docs_path, 'build/html/') + with cd(env.remote_docs_path): + run('find . -type d -exec chmod a+rx {} \; && chmod -R a+r .') + + def migrate(): local("{python} manage.py migrate".format(python=env.python), capture=False) @@ -170,13 +183,15 @@ push() if confirm("Deploy to %(hosts)s ?" % env): with cd(env.project_path): - sudo_chain('hg pull && hg up -C', + sudo_chain( + 'hg pull && hg up -C', 'chmod g+s logs', 'chmod -R g+rw logs/', user=env.deploy_user, pty=True) env.static_root = vcweb_settings.STATIC_ROOT _virtualenv(run,'%(python)s manage.py collectstatic' % env) - sudo_chain('chmod -R ug+rw .', + sudo_chain( + 'chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find %(static_root)s -type f -exec chmod a+r {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-04-19 23:59:09
|
New issue 108: network structure initialization https://bitbucket.org/virtualcommons/vcweb/issue/108/network-structure-initialization A Lee: Provide support for graph / network structure initialization: 1. Initial would be a regular network, where each participant is connected to N of their neighbors (imagine a linked list) 2. Other network structures may be needed for the broker experiment Responsible: alllee |
From: <com...@bi...> - 2013-04-19 23:37:15
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/94368fac5f22/ Changeset: 94368fac5f22 Branch: stable User: alllee Date: 2013-04-20 01:37:02 Summary: fixing survey_url construction and last_round debriefing session storage variable logic Affected #: 3 files diff -r 875a72fb1f69c3110db6bdc8824757e091e40201 -r 94368fac5f228b55b5629dbd6e3491eb7abe8f01 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -75,6 +75,7 @@ </p><p> We will now move on to the <strong>paid rounds.</strong> + Before you continue, please <a data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a></p><ul class='pager'><li class='next'> @@ -123,7 +124,7 @@ Thank you for participating. As you recall from the initial instructions, we will pay you for one of the two experimental sessions you just completed. You will select, at random, which session you will be paid for by flipping a coin. The experimenter will be with you shortly to assist you with this process. While you wait, please - <a target='_blank' data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a> + <a data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a></p><h2>Payments</h2><table class='table'> diff -r 875a72fb1f69c3110db6bdc8824757e091e40201 -r 94368fac5f228b55b5629dbd6e3491eb7abe8f01 vcweb/boundaries/views.py --- a/vcweb/boundaries/views.py +++ b/vcweb/boundaries/views.py @@ -110,17 +110,22 @@ if current_round.is_debriefing_round: experiment_model_dict['totalHarvest'] = get_total_harvest(participant_group_relationship, current_round.session_id) + if experiment.is_last_round: + (session_one_storage, session_two_storage) = get_all_session_storages(experiment, participant_group_relationship) + experiment_model_dict['sessionOneStorage'] = session_one_storage + experiment_model_dict['sessonTwoStorage'] = session_two_storage - if experiment.is_last_round: + if current_round.is_survey_enabled: # add last round data query_parameters = urlencode({ 'pid': participant_group_relationship.pk, 'eid': experiment.pk }) - experiment_model_dict['surveyUrl'] = "{0}?{1}".format(current_round.survey_url, query_parameters) - (session_one_storage, session_two_storage) = get_all_session_storages(experiment, participant_group_relationship) - experiment_model_dict['sessionOneStorage'] = session_one_storage - experiment_model_dict['sessonTwoStorage'] = session_two_storage + survey_url = current_round.survey_url + delimiter = '?' + if delimiter in survey_url: + delimiter = '&' + experiment_model_dict['surveyUrl'] = "{0}{1}{2}".format(current_round.survey_url, delimiter, query_parameters) # participant data diff -r 875a72fb1f69c3110db6bdc8824757e091e40201 -r 94368fac5f228b55b5629dbd6e3491eb7abe8f01 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -38,6 +38,9 @@ """ class DefaultValue(object): + ''' + Simple object wrapper that returns the wrapped value on any attribute reference + ''' def __init__(self, value): self.value = value def __getattr__(self, name): @@ -1179,11 +1182,10 @@ def is_playable_round(self): return self.round_type in RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS + @property def is_survey_enabled(self): - try: - return self.survey_url is not None - except: - return False + survey_url = getattr(self, 'survey_url', None) + return survey_url is not None and survey_url def get_debriefing(self, participant_id=None, **kwargs): return self.templatize(self.debriefing, participant_id, kwargs) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-19 21:13:43
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/875a72fb1f69/ Changeset: 875a72fb1f69 Branch: stable User: alllee Date: 2013-04-19 23:13:30 Summary: adding docs fabric deployment target to publish to commons.asu.edu/api/vcweb Affected #: 1 file diff -r 290e08e505ebe0648ef2653a78b24904306b3960 -r 875a72fb1f69c3110db6bdc8824757e091e40201 fabfile.py --- a/fabfile.py +++ b/fabfile.py @@ -1,14 +1,17 @@ -from fabric.api import local, run, sudo, cd, env, hide +from fabric.api import local, run, sudo, cd, env, lcd, hosts +from fabric.context_managers import settings as fab_settings +from fabric.contrib import django from fabric.contrib.console import confirm -from fabric.contrib import django -from fabric.context_managers import settings as fab_settings +from fabric.contrib.project import rsync_project import os, sys, shutil, logging logger = logging.getLogger(__name__) +# default to current working directory +env.project_path = os.path.dirname(__file__) # needed to push vcweb.settings onto the path. -sys.path.append(os.path.abspath(os.path.dirname(__file__))) +sys.path.append(os.path.abspath(env.project_path)) # default env configuration env.python = 'python' @@ -19,12 +22,12 @@ env.database = 'default' #env.virtualenv_path = '/opt/virtualenvs/%(project_name)s' % env env.deploy_path = '/opt/' -# default to current working directory -env.project_path = os.path.dirname(__file__) env.hosts = ['localhost'] env.hg_url = 'https://bitbucket.org/virtualcommons/vcweb' env.apache = 'httpd' env.applist = ['core', 'forestry', 'boundaries', 'lighterprints'] +env.docs_path = os.path.join(env.project_path, 'docs') +env.remote_docs_path = '/home/csid/public_html/api/vcweb' env.apps = ' '.join(env.applist) # django integration for access to settings, etc. @@ -39,6 +42,16 @@ '%(python)s manage.py migrate' % env, ] + +@hosts('cs...@co...') +def docs(): + with lcd(env.docs_path): + local("/usr/bin/make html") + rsync_project(env.remote_docs_path, 'build/html/') + with cd(env.remote_docs_path): + run('find . -type d -exec chmod a+rx {} \; && chmod -R a+r .') + + def migrate(): local("{python} manage.py migrate".format(python=env.python), capture=False) @@ -170,13 +183,15 @@ push() if confirm("Deploy to %(hosts)s ?" % env): with cd(env.project_path): - sudo_chain('hg pull && hg up -C', + sudo_chain( + 'hg pull && hg up -C', 'chmod g+s logs', 'chmod -R g+rw logs/', user=env.deploy_user, pty=True) env.static_root = vcweb_settings.STATIC_ROOT _virtualenv(run,'%(python)s manage.py collectstatic' % env) - sudo_chain('chmod -R ug+rw .', + sudo_chain( + 'chmod -R ug+rw .', 'find %(static_root)s -type d -exec chmod a+x {} \;' % env, 'find %(static_root)s -type f -exec chmod a+r {} \;' % env, 'find . -type d -exec chmod ug+x {} \;', Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-19 07:58:17
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/3600bf9a697f/ Changeset: 3600bf9a697f User: alllee Date: 2013-04-19 09:58:02 Summary: brushing off sphinx rST docs for further documentation Affected #: 3 files diff -r de8fa66a6ddfd5e36d0c70120564fd649ffc0c21 -r 3600bf9a697f8907d7cb823adc39d13c0d8af005 docs/source/conf.py --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,7 +43,7 @@ # General information about the project. project = u'vcweb' -copyright = u'2011, Allen Lee' +copyright = u'2013, Arizona Board of Regents' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff -r de8fa66a6ddfd5e36d0c70120564fd649ffc0c21 -r 3600bf9a697f8907d7cb823adc39d13c0d8af005 docs/source/core.rst --- a/docs/source/core.rst +++ b/docs/source/core.rst @@ -6,15 +6,12 @@ 1. participant data generated during the course of a round 2. group data shared across each participant in the group - 3. round and experiment configuration data. + 3. round and experiment configuration parameterizations -Using the forestry experiment as a concrete example, participant data may consist of -a harvest decision in a given round or a chat message sent during a communication -round. Examples of group shared data are the current resource level for a given -group and the amount of regrowth experienced by the group. Round and experiment -configuration data include how many initial trees are provided at the start of the -experiment, how many rounds the experiment should consist of as well as the explicit -ordering of the rounds. +Using the forestry experiment as a concrete example, participant data consists of a harvest decision in a given round or +a chat message. Group shared data consists of the current resource level for a given group and the amount of regrowth +experienced by the group. Round and experiment configuration parameterization data includes how many initial trees are +provided at the start of the experiment, the regrowth rate, the number of rounds, etc. .. automodule:: vcweb.core.models :members: diff -r de8fa66a6ddfd5e36d0c70120564fd649ffc0c21 -r 3600bf9a697f8907d7cb823adc39d13c0d8af005 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -521,17 +521,6 @@ return ChatMessage.objects.filter(round_data__experiment=self).reverse() @property - def all_quiz_questions(self): - # FIXME: use generator expression? - quiz_questions = list(self.default_quiz_questions.all()) - quiz_questions.extend(self.current_round.quiz_questions.all()) - return quiz_questions - - @property - def current_round_quiz_questions(self): - return self.current_round.quiz_questions - - @property def next_round(self): if self.has_next_round: return self.get_round_configuration(self.current_round_sequence_number + 1) @@ -1210,6 +1199,10 @@ ordering = [ 'experiment_configuration', 'sequence_number', 'date_created' ] class QuizQuestion(models.Model): + ''' + FIXME: deprecated, place quiz and quiz logic inline in the client, have the form submit the responses and correct + answers to the server-side. + ''' label = models.CharField(max_length=512) answer = models.CharField(max_length=64) input_type = models.CharField(max_length=32) @@ -1739,24 +1732,6 @@ birthdate = models.DateField(null=True, blank=True) address = models.ForeignKey(Address, null=True, blank=True) - ''' - FIXME: move these into a ParticipantQuerySet with PassThroughManager if needed - @property - def active_experiments(self): - return self.experiment_relationship_set.filter(experiment__status=Experiment.Status.ACTIVE) - - @property - def inactive_experiments(self): - return self.experiment_relationship_set.exclude(experiment__status=Experiment.Status.ACTIVE) - - @property - def completed_experiments(self): - return self.experiments_with_status(Experiment.Status.COMPLETED) - - def experiments_with_status(self, status=Experiment.Status.ACTIVE): - return self.experiment_relationship_set.filter(experiment__status=status) - ''' - class Meta: ordering = ['user'] Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-19 00:38:28
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/290e08e505eb/ Changeset: 290e08e505eb Branch: stable User: alllee Date: 2013-04-19 02:38:15 Summary: merging default back into stable branch Affected #: 91 files diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/fixtures/boundaries_parameters.json --- /dev/null +++ b/vcweb/boundaries/fixtures/boundaries_parameters.json @@ -0,0 +1,67 @@ +[ + { + "fields": { + "name":"player_status", + "experiment_metadata": 1, + "creator": 1, + "type": "int", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "participant" + }, + "model": "core.parameter", + "pk": 1111 + }, + { + "fields": { + "name":"regrowth_rate", + "experiment_metadata": 1, + "creator": 1, + "type": "float", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1112 + }, + { + "fields": { + "name":"cost_of_living", + "experiment_metadata": 1, + "creator": 1, + "type": "float", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1113 + }, + { + "fields": { + "name":"shared_resource", + "experiment_metadata": 1, + "creator": 1, + "type": "boolean", + "date_created": "2011-01-01 15:13:03", + "last_modified": "2011-01-01 15:13:05", + "scope": "round" + }, + "model": "core.parameter", + "pk": 1114 + }, + { + "fields": { + "name":"storage", + "experiment_metadata": 1, + "creator": 1, + "type": "int", + "date_created": "2011-01-01 15:13:07", + "last_modified": "2011-01-01 15:15:05", + "scope": "participant" + }, + "model": "core.parameter", + "pk": 1115 + } +] diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/migrations/0002_max_harvest_decision.py --- a/vcweb/boundaries/migrations/0002_max_harvest_decision.py +++ b/vcweb/boundaries/migrations/0002_max_harvest_decision.py @@ -159,7 +159,7 @@ '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']"}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.ExperimentConfiguration']"}), 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), @@ -323,7 +323,7 @@ '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']"}), + 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.ParticipantGroupRelationship']"}), 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': u"orm['core.RoundData']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'submitted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -387,7 +387,7 @@ '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']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.RoundConfiguration']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) }, u'core.spoolparticipantstatistics': { diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -1,10 +1,11 @@ from django.db.models import Sum from django.dispatch import receiver from vcweb.core import signals, simplecache -from vcweb.core.models import (ExperimentMetadata, Parameter, ParticipantRoundDataValue, GroupRelationship, GroupCluster, GroupClusterDataValue) +from vcweb.core.models import (ExperimentMetadata, Parameter, ParticipantRoundDataValue, GroupRelationship, + GroupCluster, GroupClusterDataValue, RoundData, RoundConfiguration) from vcweb.forestry.models import (get_harvest_decision_parameter, get_harvest_decision, get_harvest_decision_dv, get_regrowth_rate_parameter, - get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level, - get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, + get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level as get_unshared_resource_level, + get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, set_resource_level, get_resource_level_parameter, get_resource_level_dv as get_unshared_resource_level_dv, set_group_harvest, set_regrowth, set_harvest_decision) @@ -17,6 +18,9 @@ EXPERIMENT_METADATA_NAME = intern('bound') # constants that should live in configuration as well MAX_RESOURCE_LEVEL = 240 +MAX_SHARED_RESOURCE_LEVEL = 480 + +INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND = 3 ''' Experiment Parameters and Metadata Accessors @@ -79,8 +83,14 @@ return round_configuration.get_parameter_value(parameter=get_shared_resource_enabled_parameter(), default=False).boolean_value -def get_initial_resource_level(round_configuration, default=MAX_RESOURCE_LEVEL): - return forestry_initial_resource_level(round_configuration, default) +def get_max_resource_level(round_configuration): + ec = round_configuration.experiment_configuration +# FIXME: number of rounds currently hard coded to be 20 for regular rounds, 10 for practice rounds + number_of_rounds = 20 if round_configuration.is_regular_round else 10 + return INITIAL_RESOURCES_PER_PARTICIPANT_PER_ROUND * ec.max_group_size * number_of_rounds + +def get_initial_resource_level(round_configuration, default=None): + return get_max_resource_level(round_configuration) def should_reset_resource_level(round_configuration): return round_configuration.get_parameter_value(parameter=get_reset_resource_level_parameter(), @@ -94,19 +104,19 @@ return experiment_configuration.get_parameter_value(parameter=get_max_harvest_decision_parameter(), default=10).int_value def get_max_allowed_harvest_decision(participant_group_relationship, round_data=None, experiment_configuration=None): - group = participant_group_relationship.group - resource_level = get_resource_level(group, round_data) - return min(get_max_harvest_decision(experiment_configuration), resource_level / group.size) + return get_max_harvest_decision(experiment_configuration) +def get_resource_level(group, round_data=None, round_configuration=None, cluster=None): + return get_resource_level_dv(group, round_data, round_configuration, cluster).int_value ''' group data accessors ''' def get_average_harvest(group, round_data): - return get_total_harvest(group, round_data) / float(group.size) + return get_total_group_harvest(group, round_data) / float(group.size) def get_average_storage(group, round_data): return get_total_storage(group, round_data) / float(group.size) -def get_resource_level_dv(group, round_data=None, round_configuration=None): +def get_resource_level_dv(group, round_data=None, round_configuration=None, cluster=None): ''' Returns either the GroupClusterDataValue (shared resource condition) or the GroupRoundDataValue (standard resource per group condition) for the given group @@ -116,7 +126,7 @@ if round_configuration is None: round_configuration = round_data.round_configuration if is_shared_resource_enabled(round_configuration): - return get_shared_resource_level_dv(group, round_data) + return get_shared_resource_level_dv(group, round_data, cluster) else: return get_unshared_resource_level_dv(group, round_data) @@ -140,12 +150,36 @@ def get_storage(participant_group_relationship, round_data=None, default=0): - return get_storage_dv(participant_group_relationship, round_data, default).int_value + dv = get_storage_dv(participant_group_relationship, round_data, default) + return max(default if dv.int_value is None else dv.int_value, 0) + +def get_all_session_storages(experiment, participant_group_relationship): + debriefing_session_round_data = RoundData.objects.filter(experiment=experiment, + round_configuration__round_type=RoundConfiguration.RoundType.DEBRIEFING, + round_configuration__session_id__isnull=False) + return ParticipantRoundDataValue.objects.filter( + participant_group_relationship=participant_group_relationship, + parameter=get_storage_parameter(), + round_data__in=debriefing_session_round_data).order_by('date_created') + + +def _zero_if_none(value): + return 0 if value is None else value + +def get_total_group_harvest(group, round_data): + q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), round_data=round_data).aggregate(total_harvest=Sum('int_value')) + return _zero_if_none(q['total_harvest']) + + +def get_total_harvest(participant_group_relationship, session_id): + q = ParticipantRoundDataValue.objects.for_participant(participant_group_relationship, parameter=get_harvest_decision_parameter(), + participant_group_relationship__group__session_id=session_id).aggregate(total_harvest=Sum('int_value')) + return _zero_if_none(q['total_harvest']) # returns the sum of all stored resources for each member in the group def get_total_storage(group, round_data): q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_storage_parameter(), round_data=round_data).aggregate(total_storage=Sum('int_value')) - return q['total_storage'] + return _zero_if_none(q['total_storage']) def set_storage(participant_group_relationship, round_data, value): storage_dv = get_storage_dv(participant_group_relationship, round_data) @@ -177,75 +211,92 @@ logger.error("Received round started signal with no experiment: %s", sender) raise ValueError("Received round started signal with no experiment") round_configuration = experiment.current_round + round_data = experiment.get_round_data(round_configuration) logger.debug("setting up round %s", round_configuration) # initialize group and participant data values if round_configuration.is_playable_round: experiment.initialize_data_values( group_cluster_parameters=(get_regrowth_parameter(), get_resource_level_parameter(),), group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), - participant_parameters=(get_harvest_decision_parameter(), get_storage_parameter(), - get_player_status_parameter(),) + participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) ) +# check for dead participants and set their ready and harvest decision flags + deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), + round_data=round_data, boolean_value=False) + for prdv in deceased_participants: + pgr = prdv.participant_group_relationship + set_harvest_decision(pgr, 0, round_data, submitted=True) ''' during a practice or regular round, set up resource levels, participant harvest decision parameters, and group formation ''' if should_reset_resource_level(round_configuration): - initial_resource_level = get_initial_resource_level(round_configuration) + initial_resource_level = get_max_resource_level(round_configuration) logger.debug("Resetting resource level for %s to %d", round_configuration, initial_resource_level) - round_data = experiment.get_round_data(round_configuration) - for group in experiment.group_set.all(): + for group in experiment.group_set.filter(session_id=round_configuration.session_id): ''' set resource level to initial default ''' existing_resource_level = get_resource_level_dv(group, round_data, round_configuration) group.log( "Setting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) existing_resource_level.int_value = initial_resource_level existing_resource_level.save() - # FIXME: make sure that this is expected behavior - if the resource level is reset, reset storage to 0 + # FIXME: verify that this is expected behavior - if the resource level is reset, reset storage to 0 ParticipantRoundDataValue.objects.for_group(group, parameter=get_storage_parameter(), round_data=round_data).update(int_value=0) - # reset all player statuses to True + # reset all player statuses to alive ParticipantRoundDataValue.objects.for_group(group, parameter=get_player_status_parameter(), round_data=round_data).update(boolean_value=True) -def get_total_harvest(group, round_data): - q = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), round_data=round_data).aggregate(total_harvest=Sum('int_value')) - return q['total_harvest'] +def adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest, group_size=0): + if group_size == 0: + group_size = group.size +# pass in the group size to handle group cluster case + average_harvest = current_resource_level / group_size + group.log("GROUP HARVEST ADJUSTMENT - original total harvest: %s, resource level: %s, average harvest: %s" % + (total_harvest, current_resource_level, average_harvest)) + hds = ParticipantRoundDataValue.objects.for_group(group=group, parameter=get_harvest_decision_parameter(), + round_data=round_data, int_value__gt=0).order_by('int_value') + total_adjusted_harvest = 0 +# FIXME: should be the same as group.size + total_number_of_decisions = hds.count() + logger.debug("total number of decisions: %s - group size: %s", total_number_of_decisions, group_size) + decisions_allocated = 0 + for hd in hds: + if hd.int_value <= average_harvest: + group.log("preserving %s < average harvest" % hd) + total_adjusted_harvest += hd.int_value + else: +# now to assign the overs, find out how much resource level is remaining + remaining_resource_level = current_resource_level - total_adjusted_harvest + remaining_decisions = total_number_of_decisions - decisions_allocated + average_harvest = remaining_resource_level / remaining_decisions + hd.is_active = False + hd.save() + logger.debug("Assigning %s to hd %s", average_harvest, hd) + ParticipantRoundDataValue.objects.create(participant_group_relationship=hd.participant_group_relationship, + parameter=get_harvest_decision_parameter(), round_data=round_data, int_value=average_harvest, + submitted=True) + total_adjusted_harvest += average_harvest + decisions_allocated += 1 -def adjust_harvest_decisions(current_resource_level, group, group_size, round_data, total_harvest): - individual_harvest = current_resource_level / group_size - adjusted_harvest = individual_harvest * group_size - group.log( - "GROUP HARVEST ADJUSTMENT - original total harvest: %s, resource level: %s, individual harvest: %s, adjusted group harvest: %s" % - (total_harvest, current_resource_level, individual_harvest, adjusted_harvest)) - # deactivate old participant round data value decisions - ParticipantRoundDataValue.objects.for_group(group=group, - parameter=get_harvest_decision_parameter(), - round_data=round_data).update(is_active=False) - # create new harvest decision data values - for pgr in group.participant_group_relationship_set.all(): - ParticipantRoundDataValue.objects.create(round_data=round_data, - participant_group_relationship=pgr, - parameter=get_harvest_decision_parameter(), - int_value=individual_harvest) - return adjusted_harvest + logger.debug("harvested total %s", total_adjusted_harvest) + return total_adjusted_harvest -def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=MAX_RESOURCE_LEVEL): - current_resource_level_dv = get_resource_level_dv(group) +def update_resource_level(experiment, group, round_data, regrowth_rate, max_resource_level=None): + if max_resource_level is None: + max_resource_level = get_max_resource_level(round_data.round_configuration) + current_resource_level_dv = get_resource_level_dv(group, round_data) current_resource_level = current_resource_level_dv.int_value # FIXME: would be nicer to extend Group behavior and have group.get_total_harvest() instead of -# get_total_harvest(group, ...) - total_harvest = get_total_harvest(group, round_data) +# get_total_group_harvest(group, ...), see if we can enable this dynamically + total_harvest = get_total_group_harvest(group, round_data) logger.debug("Harvest: total group harvest for playable round: %s", total_harvest) - if current_resource_level > 0 and total_harvest > 0: + if current_resource_level > 0: if total_harvest > current_resource_level: - # divide remaining trees evenly among every participant - group_size = group.size - adjusted_harvest = adjust_harvest_decisions(current_resource_level, group, group_size, round_data, - total_harvest) + adjusted_harvest = adjust_harvest_decisions(current_resource_level, group, round_data, total_harvest) total_harvest = adjusted_harvest group.log("Harvest: removing %s from current resource level %s" % (total_harvest, current_resource_level)) @@ -254,8 +305,20 @@ resource_regrowth = calculate_regrowth(current_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to current resource level %s" % (resource_regrowth, current_resource_level)) set_regrowth(group, resource_regrowth, round_data) + # clamp resource current_resource_level_dv.int_value = min(current_resource_level + resource_regrowth, max_resource_level) current_resource_level_dv.save() + else: + group.log("current resource level is 0, no one can harvest") + set_group_harvest(group, 0, round_data) + ParticipantRoundDataValue.objects.for_group(group, parameter=get_harvest_decision_parameter(), + round_data=round_data).update(is_active=False) + for pgr in group.participant_group_relationship_set.all(): + # Create adjusted data values + ParticipantRoundDataValue.objects.create(participant_group_relationship=pgr, + round_data=round_data, parameter=get_harvest_decision_parameter(), + int_value=0) + ''' XXX: transfer resource levels across chat and quiz rounds if they exist ''' if experiment.has_next_round: @@ -264,58 +327,62 @@ group.copy_to_next_round(current_resource_level_dv) -# FIXME: try to reduce duplication between this and update_resource_level -def update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate, max_resource_level=MAX_RESOURCE_LEVEL): +# FIXME: reduce duplication between this and update_resource_level +def update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate, max_resource_level=None): + logger.debug("updating shared resource level") + if max_resource_level is None: + max_resource_level = get_max_resource_level(round_data.round_configuration) + max_resource_level = max_resource_level * group_cluster.size shared_resource_level_dv = get_shared_resource_level_dv(cluster=group_cluster, round_data=round_data) shared_resource_level = shared_resource_level_dv.int_value shared_group_harvest = 0 - total_group_size = 0 + group_cluster_size = 0 group_harvest_dict = {} for group_relationship in group_cluster.group_relationship_set.all(): group = group_relationship.group - total_group_size += group.size - group_harvest = get_total_harvest(group, round_data) + group_cluster_size += group.size + group_harvest = get_total_group_harvest(group, round_data) group_harvest_dict[group] = group_harvest shared_group_harvest += group_harvest group.log("total group harvest: %s" % group_harvest) for group, group_harvest in group_harvest_dict.items(): if shared_group_harvest > shared_resource_level: # adjust each individual harvest for each group in this cluster - group_harvest = adjust_harvest_decisions(shared_resource_level, group, total_group_size, round_data, group_harvest) + group_harvest = adjust_harvest_decisions(shared_resource_level, group, round_data, group_harvest, group_size=group_cluster_size) set_group_harvest(group, group_harvest, round_data) shared_resource_level = shared_resource_level - group_harvest # set regrowth after shared_resource_level has been modified by all groups in this cluster resource_regrowth = calculate_regrowth(shared_resource_level, regrowth_rate, max_resource_level) group.log("Regrowth: adding %s to shared resource level %s" % (resource_regrowth, shared_resource_level)) - group_cluster.set_data_value(parameter=get_regrowth_parameter(), round_data=round_data, - value=resource_regrowth) + group_cluster.set_data_value(parameter=get_regrowth_parameter(), round_data=round_data, value=resource_regrowth) + # clamp resource level to max_resource_level shared_resource_level_dv.int_value = min(shared_resource_level + resource_regrowth, max_resource_level) shared_resource_level_dv.save() if experiment.has_next_round: ''' transfer shared resource levels to next round ''' - group.log("Transferring shared resource level %s to next round" % current_resource_level_dv.int_value) + group.log("Transferring shared resource level %s to next round" % shared_resource_level_dv.int_value) group_cluster.copy_to_next_round(shared_resource_level_dv) -def update_participants(experiment, round_data): - cost_of_living = get_cost_of_living(round_data.round_configuration) +def update_participants(experiment, round_data, round_configuration): + logger.debug("updating participants") + cost_of_living = get_cost_of_living(round_configuration) + next_round_data, created = experiment.round_data_set.get_or_create(round_configuration=experiment.next_round) for pgr in experiment.participant_group_relationships: player_status_dv = get_player_status_dv(pgr, round_data) + storage_dv = get_storage_dv(pgr, round_data) player_alive = player_status_dv.boolean_value - if not player_alive: - logger.debug("Skipping deceased participant %s", pgr) - continue - harvest_decision = get_harvest_decision(pgr, round_data) - storage_dv = get_storage_dv(pgr, round_data) - updated_storage = storage_dv.int_value + harvest_decision - cost_of_living - if updated_storage < 0: - # player has "died" - player_status_dv.boolean_value = False - player_status_dv.save() - storage_dv.int_value = updated_storage - storage_dv.save() - logger.debug("updating participant %s (storage: %s, harvest: %s, status: %s)", pgr, storage_dv.int_value, - harvest_decision, player_status_dv.boolean_value) - pgr.copy_to_next_round(player_status_dv, storage_dv) + if player_alive: + harvest_decision = get_harvest_decision(pgr, round_data) + updated_storage = storage_dv.int_value + harvest_decision - cost_of_living + if updated_storage < 0: + # player has "died" + player_status_dv.boolean_value = False + player_status_dv.save() + storage_dv.int_value = updated_storage + storage_dv.save() + logger.debug("updating participant %s (storage: %s, harvest: %s, status: %s)", pgr, storage_dv.int_value, + harvest_decision, player_status_dv.boolean_value) + pgr.copy_to_next_round(player_status_dv, storage_dv, next_round_data=next_round_data) @receiver(signals.round_ended, sender=EXPERIMENT_METADATA_NAME) def round_ended_handler(sender, experiment=None, **kwargs): @@ -328,20 +395,32 @@ logger.debug("ending boundaries round: %s", round_configuration) if round_configuration.is_playable_round: regrowth_rate = get_regrowth_rate(round_configuration) + harvest_decision_parameter = get_harvest_decision_parameter() # zero out unsubmitted harvest decisions - ParticipantRoundDataValue.objects.filter(round_data=round_data, parameter=get_harvest_decision_parameter(), - submitted=False).update(int_value=0) + for pgr in experiment.participant_group_relationships: + # FIXME: not thread-safe but this *should* only be invoked once per experiment. If we start getting + # spurious data values, revisit this section + prdv, created = ParticipantRoundDataValue.objects.get_or_create( + round_data=round_data, + participant_group_relationship=pgr, + parameter=harvest_decision_parameter, + is_active=True, + defaults={ 'int_value': 0 }) + if created: + logger.debug("created new harvest decision prdv %s for participant %s", prdv, pgr) + # FIXME: generify and merge update_shared_resource_level and update_resource_level to operate on "group-like" objects if possible if is_shared_resource_enabled(round_configuration): - for group_cluster in GroupCluster.objects.for_experiment(experiment, - session_id=round_configuration.session_id): + for group_cluster in GroupCluster.objects.for_experiment(experiment, session_id=round_configuration.session_id): update_shared_resource_level(experiment, group_cluster, round_data, regrowth_rate) else: - for group in experiment.group_set.all(): + for group in experiment.group_set.filter(session_id=round_configuration.session_id): update_resource_level(experiment, group, round_data, regrowth_rate) - update_participants(experiment, round_data) + update_participants(experiment, round_data, round_configuration) -def calculate_regrowth(resource_level, regrowth_rate, max_resource_level=MAX_RESOURCE_LEVEL): +def calculate_regrowth(resource_level, regrowth_rate, max_resource_level): + if resource_level == max_resource_level: + return 0 return (regrowth_rate * resource_level) * (1 - (resource_level / float(max_resource_level))) diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/static/css/boundaries/style.css --- /dev/null +++ b/vcweb/boundaries/static/css/boundaries/style.css @@ -0,0 +1,48 @@ +.chat-sidebar { + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + padding: 0px 0px 0px 0px; + margin: 0px; +} +.chat-messages { + padding: 5px; +} +#chat-div { + overflow: auto; + height: 820px; +} +#chatMessage { +} +.boundaries-status-dashboard p { + padding-top: 15px; + font-size: 2em; +} +.boundaries-status-dashboard h4 { + border-bottom: 1px solid #ccc; +} +.boundaries-status-dashboard { + height: 100px; + width: 110px; + margin-right: 0px; + padding: 8px 8px 8px 8px; +} +.harvest-decision-resources td { + cursor: pointer; +} +.harvest-decision-tree { + padding: 3px 3px 3px 8px; + margin: 5px 5px 5px 5px; +} +.harvest-decision-tree:hover { + border: 1px solid #ddd; + border-radius: 4px; + -webkit-border-radius: 4px; + background-color: #dff0d8; +} +col.current-player { + background-color: #dff0d8; +} +.current-player { + font-weight: bold; + font-size: 1em; +} diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/static/images/boundaries/deciduous-tree.png Binary file vcweb/boundaries/static/images/boundaries/deciduous-tree.png has changed diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -5,6 +5,7 @@ <link rel='stylesheet' href='{% static "css/boundaries/style.css" %}'/> {% endblock %} {% block content %} +<h3><span data-bind='text: roundSequenceLabel'></span></h3><div data-bind='template: { name: templateId(), afterRender: afterRenderTemplate }'></div><div id='progress-modal' class='modal hide fade'> @@ -26,20 +27,18 @@ <i class='icon-warning-sign'></i> Chat is currently disabled. </div></div> -<form id="chat-form" class='form-inline'> - <div class='input-prepend input-append'> +<form id="chat-form" class='form-inline' data-bind='submit: submitChatMessage' > + <div class='input-prepend input-block-level'><span class='add-on'><i class='text-info icon-comment'></i></span> - <input id='chatMessage' type="text" placeholder="Enter a chat message"> - <button class='btn btn-primary' data-bind='click: submitChatMessage'>Send</button> + <input id='chatMessage' type="text" placeholder="Chat with your group"></div></form><div class='chat-sidebar'><div id='chat-div'><div class='chat-messages' data-bind='foreach: chatMessages'> - <i class='icon-user muted'></i><strong>Participant <span data-bind='text: participant_number'></strong><b class='pull-right muted' data-bind='text: date_created'></b> - <p> + <i class='icon-user muted'></i><strong data-bind='text: participant_number'></strong><small><i class='icon-double-angle-right'></i></small><span data-bind='text: message'></span> - </p> + <br></div></div></div> @@ -48,6 +47,26 @@ {% block javascript %} {{ block.super }} <!-- ko templates --> +<script type='text/html' id='TREATMENT_RESULTS'> + <h3>Session <span data-bind='text: sessionId'></span> Summary</h3> + <table class='table table-compact'> + <thead> + <tr><th>Trees left in the forest</th><th>Trees harvested</th><th>Trees in storage</th><th>Earnings</th></tr> + </thead> + <tbody> + <tr><td data-bind='text: resourceLevel'></td><td data-bind='text: totalHarvest'></td><td data-bind='text:storage'></td><td class='text-success' data-bind='text: totalEarnings'></td></tr> + </tbody> + </table> + <div class='alert alert-info'> + <i class='icon-info-sign'></i> Remember, only <b>one</b> of your sessions will be selected at random for + payment. We will now move on to the next session. + </div> + <ul class='pager'> + <li class='next'> + <a href='javascript: void()' data-bind='click: participantReady.bind($data, participantHandle() + " finished reading session summary.") '>Ok, I'm ready</a> + </li> + </ul> +</script><script type='text/html' id='PRACTICE_ROUND_RESULTS'><h3>Practice Round Results</h3><p> @@ -94,10 +113,30 @@ <a href='javascript: void()' data-bind='click: activateTemplate.bind($data, "PRACTICE_ROUND_RESULTS")'>Back</a></li><li class='next'> - <a href='javascript: void()' data-bind='click: participantReady'>Ok, I understand</a> + <a href='javascript: void()' data-bind='click: participantReady.bind($data, participantHandle() + " finished paid experiment instructions") '>Ok, I understand</a></li></ul></script> +<script type='text/html' id='FINAL_DEBRIEFING'> + <h2>Experiment Results</h2> + <p> + Thank you for participating. As you recall from the initial instructions, we will pay you for one of the two + experimental sessions you just completed. You will select, at random, which session you will be paid for by + flipping a coin. The experimenter will be with you shortly to assist you with this process. While you wait, please + <a target='_blank' data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a> + </p> + <h2>Payments</h2> + <table class='table'> + <thead> + <tr><th>Session 1</th><th>Session 2</th></tr> + </thead> + <tbody> + <tr><td><span data-bind='text: sessionOneStorage'></span><br><span data-bind='text: sessionOneEarnings'</span></td> + <td><span data-bind='text: sessionTwoStorage'></span><br><span data-bind='text: sessionTwoEarnings'></span></td> + </tr> + </tbody> + </table> +</script><script type='text/html' id='TREATMENT_A_INSTRUCTIONS'><h3>Session <span data-bind='text: sessionId'></span> Instructions</h3><p> @@ -114,7 +153,24 @@ </ul><ul class='pager'><li class='next'> - <a href='javascript: void()' data-bind='click: participantReady'>Ok, I am ready to start</a> + <a href='javascript: void()' data-bind='click: participantReady.bind($data, participantHandle() + " finished treatment A instructions")'>Ok, I am ready to start</a> + </li> +</ul> +</script> +<script type='text/html' id='TREATMENT_D_INSTRUCTIONS'> +<h3>Session <span data-bind='text: sessionId'></span> Instructions</h3> +<p> +In this session, there are two groups or villages. Both groups are sharing a forest to harvest. The shared forest has +twice the capacity of a single-group forest. +</p> +<h3>Key points</h3> +<ul> + <li>Two groups</li> + <li>Groups share a forest</li> +</ul> +<ul class='pager'> + <li class='next'> + <a href='javascript: void()' data-bind='click: participantReady.bind($data, participantHandle() + " finished treatment D instructions")'>Ok, I am ready to start</a></li></ul></script> @@ -124,7 +180,7 @@ Please wait. The experiment will continue shortly once all the participants are ready. <p><span class='badge' -data-bind='css: { "badge-important": readyParticipants() < (totalNumberOfParticipants() / 2), "badge-success": readyParticipants() > (totalNumberOfParticipants() / 2)}, text: readyParticipants'></span> of <span class='badge badge-success' data-bind='text: totalNumberOfParticipants'></span> participants are ready. +data-bind='css: { "badge-important": readyParticipants() < (participantCount() / 2), "badge-success": readyParticipants() > (participantCount() / 2)}, text: readyParticipants'></span> of <span class='badge badge-success' data-bind='text: participantCount'></span> participants are ready. </p></div></div> @@ -265,7 +321,7 @@ <a href='javascript:void();' data-bind='click: activateTemplate.bind($data, "GENERAL_INSTRUCTIONS3")'>Back</a></li><li class='next'> -<a href='javascript: void();' data-bind='click: participantReady'>I have fully read and understand these instructions</a> +<a href='javascript: void();' data-bind='click: participantReady.bind($data, participantHandle() + " finished practice round instructions")'>I have fully read and understand these instructions</a></li></ul></script> @@ -275,19 +331,19 @@ <h3>My Status</h3><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> + <h4 id="dashboard-last-harvest">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> + <div class='alert boundaries-status-dashboard span2'> + <h4 id='dashboard-storage'>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> + <h4 id='dashboard-time'><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 @@ -299,10 +355,16 @@ <div class='span4'><h3>Group Status</h3><table class='table table-bordered table-condensed'> + <colgroup> + <col> + <!-- ko foreach: playerData --> + <col data-bind='css: { "current-player": id() === $root.participantGroupId() }'> + <!-- /ko --> + </colgroup><thead><tr><th>Player</th><!-- ko foreach: playerData --> - <th data-bind='text: id'></th> + <th data-bind='text: number'></th><!-- /ko --></tr></thead> @@ -310,13 +372,19 @@ <tr><td>Last harvest</td><!-- ko foreach: playerData --> - <td data-bind='text: lastHarvestDecision'></td> + <td data-bind='text: lastHarvestDecision, css: { "current-player": id() === $root.participantGroupId() }'></td><!-- /ko --></tr><tr><td>Storage</td><!-- ko foreach: playerData --> - <td data-bind='text: storage'></td> + <td data-bind='text: storage, css: { "current-player": id() === $root.participantGroupId() }'></td> + <!-- /ko --> + </tr> + <tr> + <td>Status</td> + <!-- ko foreach: playerData --> + <td data-bind='css: { "current-player": id() === $root.participantGroupId(), "text-error": ! alive(), "text-success": alive() }, text: alive() ? "Alive" : "Deceased"'></td><!-- /ko --></tr></tbody> @@ -333,22 +401,24 @@ <div data-bind="ifnot: isResourceEmpty" id='resourceDisplay'><div class='alert alert-info'>There are <strong class='badge badge-success' data-bind='text:resourceLevel'></strong> trees remaining in the forest.</div> + <div class='well'><div data-bind='style: { width: resourceImageWidth, height: resourceImageHeight, background: resourceImageBackgroundUrl }'> </div> + </div></div><div data-bind="if: isResourceEmpty"> + <div class='well'> + <center><img src="{% static 'images/forestry/deforestation.jpg' %}" class="img-polaroid" width="425" + height="282"></center> + </div> + <!-- this centers the image <div style='padding: 8px; margin: auto; border: solid 1px; background: url("{{STATIC_URL}}images/forestry/deforestation.jpg") no-repeat center; height: 282px; width:425px;'> </div> - <div class='alert alert-error'>There are no more trees to harvest. Please wait until the next round begins.</div> + --> + <div class='alert alert-error'><i class='icon-warning-sign'></i> There are no more trees to harvest. Please wait until the next round begins.</div></div> -<div data-bind='if: submitted'> - <div class='alert alert-error'> - You have submitted a harvest decision for <span class='badge badge-success' data-bind='text: harvestDecision'></span> trees. - </div> -</div> - <div data-bind='if: canObserveOtherGroup'><h3>Observe Other Group</h3><div class='row'> @@ -377,37 +447,76 @@ </div></div></div> +<div data-bind='visible: ! isResourceEmpty()'><h3>Harvest</h3> +<div data-bind='if: submitted'> + <div class='alert alert-success'> + <i class='icon-leaf'></i> You have submitted a harvest decision for <span class='badge badge-info' + data-bind='text: harvestDecision'></span> trees. Please wait quietly while the rest of the participants submit + their decisions. + </div> +</div> +<div data-bind='ifnot: alive'> + <div class='alert alert-error'> + <b><i class='icon-ambulance'></i></b> You didn't harvest enough trees to meet the cost of living and are now + <b>deceased</b>. You will not be able to chat or submit harvest decisions until this session is over. + </div> +</div><div data-bind='ifnot: isResourceEmpty'> - <form id='vcweb-form' action='' method='post' class='form-horizontal' > + <form id='vcweb-form' data-bind='submit: submitDecision' action='' method='post' class='form-horizontal' > {% csrf_token %} - <div class='control-group'> - <div class='input-prepend'> - <span class='add-on'><i class='icon-leaf'></i></span> - <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: participantGroupId'/> - <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: harvestDecision'> - <select id='harvestDecisionSelect' required="required" form="vcweb-form" data-bind='value: harvestDecision'> - <!-- ko foreach: ko.utils.range(0, maxHarvestDecision()) --> - <option data-bind='value: $data, text: $data'></option> - <!-- /ko --> - </select> - - </div> - <button id='submitDecision' data-bind='click: submitDecision' type='submit' class='btn btn-primary'>Submit</button> + <div id='harvestDecisionDiv' data-bind='visible: !submitted()' class='control-group'> + <div data-bind='template: { name: "harvest-decision-multi-select-template" }'></div></div></form></div> +</div></script> +<script type='text/html' id='harvest-decision-multi-select-template'> + <table class='table'> + <tr class='row harvest-decision-resources' data-bind='foreach: harvestDecisionOptions'> + <td data-bind='attr: { id: "harvest-decision-td-" + $data }'> + <form data-bind='attr: { id: "multi-select-decision-form" + $data }'> + <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: $root.participantGroupId'/> + <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: $data'> + <div class='harvest-decision-tree' data-bind='click: $root.submitDecision.bind($data, "#multi-select-decision-form" + $data)'> + <img width="40" height="40" data-bind='attr: { src: $root.resourceImageUrl(), alt: "Harvest " + $data + " tree(s)." }'><br> + <span style='margin-left: 2px;' class='badge badge-success' data-bind='text: $data'></span> + </div> + </form> + </td> + </tr> + </table> +</script> +<script type='text/html' id='harvest-decision-select-template'> + <div class='input-prepend'> + <span class='add-on'><i class='icon-leaf'></i></span> + <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: participantGroupId'/> + <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: harvestDecision'> + <select id='harvestDecisionSelect' name='harvest_decision' required="required" form="vcweb-form" data-bind='options: harvestDecisionOptions, value: harvestDecision'> + </select> + </div> + <button id='submitDecisionButton' type='submit' class='btn btn-primary'>Submit</button> +</script> +<script src='{% static "js/bootstrap-tour.js" %}'></script><script type="text/javascript"> $(function() { function ExperimentModel(experimentModelJson) { var self = this; var model = ko.mapping.fromJS(experimentModelJson); + model.harvestDecisionOptions = ko.computed(function() { + return ko.utils.range(0, model.maxHarvestDecision()); + }); + model.calculateEarnings = function(quantity) { + return function() { + return formatCurrency(quantity * model.exchangeRate()); + } + } model.secondsLeft = ko.observable(0); model.currentInterval = ko.observable(); - model.totalEarnings = ko.computed(function() { - return formatCurrency(model.storage() * model.exchangeRate()); - }); + model.sessionOneEarnings = ko.computed(model.calculateEarnings(model.sessionOneStorage())); + model.sessionTwoEarnings = ko.computed(model.calculateEarnings(model.sessionTwoStorage())); + model.totalEarnings = ko.computed(model.calculateEarnings(model.storage())); model.templateId = ko.computed(function() { switch ( model.templateName() ) { case 'PRACTICE': @@ -416,6 +525,28 @@ return model.templateName(); } }); + model.setupTour = function() { + var tour = new Tour(); + tour.addStep({ element: "#dashboard-last-harvest", title: "Last harvest", + placement: "top", + content: "Your last round's harvest decision will be displayed here. Since this is the first practice round this is currently 0." + }); + tour.addStep({ element: "#dashboard-storage", title: "Storage", + placement: "top", + content: "Your total storage will be displayed here. You must have at least " + + model.costOfLiving() + " trees stored to survive each round."}); + tour.addStep({ element: "#dashboard-time", title: "Time Left", + placement: "top", + content: "The time remaining in this round will be displayed here. It will turn red when " + + model.warningCountdownTime() + " seconds are left."}); + tour.addStep({ element: "#chatMessage", title: "Text Chat", + placement: "left", + content: "If chat is enabled, you can communicate with your group by typing messages into this box and hitting the enter key or clicking 'Send'."}); + tour.addStep({ element: "#harvestDecisionSelect", title: "Harvest Decision", + placement: "top", + content: "Enter the number of trees you'd like to harvest here. For the purposes of this practice round, please select 10 now and click the 'Submit' button."}); + tour.start(); + } model.setCurrentInterval = function(intervalId) { model.clearCurrentInterval(); model.currentInterval(intervalId); @@ -438,14 +569,14 @@ model.templateName(name); } model.readyParticipantsPercentage = ko.computed(function() { - return (model.readyParticipants() / model.totalNumberOfParticipants()) * 100; + return (model.readyParticipants() / model.participantCount()) * 100; }); - model.participantReady = function() { + model.participantReady = function(message) { $.post('/experiment/participant-ready', { participant_group_id: model.participantGroupId() }, function(response) { console.debug("successfully posted to server, notifying sockjs"); console.debug(response); model.readyParticipants(response.number_of_ready_participants); - getWebSocket().send(createReadyEvent("Participant finished reading instructions")); + getWebSocket().send(createReadyEvent(message)); model.activateTemplate("WAITING_ROOM"); }); } @@ -463,18 +594,24 @@ } return 0; }); - model.resourceImageUrl = ko.observable("{{STATIC_URL}}images/forestry/pine-tree.png"); + model.resourceImageUrl = ko.observable("{{STATIC_URL}}images/boundaries/deciduous-tree.png"); model.resourceImageBackgroundUrl = ko.observable("url('{{STATIC_URL}}images/forestry/pine-tree.png')"); model.resourceImageWidth = ko.computed(function() { return model.resourcesToDisplay() * 30 + "px"; }); model.resourceImageHeight = ko.observable("79px"); model.startRound = function() { + // reset other group view + model.shouldShowOtherGroup(false); + if (model.showTour()) { + model.setupTour(); + } if (model.submitted() || ! model.alive()) { model.disableHarvestForm(); model.disableChatForm(); return; } + model.enableHarvestForm(); console.debug("starting round"); if (isInt(model.timeRemaining()) && model.timeRemaining() > 0) { model.setupChat(); @@ -482,12 +619,6 @@ model.setCurrentInterval( setInterval(function() { model.tick(); - if (model.secondsLeft() > 30 && ! model.isPracticeRound()) { - model.disableHarvestForm(); - } - else { - model.enableHarvestForm(); - } if (! model.isTimerRunning()) { model.submitDecision(); model.clearCurrentInterval(); @@ -496,31 +627,32 @@ 1000)); } }; - model.submitDecision = function(data, evt) { - model.enableHarvestForm(); - var formData = $('#vcweb-form').serialize(); - model.disableHarvestForm(); - model.disableChatForm(); + model.submitDecision = function(formId, numberOfTrees) { + formId = formId || "#vcweb-form"; + numberOfTrees = numberOfTrees || 0; + console.debug("formId: " + formId + " -- number of trees: " + numberOfTrees); + var formData = $(formId).serialize(); + model.submitted(true); + model.harvestDecision(numberOfTrees); console.debug(formData); $.post('submit-harvest-decision', formData, function(response) { console.log(response); - model.update($.parseJSON(response.experimentModelJson)); - // FIXME: forward the response message to the chat window should be handled completely on the server - // side instead of ping-ponging to the client side back to the real time server back out to all - // clients in this group + // model.update($.parseJSON(response.experimentModelJson)); getWebSocket().send(createReadyEvent(response.message)); model.clearCurrentInterval(); + model.secondsLeft(0); }); + return false; } model.submitChatMessage = function(data, evt) { - evt.preventDefault(); - console.log("clicking chat message"); + console.log("submitting chat message"); var message = $('#chatMessage').val(); if (message) { $('#chatMessage').val(''); getWebSocket().send(createMessageEvent(message)); $('#chatMessage').focus(); } + return false; } model.update = function() { $.get('view-model', { participant_group_id: model.participantGroupId() }, function(data) { @@ -539,10 +671,10 @@ $(formId + " :input").prop("disabled", disabled); } model.enableHarvestForm = function() { - model.setFormDisabled("#vcweb-form", false); + $('#harvestDecisionDiv').show(); } model.disableHarvestForm = function() { - model.setFormDisabled("#vcweb-form", true); + $('#harvestDecisionDiv').hide(); } model.enableChatForm = function() { model.setFormDisabled("#chat-form", false); @@ -551,14 +683,17 @@ model.setFormDisabled("#chat-form", true); } model.setupChat = function() { - var chatDisabled = model.isPracticeRound() || ! model.chatEnabled(); + var chatDisabled = model.isPracticeRound() || ! model.chatEnabled() || ! model.alive(); model.setFormDisabled("#chat-form", chatDisabled); + model.chatEnabled(! chatDisabled); } model.afterRenderTemplate = function(elements) { + console.debug("after render template: " + model.harvestDecision()); if (model.templateId() === "REGULAR") { model.startRound() } model.setupChat(); + $('[data-content]').popover({placement: 'top', trigger: 'hover'}); } return model; } @@ -566,14 +701,14 @@ var experimentModel = new ExperimentModel(experimentModelJson); ko.applyBindings(experimentModel); var s = connect(); - s.onmessage = function(json) { - console.debug(json); - var data = $.parseJSON(json.data); + s.onmessage = function(message) { + console.debug(message); + var data = $.parseJSON(message.data); console.debug(data); switch (data.event_type) { case 'chat': + console.debug("received chat message:" + message); experimentModel.chatMessages.unshift(data); - console.debug("received chat message:" + json); break; case 'update': $('#progress-modal').modal('show'); @@ -585,14 +720,14 @@ }); break; default: - console.debug("unhandled json message:" + json); + console.debug("unhandled message:" + message); break; } }; + return experimentModel; } - var experimentModelJson = $.parseJSON("{{ experimentModelJson|escapejs }}"); - initialize(experimentModelJson); - $('[data-content]').popover({placement: 'top', trigger: 'hover'}); + model = initialize($.parseJSON("{{ experimentModelJson|escapejs }}")); }); + var model; </script> {% endblock %} diff -r 88fd2ec2e64a6d9ee5769965c3de08b60c89cd17 -r 290e08e505ebe0648ef2653a78b24904306b3960 vcweb/boundaries/tests.py --- a/vcweb/boundaries/tests.py +++ b/vcweb/boundaries/tests.py @@ -1,12 +1,47 @@ """ Tests for boundaries experiment """ -from django.test import TestCase +from vcweb.core.tests import BaseVcwebTest from vcweb.core.models import Parameter -from vcweb.boundaries.models import get_experiment_metadata +from vcweb.boundaries.models import * -class BaseTest(TestCase): - fixtures = [ 'boundaries_experiment_metadata' ] +import logging +logger = logging.getLogger(__name__) + + +class BaseTest(BaseVcwebTest): + fixtures = [ 'boundaries_experiment_metadata', 'forestry_experiment_metadata', 'boundaries_parameters', ] + + def create_harvest_decisions(self, value=10): + for pgr in self.experiment.participant_group_relationships: + logger.debug("setting harvest decision for %s to %s", pgr, value) + set_harvest_decision(pgr, value, submitted=True) + + def setUp(self, **kwargs): + super(BaseTest, self).setUp(experiment_metadata=get_experiment_metadata(), **kwargs) + logger.debug("boundaries test loaded experiment %s", self.experiment) + +class AdjustHarvestDecisionsTest(BaseTest): + def test_adjust_harvest_decisions(self): + e = self.experiment + e.activate() + for rl in range(30, 40): + e.start_round() + self.create_harvest_decisions() + for g in e.groups: + set_resource_level(g, rl) + e.end_round() + for g in e.groups: + self.assertEqual(get_resource_level(g), 0) + for pgr in self.participant_group_relationships: + self.assertTrue(get_harvest_decision(pgr) <= 8) + +class MaxResourceLevelTest(BaseTest): + def test_max_resource_level(self): + e = self.experiment + e.activate() + self.assertEqual(get_max_resource_level(e.current_round), 5 * 3 * 20) + class InitialDataTest(BaseTest): def test_experiment_metadata(self): dif... [truncated message content] |
From: <com...@bi...> - 2013-04-18 07:22:12
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/de8fa66a6ddf/ Changeset: de8fa66a6ddf User: alllee Date: 2013-04-18 09:21:59 Summary: adding default values for sessionOne/Two storage variables and refactoring client-side earnings calculation a bit Affected #: 2 files diff -r 1d2208fef6ca0f87e292d28cd21146ff8f80dbb6 -r de8fa66a6ddfd5e36d0c70120564fd649ffc0c21 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -507,17 +507,16 @@ model.harvestDecisionOptions = ko.computed(function() { return ko.utils.range(0, model.maxHarvestDecision()); }); + model.calculateEarnings = function(quantity) { + return function() { + return formatCurrency(quantity * model.exchangeRate()); + } + } model.secondsLeft = ko.observable(0); model.currentInterval = ko.observable(); - model.sessionOneEarnings = ko.computed(function() { - return formatCurrency(model.sessionOneStorage() * model.exchangeRate()); - }); - model.sessionTwoEarnings = ko.computed(function() { - return formatCurrency(model.sessionTwoStorage() * model.exchangeRate()); - }); - model.totalEarnings = ko.computed(function() { - return formatCurrency(model.storage() * model.exchangeRate()); - }); + model.sessionOneEarnings = ko.computed(model.calculateEarnings(model.sessionOneStorage())); + model.sessionTwoEarnings = ko.computed(model.calculateEarnings(model.sessionTwoStorage())); + model.totalEarnings = ko.computed(model.calculateEarnings(model.storage())); model.templateId = ko.computed(function() { switch ( model.templateName() ) { case 'PRACTICE': diff -r 1d2208fef6ca0f87e292d28cd21146ff8f80dbb6 -r de8fa66a6ddfd5e36d0c70120564fd649ffc0c21 vcweb/boundaries/views.py --- a/vcweb/boundaries/views.py +++ b/vcweb/boundaries/views.py @@ -75,6 +75,8 @@ 'isInstructionsRound': False, 'waitThirtySeconds': False, 'totalHarvest': 0, + 'sessionOneStorage': 0, + 'sessionTwoStorage': 0, } # FIXME: need to distinguish between instructions / welcome rounds and practice/regular rounds def get_view_model_json(experiment, participant_group_relationship, **kwargs): Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-17 22:38:17
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/1d2208fef6ca/ Changeset: 1d2208fef6ca User: alllee Date: 2013-04-18 00:38:06 Summary: adding email to logout button Affected #: 1 file diff -r 6017c09d9c30f3958cb95befaa6dd40c6a2cd893 -r 1d2208fef6ca0f87e292d28cd21146ff8f80dbb6 vcweb/core/templates/base.html --- a/vcweb/core/templates/base.html +++ b/vcweb/core/templates/base.html @@ -48,10 +48,10 @@ <ul class='nav pull-right'> {% if request.user.is_authenticated %} <li class='{% active request login %} dropdown'> - <a class='dropdown-toggle' data-toggle='dropdown' href='#'><i class='icon-cog'></i><b class='caret'></b></a> + <a class='dropdown-toggle' data-toggle='dropdown' href='#'>{{request.user.email}}<i class='icon-cog'></i><b class='caret'></b></a><ul class='dropdown-menu'><li><a href='{{ profile }}'>Account</a></li> - <li><a href='{{ logout }}'>Logout ({{request.user.email}})</a></li> + <li><a href='{{ logout }}'>Logout</a></li></ul></li> {% else %} Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-17 22:37:30
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/6017c09d9c30/ Changeset: 6017c09d9c30 User: alllee Date: 2013-04-18 00:37:13 Summary: adding per-session storage query and payment screen templates Affected #: 3 files diff -r aa58fa5c53c194bd16bee3c10df8e52277ef7a6a -r 6017c09d9c30f3958cb95befaa6dd40c6a2cd893 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -1,7 +1,8 @@ from django.db.models import Sum from django.dispatch import receiver from vcweb.core import signals, simplecache -from vcweb.core.models import (ExperimentMetadata, Parameter, ParticipantRoundDataValue, GroupRelationship, GroupCluster, GroupClusterDataValue) +from vcweb.core.models import (ExperimentMetadata, Parameter, ParticipantRoundDataValue, GroupRelationship, + GroupCluster, GroupClusterDataValue, RoundData, RoundConfiguration) from vcweb.forestry.models import (get_harvest_decision_parameter, get_harvest_decision, get_harvest_decision_dv, get_regrowth_rate_parameter, get_group_harvest_parameter, get_reset_resource_level_parameter, get_resource_level as get_unshared_resource_level, get_initial_resource_level as forestry_initial_resource_level, get_regrowth_parameter, set_resource_level, @@ -152,6 +153,15 @@ dv = get_storage_dv(participant_group_relationship, round_data, default) return max(default if dv.int_value is None else dv.int_value, 0) +def get_all_session_storages(experiment, participant_group_relationship): + debriefing_session_round_data = RoundData.objects.filter(experiment=experiment, + round_configuration__round_type=RoundConfiguration.RoundType.DEBRIEFING, + round_configuration__session_id__isnull=False) + return ParticipantRoundDataValue.objects.filter( + participant_group_relationship=participant_group_relationship, + parameter=get_storage_parameter(), + round_data__in=debriefing_session_round_data).order_by('date_created') + def _zero_if_none(value): return 0 if value is None else value @@ -230,10 +240,10 @@ "Setting resource level (%s) to initial value [%s]" % (existing_resource_level, initial_resource_level)) existing_resource_level.int_value = initial_resource_level existing_resource_level.save() - # FIXME: make sure that this is expected behavior - if the resource level is reset, reset storage to 0 + # FIXME: verify that this is expected behavior - if the resource level is reset, reset storage to 0 ParticipantRoundDataValue.objects.for_group(group, parameter=get_storage_parameter(), round_data=round_data).update(int_value=0) - # reset all player statuses to True + # reset all player statuses to alive ParticipantRoundDataValue.objects.for_group(group, parameter=get_player_status_parameter(), round_data=round_data).update(boolean_value=True) diff -r aa58fa5c53c194bd16bee3c10df8e52277ef7a6a -r 6017c09d9c30f3958cb95befaa6dd40c6a2cd893 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -117,6 +117,26 @@ </li></ul></script> +<script type='text/html' id='FINAL_DEBRIEFING'> + <h2>Experiment Results</h2> + <p> + Thank you for participating. As you recall from the initial instructions, we will pay you for one of the two + experimental sessions you just completed. You will select, at random, which session you will be paid for by + flipping a coin. The experimenter will be with you shortly to assist you with this process. While you wait, please + <a target='_blank' data-bind='attr: { href: surveyUrl }'>fill out a final questionnaire by clicking this link.</a> + </p> + <h2>Payments</h2> + <table class='table'> + <thead> + <tr><th>Session 1</th><th>Session 2</th></tr> + </thead> + <tbody> + <tr><td><span data-bind='text: sessionOneStorage'></span><br><span data-bind='text: sessionOneEarnings'</span></td> + <td><span data-bind='text: sessionTwoStorage'></span><br><span data-bind='text: sessionTwoEarnings'></span></td> + </tr> + </tbody> + </table> +</script><script type='text/html' id='TREATMENT_A_INSTRUCTIONS'><h3>Session <span data-bind='text: sessionId'></span> Instructions</h3><p> @@ -489,6 +509,12 @@ }); model.secondsLeft = ko.observable(0); model.currentInterval = ko.observable(); + model.sessionOneEarnings = ko.computed(function() { + return formatCurrency(model.sessionOneStorage() * model.exchangeRate()); + }); + model.sessionTwoEarnings = ko.computed(function() { + return formatCurrency(model.sessionTwoStorage() * model.exchangeRate()); + }); model.totalEarnings = ko.computed(function() { return formatCurrency(model.storage() * model.exchangeRate()); }); diff -r aa58fa5c53c194bd16bee3c10df8e52277ef7a6a -r 6017c09d9c30f3958cb95befaa6dd40c6a2cd893 vcweb/boundaries/views.py --- a/vcweb/boundaries/views.py +++ b/vcweb/boundaries/views.py @@ -7,9 +7,11 @@ from vcweb.boundaries.forms import SingleIntegerDecisionForm from vcweb.boundaries.models import (get_experiment_metadata, get_regrowth_rate, get_max_allowed_harvest_decision, get_cost_of_living, get_resource_level, get_initial_resource_level, get_total_storage, get_storage, - get_last_harvest_decision, get_harvest_decision_dv, get_harvest_decision_parameter, set_harvest_decision, - can_observe_other_group, get_player_status, get_average_harvest, get_average_storage, get_total_harvest) + get_all_session_storages, get_last_harvest_decision, get_harvest_decision_dv, get_harvest_decision_parameter, + set_harvest_decision, can_observe_other_group, get_player_status, get_average_harvest, get_average_storage, + get_total_harvest) +from urllib import urlencode import logging logger = logging.getLogger(__name__) @@ -107,6 +109,18 @@ if current_round.is_debriefing_round: experiment_model_dict['totalHarvest'] = get_total_harvest(participant_group_relationship, current_round.session_id) + if experiment.is_last_round: + # add last round data + query_parameters = urlencode({ + 'pid': participant_group_relationship.pk, + 'eid': experiment.pk + }) + experiment_model_dict['surveyUrl'] = "{0}?{1}".format(current_round.survey_url, query_parameters) + (session_one_storage, session_two_storage) = get_all_session_storages(experiment, participant_group_relationship) + experiment_model_dict['sessionOneStorage'] = session_one_storage + experiment_model_dict['sessonTwoStorage'] = session_two_storage + + # participant data experiment_model_dict['participantNumber'] = participant_group_relationship.participant_number experiment_model_dict['participantGroupId'] = participant_group_relationship.pk Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-04-17 21:39:19
|
New issue 107: boundaries: query storage values for each session https://bitbucket.org/virtualcommons/vcweb/issue/107/boundaries-query-storage-values-for-each A Lee: Right now there is a single "storage" participant data value that keeps track of their current storage. At payment time we need to be able to look up both session's storages to display them. Possible solutions: 1. Store a session_x_storage value when we detect an "end of session" debriefing round. This has the benefit of being very easy to look up but then we need to add a session_x_storage parameter for each session, which is ugly. 2. Calculate storage dynamically by summing harvest decisions and subtracting cost of living across the appropriate rounds. 3. Look up storage value in debriefing rounds where session_id is not None (probably going with this one). Responsible: alllee |
From: A L. <iss...@bi...> - 2013-04-13 07:28:37
|
New issue 106: updated boundary effects UI https://bitbucket.org/virtualcommons/vcweb/issue/106/updated-boundary-effects-ui A Lee: develop a harvest decision UI similar to the netflix ratings UI, e.g., 1. grayed out trees with numbers on the bottom 2. hover highlights the tree and bounding box 3. click selects and stores the harvest decision 4. possible: allow multiple harvest decisions? Responsible: alllee |
From: A L. <iss...@bi...> - 2013-04-12 18:28:04
|
New issue 105: error in export data https://bitbucket.org/virtualcommons/vcweb/issue/105/error-in-export-data A Lee: Types: KeyError Value: u'.csv' Location: vcweb/core/views.py in download_data , line 426 vcweb/core/views.py ? in download_data (application) response = HttpResponse(content_type=mimetypes.types_map['.%s' % file_type]) Responsible: alllee |
From: <com...@bi...> - 2013-04-12 05:42:20
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/aa58fa5c53c1/ Changeset: aa58fa5c53c1 User: alllee Date: 2013-04-12 07:42:09 Summary: updating cron tasks and fixing issue 104 Affected #: 2 files diff -r 8492223fe4b895f2d69bdedad0072da695a120d6 -r aa58fa5c53c194bd16bee3c10df8e52277ef7a6a vcweb/core/cron.py --- a/vcweb/core/cron.py +++ b/vcweb/core/cron.py @@ -3,18 +3,18 @@ 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('@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()) +#@register('@hourly') +#def every_hour(): +# signals.hour_tick.send(sender=None, time=datetime.now()) -@register('@daily') +@register('1 0 * * *') def at_midnight(): signals.midnight_tick.send(sender=None, time=datetime.now()) -@register('@weekly') -def refresh_foursquare_categories(): - fetch_foursquare_categories(refresh=True) +#@register('@weekly') +#def refresh_foursquare_categories(): +# fetch_foursquare_categories(refresh=True) diff -r 8492223fe4b895f2d69bdedad0072da695a120d6 -r aa58fa5c53c194bd16bee3c10df8e52277ef7a6a vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -121,8 +121,6 @@ handles each minute tick """ logger.debug("handling minute tick signal at %s with kwargs %s", time, kwargs) - # inspect all active experiments and update their time left - Experiment.objects.increment_elapsed_time(status='ROUND_IN_PROGRESS') class ExperimentMetadataManager(models.Manager): def get_by_natural_key(self, key): Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-11 20:17:24
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/8492223fe4b8/ Changeset: 8492223fe4b8 User: alllee Date: 2013-04-11 19:51:49 Summary: adding ParameterValueMixin for reading round or experiment configuration parameter values, using same convention as data values, e.g., round_configuration.parameter_value_set returns the RelatedManager for all RoundParameterValues associated with that round configuration experiment_configuration.parameter_value_set returns the RelatedManager for all ExperimentParameterValues associated with that experiment configuration Affected #: 5 files diff -r 446cbad125632179a99e01c7244bc6739470bbdb -r 8492223fe4b895f2d69bdedad0072da695a120d6 vcweb/boundaries/migrations/0002_max_harvest_decision.py --- a/vcweb/boundaries/migrations/0002_max_harvest_decision.py +++ b/vcweb/boundaries/migrations/0002_max_harvest_decision.py @@ -159,7 +159,7 @@ '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']"}), + 'experiment_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.ExperimentConfiguration']"}), 'float_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'int_value': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), @@ -323,7 +323,7 @@ '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']"}), + 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': u"orm['core.ParticipantGroupRelationship']"}), 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': u"orm['core.RoundData']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'submitted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -387,7 +387,7 @@ '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']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': u"orm['core.RoundConfiguration']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) }, u'core.spoolparticipantstatistics': { diff -r 446cbad125632179a99e01c7244bc6739470bbdb -r 8492223fe4b895f2d69bdedad0072da695a120d6 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -43,7 +43,22 @@ def __getattr__(self, name): return self.value -class DataManagerMixin(object): +class ParameterValueMixin(object): + def get_parameter_value(self, parameter=None, name=None, default=None): + if parameter is None and name is None: + logger.error("Can't lookup parameter values with no name or parameter, returning default %s", default) + return DefaultValue(default) + parameter_value_set = self.parameter_value_set + try: + if parameter: + return parameter_value_set.get(parameter=parameter) + elif name: + return parameter_value_set.get(parameter__name=name) + except parameter_value_set.model.DoesNotExist as e: + logger.debug("no parameter value exists: (%s, %s) returning default %s", parameter, name, default) + return DefaultValue(default) + +class DataValueMixin(object): ''' this mixin will only work on model classes that expose an "experiment" property. ''' @@ -86,8 +101,6 @@ existing_dv.round_data = next_round_data existing_dv.save() - ''' - FIXME: these need more work to generify them properly def set_data_value(self, parameter=None, value=None, round_data=None, **kwargs): if parameter is None or value is None or round_data is None: raise ValueError("need parameter, value, and round data to set") @@ -95,7 +108,6 @@ dv.value = value dv.save() - ''' class AutoDateTimeField(models.DateTimeField): def pre_save(self, model_instance, add): @@ -219,7 +231,7 @@ date_created = models.DateTimeField(default=datetime.now) approved = models.BooleanField(default=False) -class ExperimentConfiguration(models.Model): +class ExperimentConfiguration(models.Model, ParameterValueMixin): """ The configuration for a given Experiment instance. One ExperimentConfiguration can be applied to many Experiment instances but can only be associated to a single ExperimentMetadata record. @@ -266,26 +278,13 @@ def namespace(self): return self.experiment_metadata.namespace - def get_parameter_value(self, parameter=None, name=None, default=None): - if parameter is None and name is None: - logger.error("Can't find a parameter value with no name or parameter, returning default") - return DefaultValue(default) - try: - if parameter: - return self.experiment_parameter_value_set.get(parameter=parameter) - elif name: - return self.experiment_parameter_value_set.get(parameter__name=name) - except ExperimentParameterValue.DoesNotExist as e: - logger.debug("no experiment configuration parameter value found: (%s, %s) returning default %s", parameter, - name, default) - return DefaultValue(default) def serialize(self, output_format='xml', **kwargs): if self.round_configuration_set.count() > 0: all_objects = [] for rc in self.round_configuration_set.all(): all_objects.append(rc) - all_objects.extend(rc.round_parameter_value_set.all()) + all_objects.extend(rc.parameter_value_set.all()) all_objects.append(self) return serializers.serialize(output_format, all_objects, **kwargs) @@ -1050,7 +1049,7 @@ class Meta: ordering = ['date_created', 'status'] -class RoundConfiguration(models.Model): +class RoundConfiguration(models.Model, ParameterValueMixin): # FIXME: refactor this into a single data structure # maps round type name to (description, default_template_filename) ROUND_TYPES_DICT = dict( @@ -1182,18 +1181,6 @@ def is_playable_round(self): return self.round_type in RoundConfiguration.PLAYABLE_ROUND_CONFIGURATIONS - def get_parameter_value(self, parameter=None, name=None, default=None): - if parameter is None and name is None: - logger.error("Can't find a parameter value with no name or parameter, returning default") - return default - try: - if parameter: - return RoundParameterValue.objects.get(round_configuration=self, parameter=parameter) - elif name: - return RoundParameterValue.objects.get(round_configuration=self, parameter__name=name) - except RoundParameterValue.DoesNotExist: - return DefaultValue(default) - def is_survey_enabled(self): try: return self.survey_url is not None @@ -1453,7 +1440,7 @@ # configuration parameters across the entire experiment class ExperimentParameterValue(ParameterizedValue): - experiment_configuration = models.ForeignKey(ExperimentConfiguration, related_name='experiment_parameter_value_set') + experiment_configuration = models.ForeignKey(ExperimentConfiguration, related_name='parameter_value_set') def __unicode__(self): ec = self.experiment_configuration @@ -1463,13 +1450,13 @@ """ Represents a specific piece of round configuration data. """ - round_configuration = models.ForeignKey(RoundConfiguration, related_name='round_parameter_value_set') + round_configuration = models.ForeignKey(RoundConfiguration, related_name='parameter_value_set') def __unicode__(self): rc = self.round_configuration return u"{0}:{1} -> [{2}: {3}]".format(rc.experiment_configuration, rc.sequence_label, self.parameter, self.value) -class Group(models.Model, DataManagerMixin): +class Group(models.Model, DataValueMixin): number = models.PositiveIntegerField() ''' internal numbering unique to the given experiment ''' max_size = models.PositiveIntegerField(default=5) @@ -1644,7 +1631,7 @@ def for_experiment(self, experiment, **kwargs): return self.prefetch_related('group_relationship_set').filter(experiment=experiment, **kwargs) -class GroupCluster(models.Model, DataManagerMixin): +class GroupCluster(models.Model, DataValueMixin): date_created = models.DateTimeField(default=datetime.now) name = models.CharField(max_length=64, null=True, blank=True) session_id = models.CharField(max_length=64, null=True, blank=True) @@ -1833,7 +1820,7 @@ logger.warning("Participant %s does not belong to a group in %s", participant, experiment) return None -class ParticipantGroupRelationship(models.Model, DataManagerMixin): +class ParticipantGroupRelationship(models.Model, DataValueMixin): """ Many-to-many relationship entity storing a participant, group, their participant number in that group, the round in which they joined the group, and the datetime that they joined the group. diff -r 446cbad125632179a99e01c7244bc6739470bbdb -r 8492223fe4b895f2d69bdedad0072da695a120d6 vcweb/forestry/migrations/0002_regrowth_rate.py --- a/vcweb/forestry/migrations/0002_regrowth_rate.py +++ b/vcweb/forestry/migrations/0002_regrowth_rate.py @@ -280,7 +280,7 @@ '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': "orm['core.Parameter']"}), - 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': "orm['core.ParticipantGroupRelationship']"}), + 'participant_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data_value_set'", 'to': "orm['core.ParticipantGroupRelationship']"}), 'round_data': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participant_data_value_set'", 'to': "orm['core.RoundData']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'submitted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -335,7 +335,7 @@ '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': "orm['core.Parameter']"}), - 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'round_parameter_value_set'", 'to': "orm['core.RoundConfiguration']"}), + 'round_configuration': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parameter_value_set'", 'to': "orm['core.RoundConfiguration']"}), 'string_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) }, 'core.spoolparticipantstatistics': { diff -r 446cbad125632179a99e01c7244bc6739470bbdb -r 8492223fe4b895f2d69bdedad0072da695a120d6 vcweb/lighterprints/models.py --- a/vcweb/lighterprints/models.py +++ b/vcweb/lighterprints/models.py @@ -293,7 +293,7 @@ today = datetime.combine(date.today(), time()) available_activity_ids = Activity.objects.currently_available(participant_group_relationship=participant_group_relationship, level=group_level) # filter out all activities that have already been performed today (activities may only be performed once a day) - performed_activity_data_values = participant_group_relationship.participant_data_value_set.filter(parameter=get_activity_performed_parameter(), + performed_activity_data_values = participant_group_relationship.data_value_set.filter(parameter=get_activity_performed_parameter(), int_value__in=available_activity_ids, date_created__gte=today) upcoming_activity_ids = Activity.objects.upcoming(level=group_level) @@ -389,7 +389,7 @@ today = datetime.combine(date.today(), time()) available_activity_ids = Activity.objects.currently_available(participant_group_relationship=participant_group_relationship, level=group_level) # filter out all activities that have already been performed today (activities may only be performed once a day) - performed_activity_data_values = participant_group_relationship.participant_data_value_set.filter(parameter=get_activity_performed_parameter(), + performed_activity_data_values = participant_group_relationship.data_value_set.filter(parameter=get_activity_performed_parameter(), int_value__in=available_activity_ids, date_created__gte=today) # XXX: data value's int_value stores the fk directly, using .value does a fk lookup to restore the full entity @@ -399,7 +399,7 @@ def check_already_performed_today(activity, participant_group_relationship): today = datetime.combine(date.today(), time()) - already_performed = participant_group_relationship.participant_data_value_set.filter(parameter=get_activity_performed_parameter(), + already_performed = participant_group_relationship.data_value_set.filter(parameter=get_activity_performed_parameter(), int_value=activity.id, date_created__gt=today) return ActivityStatus.AVAILABLE if already_performed.count() == 0 else ActivityStatus.COMPLETED @@ -457,7 +457,7 @@ ) def get_performed_activity_ids(participant_group_relationship): - return participant_group_relationship.participant_data_value_set.filter(parameter=get_activity_performed_parameter()).values_list('id', flat=True) + return participant_group_relationship.data_value_set.filter(parameter=get_activity_performed_parameter()).values_list('id', flat=True) def get_activity_points_cache(): cv = 'activity_points_cache' @@ -542,7 +542,7 @@ def get_activity_performed_counts(participant_group_relationship, activity_performed_parameter=None): if activity_performed_parameter is None: activity_performed_parameter = get_activity_performed_parameter() - return participant_group_relationship.participant_data_value_set.filter(parameter=activity_performed_parameter).values('int_value').order_by().annotate(count=models.Count('int_value')) + return participant_group_relationship.data_value_set.filter(parameter=activity_performed_parameter).values('int_value').order_by().annotate(count=models.Count('int_value')) def get_time_remaining(): ''' diff -r 446cbad125632179a99e01c7244bc6739470bbdb -r 8492223fe4b895f2d69bdedad0072da695a120d6 vcweb/lighterprints/views.py --- a/vcweb/lighterprints/views.py +++ b/vcweb/lighterprints/views.py @@ -313,7 +313,7 @@ # write out participant summary writer.writerow(['Participant', 'Total Points']) for participant_group_relationship in experiment.participant_group_relationships: - performed_activities = participant_group_relationship.participant_data_value_set.filter(parameter=get_activity_performed_parameter()) + performed_activities = participant_group_relationship.data_value_set.filter(parameter=get_activity_performed_parameter()) total_points = 0 for performed_activity in performed_activities: total_points += performed_activity.value.points Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-04-11 20:05:40
|
New issue 104: lighterprints: move update_active_experiments to a little bit after midnight https://bitbucket.org/virtualcommons/vcweb/issue/104/lighterprints-move A Lee: There still seems to be a bug with update_active_experiments and timing, change the midnight_tick to be a little bit after midnight. 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: <com...@bi...> - 2013-04-11 00:05:53
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/446cbad12563/ Changeset: 446cbad12563 User: alllee Date: 2013-04-11 02:05:35 Summary: adding get_data_value to DataManagerMixin, set_data_value next Affected #: 1 file diff -r f75244bdf3e7e5d848512bf51f0190c47f57dfa6 -r 446cbad125632179a99e01c7244bc6739470bbdb vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -44,6 +44,33 @@ return self.value class DataManagerMixin(object): + ''' + this mixin will only work on model classes that expose an "experiment" property. + ''' + def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None, **kwargs): + # FIXME: do we need to provide a mechanism for finding inactive data values? + return dict([ + ('is_active', True), + ('parameter', parameter) if parameter else ('parameter__name', parameter_name), + ('round_data', self.current_round_data if round_data is None else round_data) + ], **kwargs) + + def get_data_value(self, parameter=None, parameter_name=None, round_data=None, filter=False, default=None): + if round_data is None: + round_data = self.experiment.current_round_data + criteria = self._data_parameter_criteria(parameter=parameter, parameter_name=parameter_name, round_data=round_data) + data_value_set = self.data_value_set.select_related('parameter') + data_value_class = data_value_set.model + try: + if filter: + return data_value_set.filter(**criteria) + else: + return data_value_set.get(**criteria) + except data_value_class.DoesNotExist as e: + if default is None: + raise e + else: + return DefaultValue(default) def copy_to_next_round(self, *data_values, **kwargs): e = self.experiment @@ -61,12 +88,6 @@ ''' FIXME: these need more work to generify them properly - def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None, **kwargs): - return dict([ - ('is_active', True), - ('parameter', parameter) if parameter else ('parameter__name', parameter_name), - ('round_data', self.current_round_data if round_data is None else round_data) - ], **kwargs) def set_data_value(self, parameter=None, value=None, round_data=None, **kwargs): if parameter is None or value is None or round_data is None: raise ValueError("need parameter, value, and round data to set") @@ -74,17 +95,6 @@ dv.value = value dv.save() - 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) - try: - return self.data_value_set.select_related('parameter', 'group', 'round_data').get(**criteria) - except self.model.DoesNotExist as e: - if default is None: - raise e - else: - return DefaultValue(default) ''' class AutoDateTimeField(models.DateTimeField): @@ -1564,19 +1574,6 @@ logger.debug("no round configuration value found for parameter (%s, %s) in round: %s", parameter, name, current_round_configuration) return round_configuration_value - def get_data_value(self, parameter=None, parameter_name=None, round_data=None, default=None): - # FIXME: factor out the duplication in all the get_data_value methods - 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_value_set.select_related('parameter', 'group', 'round_data').get(**criteria) - except GroupRoundDataValue.DoesNotExist as e: - if default is None: - raise e - else: - return DefaultValue(default) - def set_data_value(self, parameter=None, value=None, round_data=None, **kwargs): ''' Not as efficient as a simple SQL update because we need to do some type @@ -1662,19 +1659,6 @@ def add(self, group): return GroupRelationship.objects.create(cluster=self, group=group) - def get_data_value(self, parameter=None, round_data=None, default=None): - # FIXME: factor out duplication in the various get_data_value methods - if parameter is None: - raise ValueError("cannot get a data value without a parameter") - if round_data is None: - round_data = self.experiment.current_round_data - try: - return GroupClusterDataValue.objects.get(group_cluster=self, round_data=round_data, parameter=parameter, - is_active=True) - except GroupClusterDataValue.DoesNotExist as e: - if default is None: - raise e - return DefaultValue(default) def set_data_value(self, parameter=None, value=None, round_data=None): if parameter is None or value is None: @@ -1725,7 +1709,7 @@ unique_together = (('round_configuration', 'experiment'),) class GroupClusterDataValue(ParameterizedValue): - group_cluster = models.ForeignKey(GroupCluster) + group_cluster = models.ForeignKey(GroupCluster, related_name='data_value_set') round_data = models.ForeignKey(RoundData, related_name='group_cluster_data_value_set') class GroupRoundDataValue(ParameterizedValue): @@ -1901,18 +1885,6 @@ def get_round_configuration_value(self, **kwargs): return self.group.get_round_configuration_value(**kwargs) - def get_data_value(self, parameter=None, round_data=None, default=None): - # FIXME: factor out the duplication in all the get_data_value methods - if round_data is None: - round_data = self.current_round_data - try: - return ParticipantRoundDataValue.objects.get(is_active=True, round_data=round_data, parameter=parameter, participant_group_relationship=self) - except ParticipantRoundDataValue.DoesNotExist as e: - if default is None: - raise e - else: - return DefaultValue(default) - def set_data_value(self, parameter=None, value=None, round_data=None): if round_data is None: round_data = self.current_round_data @@ -1971,7 +1943,7 @@ Represents one data point collected for a given Participant in a given Round. """ round_data = models.ForeignKey(RoundData, related_name='participant_data_value_set') - participant_group_relationship = models.ForeignKey(ParticipantGroupRelationship, related_name='participant_data_value_set') + participant_group_relationship = models.ForeignKey(ParticipantGroupRelationship, related_name='data_value_set') submitted = models.BooleanField(default=False) target_data_value = models.ForeignKey('ParticipantRoundDataValue', related_name='target_data_value_set', null=True, blank=True) objects = PassThroughManager.for_queryset_class(ParticipantRoundDataValueQuerySet)() Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-10 08:46:41
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/f75244bdf3e7/ Changeset: f75244bdf3e7 User: alllee Date: 2013-04-10 10:46:27 Summary: fixing off-by-one on column coloring in group status Affected #: 2 files diff -r 02b48171ea5c1c30db7c67317f1204ef417257a9 -r f75244bdf3e7e5d848512bf51f0190c47f57dfa6 vcweb/boundaries/static/css/boundaries/style.css --- a/vcweb/boundaries/static/css/boundaries/style.css +++ b/vcweb/boundaries/static/css/boundaries/style.css @@ -39,3 +39,10 @@ -webkit-border-radius: 4px; background-color: #dff0d8; } +col.current-player { + background-color: #dff0d8; +} +.current-player { + font-weight: bold; + font-size: 1em; +} diff -r 02b48171ea5c1c30db7c67317f1204ef417257a9 -r f75244bdf3e7e5d848512bf51f0190c47f57dfa6 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -336,8 +336,9 @@ <h3>Group Status</h3><table class='table table-bordered table-condensed'><colgroup> + <col><!-- ko foreach: playerData --> - <col data-bind='css: { success: id() == $root.participantGroupId() }'> + <col data-bind='css: { "current-player": id() === $root.participantGroupId() }'><!-- /ko --></colgroup><thead> @@ -351,19 +352,19 @@ <tr><td>Last harvest</td><!-- ko foreach: playerData --> - <td data-bind='text: lastHarvestDecision'></td> + <td data-bind='text: lastHarvestDecision, css: { "current-player": id() === $root.participantGroupId() }'></td><!-- /ko --></tr><tr><td>Storage</td><!-- ko foreach: playerData --> - <td data-bind='text: storage'></td> + <td data-bind='text: storage, css: { "current-player": id() === $root.participantGroupId() }'></td><!-- /ko --></tr><tr><td>Status</td><!-- ko foreach: playerData --> - <td data-bind='css: { "text-error": ! alive(), "text-success": alive() }, text: alive() ? "Alive" : "Deceased"'></td> + <td data-bind='css: { "current-player": id() === $root.participantGroupId(), "text-error": ! alive(), "text-success": alive() }, text: alive() ? "Alive" : "Deceased"'></td><!-- /ko --></tr></tbody> Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-10 08:16:40
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/02b48171ea5c/ Changeset: 02b48171ea5c User: alllee Date: 2013-04-10 10:16:19 Summary: fixing bugs in boundaries round transitions, including a nasty one in set_harvest_decision that was inactivating all participant round data values for the given participant instead of only the harvest decisions. Affected #: 4 files diff -r f99d43b82580ad0229022984bc6f8379d5f901c1 -r 02b48171ea5c1c30db7c67317f1204ef417257a9 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -356,7 +356,7 @@ def update_participants(experiment, round_data, round_configuration): logger.debug("updating participants") cost_of_living = get_cost_of_living(round_configuration) - next_round_data = experiment.round_data_set.get_or_create(round_configuration=experiment.next_round) + next_round_data, created = experiment.round_data_set.get_or_create(round_configuration=experiment.next_round) for pgr in experiment.participant_group_relationships: player_status_dv = get_player_status_dv(pgr, round_data) storage_dv = get_storage_dv(pgr, round_data) diff -r f99d43b82580ad0229022984bc6f8379d5f901c1 -r 02b48171ea5c1c30db7c67317f1204ef417257a9 vcweb/boundaries/static/css/boundaries/style.css --- a/vcweb/boundaries/static/css/boundaries/style.css +++ b/vcweb/boundaries/static/css/boundaries/style.css @@ -29,7 +29,13 @@ .harvest-decision-resources td { cursor: pointer; } +.harvest-decision-tree { + padding: 3px 3px 3px 8px; + margin: 5px 5px 5px 5px; +} .harvest-decision-tree:hover { - background-color: yellow; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-border-radius: 4px; + background-color: #dff0d8; } - diff -r f99d43b82580ad0229022984bc6f8379d5f901c1 -r 02b48171ea5c1c30db7c67317f1204ef417257a9 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -711,7 +711,7 @@ # http://effbot.org/zone/default-values.htm) # defaults map parameter model instances to their default initial value, e.g., { footprint-level-parameter: 1, resource-level-parameter: 100 } def initialize_data_values(self, group_parameters=[], participant_parameters=[], group_cluster_parameters=[], round_data=None, defaults={}): - # logger.debug("initializing [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) + logger.debug("initializing [participant params: %s] [group parameters: %s] [group_cluster_parameters: %s] ", participant_parameters, group_parameters, group_cluster_parameters) if round_data is None: round_data = self.current_round_data parameter_defaults = defaultdict(dict) diff -r f99d43b82580ad0229022984bc6f8379d5f901c1 -r 02b48171ea5c1c30db7c67317f1204ef417257a9 vcweb/forestry/models.py --- a/vcweb/forestry/models.py +++ b/vcweb/forestry/models.py @@ -101,6 +101,7 @@ round_data = participant_group_relationship.current_round_data # deactivate all previous harvest decisions in this round ParticipantRoundDataValue.objects.for_participant(participant_group_relationship, + parameter=get_harvest_decision_parameter(), round_data=round_data).update(is_active=False) return ParticipantRoundDataValue.objects.create(participant_group_relationship=participant_group_relationship, parameter=get_harvest_decision_parameter(), 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: <com...@bi...> - 2013-04-10 07:08:44
|
2 new commits in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/b3551cb0fc08/ Changeset: b3551cb0fc08 User: alllee Date: 2013-04-10 09:07:20 Summary: setting sane defaults on submitDecision for the timer driven submit Affected #: 2 files diff -r 6a1e25f62f1dbdc3be9d2067389a4d54168b5595 -r b3551cb0fc087a815d926f21e71f656fc20c2344 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -410,5 +410,7 @@ def calculate_regrowth(resource_level, regrowth_rate, max_resource_level): + if resource_level == max_resource_level: + return 0 return (regrowth_rate * resource_level) * (1 - (resource_level / float(max_resource_level))) diff -r 6a1e25f62f1dbdc3be9d2067389a4d54168b5595 -r b3551cb0fc087a815d926f21e71f656fc20c2344 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -602,11 +602,9 @@ } }; model.submitDecision = function(formId, numberOfTrees) { - console.debug("formId: " + formId); - if (! formId) { - formId = "#vcweb-form"; - } - console.debug("number of trees: " + numberOfTrees); + formId = formId || "#vcweb-form"; + numberOfTrees = numberOfTrees || 0; + console.debug("formId: " + formId + " -- number of trees: " + numberOfTrees); var formData = $(formId).serialize(); model.submitted(true); model.harvestDecision(numberOfTrees); https://bitbucket.org/virtualcommons/vcweb/commits/f99d43b82580/ Changeset: f99d43b82580 User: alllee Date: 2013-04-10 09:08:30 Summary: pushing copy_to_next_round into a mixin class, still need to work out how to unify get_data_value / set_data_value using it as well, maybe by passing in the appropriate QuerySet class / manager class? Affected #: 1 file diff -r b3551cb0fc087a815d926f21e71f656fc20c2344 -r f99d43b82580ad0229022984bc6f8379d5f901c1 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -43,18 +43,34 @@ def __getattr__(self, name): return self.value -class ParameterizedValueQuerySetMixin(models.query.QuerySet): +class DataManagerMixin(object): + + def copy_to_next_round(self, *data_values, **kwargs): + e = self.experiment + if e.is_last_round: + return + next_round_data = kwargs.get('next_round_data', None) + if not next_round_data: + next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) + for existing_dv in data_values: + # Taking advantage of a trick from here: + # http://stackoverflow.com/questions/12182657/copy-or-clone-an-object-instance-in-django-python + existing_dv.pk = None + existing_dv.round_data = next_round_data + existing_dv.save() + + ''' + FIXME: these need more work to generify them properly def _data_parameter_criteria(self, parameter=None, parameter_name=None, round_data=None, **kwargs): return dict([ ('is_active', True), ('parameter', parameter) if parameter else ('parameter__name', parameter_name), ('round_data', self.current_round_data if round_data is None else round_data) ], **kwargs) - def set_data_value(self, parameter=None, value=None, round_data=None, **kwargs): if parameter is None or value is None or round_data is None: raise ValueError("need parameter, value, and round data to set") - dv = self.get(round_data=round_data, parameter=parameter, **kwargs) + dv = self.get_data_value(round_data=round_data, parameter=parameter, **kwargs) dv.value = value dv.save() @@ -69,6 +85,7 @@ raise e else: return DefaultValue(default) + ''' class AutoDateTimeField(models.DateTimeField): def pre_save(self, model_instance, add): @@ -1442,7 +1459,7 @@ rc = self.round_configuration return u"{0}:{1} -> [{2}: {3}]".format(rc.experiment_configuration, rc.sequence_label, self.parameter, self.value) -class Group(models.Model): +class Group(models.Model, DataManagerMixin): number = models.PositiveIntegerField() ''' internal numbering unique to the given experiment ''' max_size = models.PositiveIntegerField(default=5) @@ -1593,41 +1610,6 @@ logger.warning("Trying to retrieve data value by name with no args") return None - def transfer_to_next_round(self, parameter=None, value=None, transfer_existing_value=True): - ''' - Transfers the given parameter to the next round. If parameter isn't set, - transfer all parameters to the next round. - FIXME: If this ends up being surprising or isn't desired behavior for - common use cases, revisit and remove. - ''' - if self.experiment.is_last_round: - logger.warning("Trying to transfer parameter %s to next round but this is the last round", parameter) - return - value = self.get_scalar_data_value(parameter=parameter) if transfer_existing_value else value - if not parameter: - for p in self.data_parameters: - self.transfer_parameter(p, value) - else: - self.transfer_parameter(parameter, value) - - def copy_to_next_round(self, data_value): - ''' copies the given data value to the next round if it exists and returns the newly created group data value''' - return self.transfer_parameter(data_value.parameter, data_value.value) - - def transfer_parameter(self, parameter, value): - e = self.experiment - if e.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 = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) - logger.debug("next round data: %s (%s)", next_round_data, created) - gdv, created = GroupRoundDataValue.objects.get_or_create(group=self, round_data=next_round_data, parameter=parameter) - logger.debug("group data value: %s (%s)", gdv, created) - gdv.value = value - gdv.save() - return gdv - def get_participant_data_values(self, **kwargs): criteria = self._data_parameter_criteria(participant_group_relationship__group=self, **kwargs) return ParticipantRoundDataValue.objects.filter(**criteria) @@ -1665,7 +1647,7 @@ def for_experiment(self, experiment, **kwargs): return self.prefetch_related('group_relationship_set').filter(experiment=experiment, **kwargs) -class GroupCluster(models.Model): +class GroupCluster(models.Model, DataManagerMixin): date_created = models.DateTimeField(default=datetime.now) name = models.CharField(max_length=64, null=True, blank=True) session_id = models.CharField(max_length=64, null=True, blank=True) @@ -1703,21 +1685,6 @@ gcdv.value = value gcdv.save() - def copy_to_next_round(self, next_round_data=None, *data_values): - e = self.experiment - if e.is_last_round: - logger.error("Trying to transfer data values %s past the last round of the experiment, aborting", - data_values) - else: - if not next_round_data: - next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) - for data_value in data_values: - parameter = data_value.parameter - gcdv, created = GroupClusterDataValue.objects.get_or_create(group_cluster=self, round_data=next_round_data, parameter=parameter) - gcdv.value = data_value.value - logger.debug("transferred group cluster data value: %s (%s)", gcdv, created) - gcdv.save() - def __unicode__(self): return u"GroupCluster #%s %s (%s)" % (self.pk, self.session_id, self.experiment) @@ -1882,7 +1849,7 @@ logger.warning("Participant %s does not belong to a group in %s", participant, experiment) return None -class ParticipantGroupRelationship(models.Model): +class ParticipantGroupRelationship(models.Model, DataManagerMixin): """ Many-to-many relationship entity storing a participant, group, their participant number in that group, the round in which they joined the group, and the datetime that they joined the group. @@ -1959,16 +1926,6 @@ else: logger.warning("Unable to set data value %s on round data %s for %s", value, round_data, parameter) - def copy_to_next_round(self, next_round_data=None, *data_values): - e = self.experiment - if not next_round_data: - next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) - for existing_dv in data_values: - # Taking advantage of a trick from here: - # http://stackoverflow.com/questions/12182657/copy-or-clone-an-object-instance-in-django-python - existing_dv.pk = None - existing_dv.round_data = next_round_data - existing_dv.save() def __unicode__(self): return u"{0}: #{1} (in {2})".format(self.participant, self.participant_number, self.group) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-10 06:08:32
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/6a1e25f62f1d/ Changeset: 6a1e25f62f1d User: alllee Date: 2013-04-10 08:08:21 Summary: adding deciduous tree outline from openclipart, will replace with assets from shelby eventually Affected #: 1 file diff -r 68867218d8e4a370488d378f3f1d1c4baacbd7b7 -r 6a1e25f62f1dbdc3be9d2067389a4d54168b5595 vcweb/boundaries/static/images/boundaries/deciduous-tree.png Binary file vcweb/boundaries/static/images/boundaries/deciduous-tree.png has changed Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-10 00:33:22
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/68867218d8e4/ Changeset: 68867218d8e4 User: alllee Date: 2013-04-10 02:33:12 Summary: fixing issues related to the updated harvest decision submission: - no longer create harvest decision participant data values at the start of the round - at the end of the round this means we can't assume that all harvest decisions have already been made - improving signal handling error messaging a bit and adding a trick for cloning django object instances Affected #: 4 files diff -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 -r 68867218d8e4a370488d378f3f1d1c4baacbd7b7 vcweb/boundaries/models.py --- a/vcweb/boundaries/models.py +++ b/vcweb/boundaries/models.py @@ -208,8 +208,7 @@ experiment.initialize_data_values( group_cluster_parameters=(get_regrowth_parameter(), get_resource_level_parameter(),), group_parameters=(get_regrowth_parameter(), get_group_harvest_parameter(), get_resource_level_parameter(),), - participant_parameters=(get_storage_parameter(), - get_player_status_parameter(),) + participant_parameters=(get_storage_parameter(), get_player_status_parameter(),) ) # check for dead participants and set their ready and harvest decision flags deceased_participants = ParticipantRoundDataValue.objects.select_related('participant_group_relationship').filter(parameter=get_player_status_parameter(), @@ -354,9 +353,10 @@ group.log("Transferring shared resource level %s to next round" % shared_resource_level_dv.int_value) group_cluster.copy_to_next_round(shared_resource_level_dv) -def update_participants(experiment, round_data): +def update_participants(experiment, round_data, round_configuration): logger.debug("updating participants") - cost_of_living = get_cost_of_living(round_data.round_configuration) + cost_of_living = get_cost_of_living(round_configuration) + next_round_data = experiment.round_data_set.get_or_create(round_configuration=experiment.next_round) for pgr in experiment.participant_group_relationships: player_status_dv = get_player_status_dv(pgr, round_data) storage_dv = get_storage_dv(pgr, round_data) @@ -372,7 +372,7 @@ storage_dv.save() logger.debug("updating participant %s (storage: %s, harvest: %s, status: %s)", pgr, storage_dv.int_value, harvest_decision, player_status_dv.boolean_value) - pgr.copy_to_next_round(player_status_dv, storage_dv) + pgr.copy_to_next_round(player_status_dv, storage_dv, next_round_data=next_round_data) @receiver(signals.round_ended, sender=EXPERIMENT_METADATA_NAME) def round_ended_handler(sender, experiment=None, **kwargs): @@ -385,9 +385,20 @@ logger.debug("ending boundaries round: %s", round_configuration) if round_configuration.is_playable_round: regrowth_rate = get_regrowth_rate(round_configuration) + harvest_decision_parameter = get_harvest_decision_parameter() # zero out unsubmitted harvest decisions - ParticipantRoundDataValue.objects.filter(round_data=round_data, parameter=get_harvest_decision_parameter(), - submitted=False).update(int_value=0) + for pgr in experiment.participant_group_relationships: + # FIXME: not thread-safe but this *should* only be invoked once per experiment. If we start getting + # spurious data values, revisit this section + prdv, created = ParticipantRoundDataValue.objects.get_or_create( + round_data=round_data, + participant_group_relationship=pgr, + parameter=harvest_decision_parameter, + is_active=True, + defaults={ 'int_value': 0 }) + if created: + logger.debug("created new harvest decision prdv %s for participant %s", prdv, pgr) + # FIXME: generify and merge update_shared_resource_level and update_resource_level to operate on "group-like" objects if possible if is_shared_resource_enabled(round_configuration): for group_cluster in GroupCluster.objects.for_experiment(experiment, session_id=round_configuration.session_id): @@ -395,7 +406,7 @@ else: for group in experiment.group_set.filter(session_id=round_configuration.session_id): update_resource_level(experiment, group, round_data, regrowth_rate) - update_participants(experiment, round_data) + update_participants(experiment, round_data, round_configuration) def calculate_regrowth(resource_level, regrowth_rate, max_resource_level): diff -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 -r 68867218d8e4a370488d378f3f1d1c4baacbd7b7 vcweb/boundaries/static/css/boundaries/style.css --- a/vcweb/boundaries/static/css/boundaries/style.css +++ b/vcweb/boundaries/static/css/boundaries/style.css @@ -29,4 +29,7 @@ .harvest-decision-resources td { cursor: pointer; } +.harvest-decision-tree:hover { + background-color: yellow; +} diff -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 -r 68867218d8e4a370488d378f3f1d1c4baacbd7b7 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -459,7 +459,7 @@ <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: $root.participantGroupId'/><input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: $data'><div class='harvest-decision-tree' data-bind='click: $root.submitDecision.bind($data, "#multi-select-decision-form" + $data)'> - <img data-bind='attr: { src: $root.resourceImageUrl(), alt: "Harvest " + $data + " tree(s)." }'><br> + <img width="40" height="40" data-bind='attr: { src: $root.resourceImageUrl(), alt: "Harvest " + $data + " tree(s)." }'><br><span style='margin-left: 2px;' class='badge badge-success' data-bind='text: $data'></span></div></form> @@ -568,7 +568,7 @@ } return 0; }); - model.resourceImageUrl = ko.observable("{{STATIC_URL}}images/forestry/pine-tree.png"); + model.resourceImageUrl = ko.observable("{{STATIC_URL}}images/boundaries/deciduous-tree.png"); model.resourceImageBackgroundUrl = ko.observable("url('{{STATIC_URL}}images/forestry/pine-tree.png')"); model.resourceImageWidth = ko.computed(function() { return model.resourcesToDisplay() * 30 + "px"; @@ -609,6 +609,7 @@ console.debug("number of trees: " + numberOfTrees); var formData = $(formId).serialize(); model.submitted(true); + model.harvestDecision(numberOfTrees); console.debug(formData); $.post('submit-harvest-decision', formData, function(response) { console.log(response); diff -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 -r 68867218d8e4a370488d378f3f1d1c4baacbd7b7 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -886,9 +886,15 @@ if sender is None: sender = intern(self.experiment_metadata.namespace.encode('utf8')) signal_tuple = signals.round_started.send_robust(sender, experiment=self, time=datetime.now(), round_configuration=current_round_configuration) - logger.debug("round started signal returned %s", signal_tuple) + self._check_signal_result(signal_tuple) return signal_tuple + def _check_signal_result(self, signal_tuple_list): + logger.debug("checking signal tuple list: %s", signal_tuple_list) + for signal_tuple in signal_tuple_list: + if signal_tuple[1] is not None: + logger.error("%s resulted in an error condition: %s", signal_tuple[0], signal_tuple[1]) + def stop_round(self, sender=None, **kwargs): return self.end_round() @@ -900,7 +906,7 @@ #sender = self.namespace.encode('utf-8') logger.debug("about to send round ended signal with sender %s", sender) signal_tuple = signals.round_ended.send_robust(sender, experiment=self, round_configuration=self.current_round) - logger.debug("signal tuple: %s", signal_tuple) + self._check_signal_result(signal_tuple) return signal_tuple def activate(self): @@ -1697,13 +1703,14 @@ gcdv.value = value gcdv.save() - def copy_to_next_round(self, *data_values): + def copy_to_next_round(self, next_round_data=None, *data_values): e = self.experiment if e.is_last_round: logger.error("Trying to transfer data values %s past the last round of the experiment, aborting", data_values) else: - next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) + if not next_round_data: + next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) for data_value in data_values: parameter = data_value.parameter gcdv, created = GroupClusterDataValue.objects.get_or_create(group_cluster=self, round_data=next_round_data, parameter=parameter) @@ -1952,15 +1959,16 @@ else: logger.warning("Unable to set data value %s on round data %s for %s", value, round_data, parameter) - def copy_to_next_round(self, *data_values): + def copy_to_next_round(self, next_round_data=None, *data_values): e = self.experiment - next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) + if not next_round_data: + next_round_data, created = RoundData.objects.get_or_create(experiment=e, round_configuration=e.next_round) for existing_dv in data_values: - parameter = existing_dv.parameter - new_dv, created = ParticipantRoundDataValue.objects.get_or_create(participant_group_relationship=self, - round_data=next_round_data, parameter=parameter) - new_dv.value = existing_dv.value - new_dv.save() + # Taking advantage of a trick from here: + # http://stackoverflow.com/questions/12182657/copy-or-clone-an-object-instance-in-django-python + existing_dv.pk = None + existing_dv.round_data = next_round_data + existing_dv.save() def __unicode__(self): return u"{0}: #{1} (in {2})".format(self.participant, self.participant_number, self.group) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: <com...@bi...> - 2013-04-09 00:36:36
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/bca65c1a5c50/ Changeset: bca65c1a5c50 User: alllee Date: 2013-04-09 02:36:23 Summary: refs issue 102 - setting up individual form submissions, still need to set up UI Affected #: 4 files diff -r 9b5768e0d3151a152ea2182d3fe19946583e8505 -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 vcweb/boundaries/static/css/boundaries/style.css --- a/vcweb/boundaries/static/css/boundaries/style.css +++ b/vcweb/boundaries/static/css/boundaries/style.css @@ -26,3 +26,7 @@ margin-right: 0px; padding: 8px 8px 8px 8px; } +.harvest-decision-resources td { + cursor: pointer; +} + diff -r 9b5768e0d3151a152ea2182d3fe19946583e8505 -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 vcweb/boundaries/templates/boundaries/participate.html --- a/vcweb/boundaries/templates/boundaries/participate.html +++ b/vcweb/boundaries/templates/boundaries/participate.html @@ -445,19 +445,38 @@ <form id='vcweb-form' data-bind='submit: submitDecision' action='' method='post' class='form-horizontal' > {% csrf_token %} <div id='harvestDecisionDiv' data-bind='visible: !submitted()' class='control-group'> - <div class='input-prepend'> - <span class='add-on'><i class='icon-leaf'></i></span> - <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: participantGroupId'/> - <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: harvestDecision'> - <select id='harvestDecisionSelect' name='harvest_decision' required="required" form="vcweb-form" data-bind='options: harvestDecisionOptions, value: harvestDecision'> - </select> - </div> - <button id='submitDecisionButton' type='submit' class='btn btn-primary'>Submit</button> + <div data-bind='template: { name: "harvest-decision-multi-select-template" }'></div></div></form></div></div></script> +<script type='text/html' id='harvest-decision-multi-select-template'> + <table class='table'> + <tr class='row harvest-decision-resources' data-bind='foreach: harvestDecisionOptions'> + <td data-bind='attr: { id: "harvest-decision-td-" + $data }'> + <form data-bind='attr: { id: "multi-select-decision-form" + $data }'> + <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: $root.participantGroupId'/> + <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: $data'> + <div class='harvest-decision-tree' data-bind='click: $root.submitDecision.bind($data, "#multi-select-decision-form" + $data)'> + <img data-bind='attr: { src: $root.resourceImageUrl(), alt: "Harvest " + $data + " tree(s)." }'><br> + <span style='margin-left: 2px;' class='badge badge-success' data-bind='text: $data'></span> + </div> + </form> + </td> + </tr> + </table> +</script> +<script type='text/html' id='harvest-decision-select-template'> + <div class='input-prepend'> + <span class='add-on'><i class='icon-leaf'></i></span> + <input id='participantGroupId' type='hidden' name='participant_group_id' data-bind='value: participantGroupId'/> + <input id='harvestDecisionTextInput' type='hidden' name='integer_decision' data-bind='value: harvestDecision'> + <select id='harvestDecisionSelect' name='harvest_decision' required="required" form="vcweb-form" data-bind='options: harvestDecisionOptions, value: harvestDecision'> + </select> + </div> + <button id='submitDecisionButton' type='submit' class='btn btn-primary'>Submit</button> +</script><script src='{% static "js/bootstrap-tour.js" %}'></script><script type="text/javascript"> $(function() { @@ -582,8 +601,13 @@ 1000)); } }; - model.submitDecision = function(data, evt) { - var formData = $('#vcweb-form').serialize(); + model.submitDecision = function(formId, numberOfTrees) { + console.debug("formId: " + formId); + if (! formId) { + formId = "#vcweb-form"; + } + console.debug("number of trees: " + numberOfTrees); + var formData = $(formId).serialize(); model.submitted(true); console.debug(formData); $.post('submit-harvest-decision', formData, function(response) { diff -r 9b5768e0d3151a152ea2182d3fe19946583e8505 -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -352,16 +352,19 @@ @property def current_round_elapsed_time(self): - if self.current_round_start_time: + if self.current_round_start_time and self.is_timed_round: return datetime.now() - self.current_round_start_time return timedelta(0) @property def time_remaining(self): - tr = self.current_round.duration - self.current_round_elapsed_time.seconds - if tr < 0: - return u"Expired %s seconds ago" % abs(tr) - return tr + if self.is_timed_round: + tr = self.current_round.duration - self.current_round_elapsed_time.seconds + if tr <= 0: + return u"Expired (%s seconds ago)" % abs(tr) + return tr + else: + return "Untimed round (advance manually or via automated checkpointing)" @property def is_timed_round(self): @@ -630,7 +633,7 @@ p.institution = institution p.save() per = ParticipantExperimentRelationship.objects.create(participant=p, experiment=self, created_by=self.experimenter.user) - email_messages.append(self.create_registration_email(per, password=password)) + email_messages.append(self.create_registration_email(per, password=password, is_new_participant=created)) if email_messages: mail.get_connection().send_messages(email_messages) diff -r 9b5768e0d3151a152ea2182d3fe19946583e8505 -r bca65c1a5c5073b4dbbeefb3a3fbf553cbce1763 vcweb/lighterprints/models.py --- a/vcweb/lighterprints/models.py +++ b/vcweb/lighterprints/models.py @@ -495,7 +495,6 @@ total_group_points = group_data_dict['total_group_points'] average = total_group_points / group_size group_data_dict['average_group_points'] = average - logger.debug("total carbon savings: %s divided by %s members = %s per person", total_group_points, group_size, average) return (group_scores, total_participant_points) Repository URL: https://bitbucket.org/virtualcommons/vcweb/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. |
From: A L. <iss...@bi...> - 2013-04-08 22:58:14
|
New issue 103: what to do when django app.name != experiment_metadata.namespace https://bitbucket.org/virtualcommons/vcweb/issue/103/what-to-do-when-django-appname A Lee: The boundaries experiment is using experiment_metadata.namespace of 'bound'. Dynamically looked up templates use the namespace instead of the appname, which raises the issue of what to consistently name the templates directory (and how to refer to them). Right now urls.py and the templates themselves use the appname boundaries when creating links to other of the namespace (which could be changed in the database at any time). However, the core views don't know anything about the app a particular Experiment belongs to and so when it generates paths to templates it uses experiment_metadata.namespace. Find some way to reconcile these so that we don't need workarounds like symlinking boundaries -> bound in boundaries/templates 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: <com...@bi...> - 2013-04-06 09:12:45
|
1 new commit in vcweb: https://bitbucket.org/virtualcommons/vcweb/commits/9b5768e0d315/ Changeset: 9b5768e0d315 User: alllee Date: 2013-04-06 11:12:28 Summary: allowing for empty experiment passcodes (signifies we should generate random passwords and assign them to the user) Affected #: 5 files diff -r 67ff21bb249d6169a1997898d342756ae6964188 -r 9b5768e0d3151a152ea2182d3fe19946583e8505 vcweb/core/forms.py --- a/vcweb/core/forms.py +++ b/vcweb/core/forms.py @@ -102,7 +102,7 @@ class RegisterParticipantsForm(forms.Form): experiment_pk = forms.IntegerField(widget=widgets.HiddenInput) - experiment_passcode = forms.CharField(min_length=3, label="Experiment passcode", help_text=_('The password used to login to your experiment.'), initial='test') + experiment_passcode = forms.CharField(required=False, min_length=3, label="Experiment passcode", help_text=_('The password used to login to your experiment.'), initial='test') institution_name = forms.CharField(min_length=3, label="Institution name", required=False, initial='Arizona State University', help_text=_('The name of the institution to be associated with these test participants')) @@ -123,7 +123,7 @@ class RegisterTestParticipantsForm(RegisterParticipantsForm): - username_suffix = forms.CharField(min_length=3, initial='asu', + username_suffix = forms.CharField(min_length=1, initial='asu', help_text=_('''Appended to every generated username before the "@" symbol, e.g., s1...@fo...''')) email_suffix = forms.CharField(min_length=3, initial='mailinator.com', help_text=_(''' diff -r 67ff21bb249d6169a1997898d342756ae6964188 -r 9b5768e0d3151a152ea2182d3fe19946583e8505 vcweb/core/models.py --- a/vcweb/core/models.py +++ b/vcweb/core/models.py @@ -634,7 +634,7 @@ if email_messages: mail.get_connection().send_messages(email_messages) - def create_registration_email(self, participant_experiment_relationship, password=None, **kwargs): + def create_registration_email(self, participant_experiment_relationship, password='', **kwargs): ''' Override the email template by creating <experiment-namespace>/email/experiment-registration(txt|html) template files ''' @@ -644,7 +644,7 @@ experiment = participant_experiment_relationship.experiment participant = participant_experiment_relationship.participant user = participant.user - if password is None: + if not password.strip(): password = User.objects.make_random_password() # FIXME: this resets existing user passwords.. user.set_password(password) @@ -661,8 +661,9 @@ if subject is None: subject = 'VCWEB: experiment registration for %s' % self.display_name experimenter_email = self.experimenter.email - to_address = [ participant_experiment_relationship.participant.email, experimenter_email ] - msg = EmailMultiAlternatives(subject, plaintext_content, experimenter_email, to_address) + to_address = [ participant_experiment_relationship.participant.email ] + bcc_address = [ experimenter_email ] + msg = EmailMultiAlternatives(subject, plaintext_content, experimenter_email, to_address, bcc_address) msg.attach_alternative(html_content, "text/html") return msg diff -r 67ff21bb249d6169a1997898d342756ae6964188 -r 9b5768e0d3151a152ea2182d3fe19946583e8505 vcweb/core/templates/email/experiment-registration.html --- a/vcweb/core/templates/email/experiment-registration.html +++ b/vcweb/core/templates/email/experiment-registration.html @@ -1,15 +1,15 @@ <h4>{{experiment.display_name}} registration</h4><p> -You have been registered with the {{ experiment.display_name }} experiment. You can set your password by visiting: +You've been registered with the {{ experiment.display_name }}. You can access the experiment by <a href='//{{experiment.participant_url}}'>logging in to our experiment website</a> using the following credentials: + +<ul> + <li>Email: {{participant.email}}</li> + <li>Password: {{ password }}</li> +</ul> +<p> +If you'd like to reset your password, please visit: </p><ul><li>https://vcweb.asu.edu/accounts/password/reset</li></ul> -<p> -Your login email address is: -</p> -<ul> - <li>Email: {{participant.email}}</li> -</ul> - diff -r 67ff21bb249d6169a1997898d342756ae6964188 -r 9b5768e0d3151a152ea2182d3fe19946583e8505 vcweb/core/templates/email/experiment-registration.txt --- a/vcweb/core/templates/email/experiment-registration.txt +++ b/vcweb/core/templates/email/experiment-registration.txt @@ -1,8 +1,10 @@ -You have been registered with the {{ experiment.display_name }} experiment. You can set your password by visiting +You've been registered with the {{ experiment.display_name }}. You can access the experiment by logging in to our experiment website at https://vcweb.asu.edu using the following credentials: + + Email: {{participant.email}} + Password: {{password}} + +You can reset your password by visiting: https://vcweb.asu.edu/accounts/password/reset?email={{ participant.email }} -Your login email address: - Email: {{participant.email}} - diff -r 67ff21bb249d6169a1997898d342756ae6964188 -r 9b5768e0d3151a152ea2182d3fe19946583e8505 vcweb/core/templates/experimenter/dashboard.html --- a/vcweb/core/templates/experimenter/dashboard.html +++ b/vcweb/core/templates/experimenter/dashboard.html @@ -9,7 +9,7 @@ <div class='alert alert-message'><h4>{{e.status_line}} <span class='pull-right'><span class='badge badge-important'>{{e.participant_set.count}}</span> participants</span></h4><div class='btn-group'> - <a class='btn' href='{{e.monitor_url}}'><i class='icon-zoom-in'></i> monitor</a></li> + <a class='btn' href='{{e.monitor_url}}'><i class='icon-zoom-in'></i> monitor</a><a class='btn confirm-experiment-action' data-content='Creates a new copy of this experiment with the same configuration and no participants.' href='{{e.clone_url}}'><i class='icon-copy'></i> clone</a> {% if not e.is_archived %} @@ -21,7 +21,7 @@ <img src='{{STATIC_URL}}images/famfamfam/group_add.png' alt='register'/> test participants</a> {% else %} <a data-content='Remove all participants from the experiment, including any data they may have generated.' href='{{e.controller_url}}/clear-participants' class='btn confirm-experiment-action'> - <i class='icon-trash'></i> clear all participants</a></li> + <i class='icon-trash'></i> clear all participants</a> {% endif %} {% if e.is_active %} <a data-action='complete' data-content='Mark this experiment as completed' href='{{e.complete_url}}' class='btn confirm-experiment-action'><i class='icon-save'></i> archive</a> 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. |