From 7b396d46add6349bc9545ebf07a11d3aec2b7eed Mon Sep 17 00:00:00 2001 From: Jeffrey Kirchner Date: Sun, 17 Sep 2023 12:35:15 -0700 Subject: [PATCH 1/4] instruction set dev --- main/fixtures/InstructionSet.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/fixtures/InstructionSet.json b/main/fixtures/InstructionSet.json index 523d0470..3d3af816 100644 --- a/main/fixtures/InstructionSet.json +++ b/main/fixtures/InstructionSet.json @@ -1 +1 @@ -[{"model": "main.instructionset", "pk": 1, "fields": {"label": "One - Limited Chat - Attack", "timestamp": "2023-07-19T16:48:49.790Z", "updated": "2023-09-13T23:12:47.255Z"}}, {"model": "main.instruction", "pk": 1, "fields": {"instruction_set": 1, "text_html": "

Welcome

\r\n

This is an experiment in the economics of decision-making. The instructions are simple, and if you follow them carefully and make good decisions, you can earn a considerable amount of money that will be paid to you privately at the end of the experiment.

\r\n

In this experiment, your name is #id_label#, and you are represented with the gear-shaped avatar in the center of your screen.

\r\n

You and the other #player_count-1# people each control an avatar that can harvest goods from resource patches, consume those resources in your home, and interact with other avatars. Those resources will maintain your avatar's health which determines your earnings.

\r\n

Your house is the one labeled #id_label#. A map in the top left corner of the area below shows where you are. Try navigating the environment now by left-clicking \"\" on the screen to locate your house. Do this now.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 1, "timestamp": "2023-07-19T16:48:49.792Z", "updated": "2023-09-13T22:36:44.366Z"}}, {"model": "main.instruction", "pk": 2, "fields": {"instruction_set": 1, "text_html": "

Resource Patches

\r\n

Resource patches are located throughout the environment. Each patch displays how many cherries \"\" or blueberries \"\" are currently available to harvest. The amount available to harvest depends on the number of available rings around the resource.

\r\n

To harvest from a patch, move your avatar close to one and right-click \"\" on it, then press Harvest   to transfer those units to your avatar. Do this now. After you harvest, the outer-most ring around the resource is greyed out, leaving fewer units for another person to harvest. Each time someone harvests a resource, one ring is removed.

\r\n

One ring, and only one ring, will grow back each period.

\r\n

Each patch has a maximum number of rings, which may differ for different patches.

\r\n

Your avatar can only harvest from one patch each period.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 3, "timestamp": "2023-09-12T22:25:54.108Z", "updated": "2023-09-13T22:41:39.923Z"}}, {"model": "main.instruction", "pk": 3, "fields": {"instruction_set": 1, "text_html": "

Sleep

\r\n

A #night_length#-second night phase will occur at the end of every period. During the night, you can optionally rest your avatar at your house. For every second of sleep your avatar gets, its health will rise by #heath_gain_per_sleep_second#. While your avatar is asleep, it cannot move or interact with the environment. To sleep,  right-click \"\"  on your house and press the Sleep   button. Avatars can only sleep during the night and will automatically awaken at the start of the next period.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 5, "timestamp": "2023-09-12T22:25:54.111Z", "updated": "2023-09-13T22:44:59.173Z"}}, {"model": "main.instruction", "pk": 4, "fields": {"instruction_set": 1, "text_html": "

Houses

\r\n

Your house converts any resources it contains into health for your avatar at the end of each period. The amount of health you will gain is displayed at the bottom of your house. Move some of the resources you harvested earlier from your avatar into your house by moving your avatar close to your house and right-clicking \"\" on your house. Use the menu to select the desired amount, then press Move  . Do this now. Notice that the amount of health you will receive at the end of the period has increased.

\r\n

An equal number of cherries \"\" and blueberries \"\" in your house generates more health points than an unequal number of the same total. For example, if you have six cherries \"\"  and six blueberries \"\"  in your house, #healthpoints_6_6# health points will be added to your total. But if you have two cherries \"\" and ten blueberries \"\" in your house, only #healthpoints_10_2# health points will be added. Additional single units of either resource will always increase the number of health points a house will generate.

\r\n

You can move resources out of your house by changing the menu to \"House -> Avatar\".

\r\n

You can move resources into another person's house, but you cannot move them out.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 4, "timestamp": "2023-09-12T22:25:54.111Z", "updated": "2023-09-13T22:43:49.897Z"}}, {"model": "main.instruction", "pk": 5, "fields": {"instruction_set": 1, "text_html": "

Health

\r\n

Your earnings in this experiment are based on how healthy you keep your avatar. All avatars in this experiment have a health score \"\"displayed above them. This score ranges from zero to one hundred.

\r\n

For each second that passes, you earn this many US cents (¢):

\r\n

#cents_per_second# x your health score.

\r\n

Avatars have a metabolism that slowly reduces their health by #health_loss_per_second# points for every second that passes. If your health score reaches zero, you will stop earning money until you raise your score above zero.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 2, "timestamp": "2023-09-13T16:18:50.489Z", "updated": "2023-09-13T22:37:14.609Z"}}, {"model": "main.instruction", "pk": 6, "fields": {"instruction_set": 1, "text_html": "

Other Avatars

\r\n

Every participant in this experiment is assigned an avatar. Your avatar can interact with other nearby avatars by right-clicking \"\"  on them. Move your avatar close to another avatar and interact with it. The other avatar must be within the circle surrounding your avatar to interact. You can move resources to another avatar like you can move resources to a house.

\r\n

You can attack another avatar by pressing the Attack  button at the bottom of the avatar interaction menu. Do this now. An attack will reduce the target's health by #attack_damage# points. An attack will also reduce the attacker's health by #attack_cost# points. After an attack, the target and the attacker's avatar are subject to a #cool_down_length#-second cool down where neither avatar can attack.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 6, "timestamp": "2023-09-13T16:52:18.287Z", "updated": "2023-09-13T22:48:34.719Z"}}, {"model": "main.instruction", "pk": 9, "fields": {"instruction_set": 1, "text_html": "

Chat

\r\n

You can chat with other nearby avatars using the chat box at the bottom of your screen.

\r\n

Avatars in other groups do not speak the same language as avatars in your group. This limits the available words that you can use to communicate with other groups. It is up to you to discover which words work and which words appear as a different language.

\r\n

You are free to discuss any aspects of the experiment, with the following exceptions: you may not reveal your name, discuss side payments outside the laboratory, or engage in inappropriate language (including such shorthand as ‘WTF’). If you do, you will be excused and forgo your experiment earnings.

\r\n

Every #break_frequency# periods has a #break_length#-second break phase, where chatting is enabled, but all other interactions are disabled. Metabolism and earnings are paused during the break phase.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 7, "timestamp": "2023-09-13T18:00:36.723Z", "updated": "2023-09-13T22:59:58.201Z"}}, {"model": "main.instruction", "pk": 10, "fields": {"instruction_set": 1, "text_html": "

Conclusion

\r\n

This is the end of the instructions. If you have any questions, please raise your hand, and a monitor will come by to answer them. If you are finished with the instructions, please click the Start   button located in the bottom right of this frame. The instructions will remain on your screen until the experiment begins. We need everyone to click the  Start   button before we can begin the experiment.

", "page_number": 8, "timestamp": "2023-09-13T18:32:07.895Z", "updated": "2023-09-13T23:01:45.560Z"}}] \ No newline at end of file +[{"model": "main.instructionset", "pk": 1, "fields": {"label": "One - Limited Chat - Attack", "action_page_move": 1, "action_page_harvest": 3, "action_page_house": 4, "action_page_sleep": 5, "action_page_attacks": 6, "timestamp": "2023-07-19T16:48:49.790Z", "updated": "2023-09-14T17:07:15.967Z"}}, {"model": "main.instruction", "pk": 1, "fields": {"instruction_set": 1, "text_html": "

Welcome

\r\n

This is an experiment in the economics of decision-making. The instructions are simple, and if you follow them carefully and make good decisions, you can earn a considerable amount of money that will be paid to you privately at the end of the experiment.

\r\n

In this experiment, your name is #id_label#, and you are represented with the gear-shaped avatar in the center of your screen.

\r\n

You and the other #player_count-1# people each control an avatar that can harvest goods from resource patches, consume those resources in your home, and interact with other avatars. Those resources will maintain your avatar's health which determines your earnings.

\r\n

Your house is the one labeled #id_label#. A map in the top left corner of the area below shows where you are. Try navigating the environment now by left-clicking \"\" on the screen to locate your house. Do this now.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 1, "timestamp": "2023-07-19T16:48:49.792Z", "updated": "2023-09-13T22:36:44.366Z"}}, {"model": "main.instruction", "pk": 2, "fields": {"instruction_set": 1, "text_html": "

Resource Patches

\r\n

Resource patches are located throughout the environment. Each patch displays how many cherries \"\" or blueberries \"\" are currently available to harvest. The amount available to harvest depends on the number of available rings around the resource.

\r\n

To harvest from a patch, move your avatar close to one and right-click \"\" on it, then press Harvest   to transfer those units to your avatar. Do this now. After you harvest, the outer-most ring around the resource is greyed out, leaving fewer units for another person to harvest. Each time someone harvests a resource, one ring is removed.

\r\n

One ring, and only one ring, will grow back each period.

\r\n

Each patch has a maximum number of rings, which may differ for different patches.

\r\n

Your avatar can only harvest from one patch each period.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 3, "timestamp": "2023-09-12T22:25:54.108Z", "updated": "2023-09-13T22:41:39.923Z"}}, {"model": "main.instruction", "pk": 3, "fields": {"instruction_set": 1, "text_html": "

Sleep

\r\n

A #night_length#-second night phase will occur at the end of every period. During the night, you can optionally rest your avatar at your house. For every second of sleep your avatar gets, its health will rise by #heath_gain_per_sleep_second#. While your avatar is asleep, it cannot move or interact with the environment. To sleep,  right-click \"\"  on your house and press the Sleep   button. Avatars can only sleep during the night and will automatically awaken at the start of the next period.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 5, "timestamp": "2023-09-12T22:25:54.111Z", "updated": "2023-09-13T22:44:59.173Z"}}, {"model": "main.instruction", "pk": 4, "fields": {"instruction_set": 1, "text_html": "

Houses

\r\n

Your house converts any resources it contains into health for your avatar at the end of each period. The amount of health you will gain is displayed at the bottom of your house. Move some of the resources you harvested earlier from your avatar into your house by moving your avatar close to your house and right-clicking \"\" on your house. Use the menu to select the desired amount, then press Move  . Do this now. Notice that the amount of health you will receive at the end of the period has increased.

\r\n

An equal number of cherries \"\" and blueberries \"\" in your house generates more health points than an unequal number of the same total. For example, if you have six cherries \"\"  and six blueberries \"\"  in your house, #healthpoints_6_6# health points will be added to your total. But if you have two cherries \"\" and ten blueberries \"\" in your house, only #healthpoints_10_2# health points will be added. Additional single units of either resource will always increase the number of health points a house will generate.

\r\n

You can move resources out of your house by changing the menu to \"House -> Avatar\".

\r\n

You can move resources into another person's house, but you cannot move them out.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 4, "timestamp": "2023-09-12T22:25:54.111Z", "updated": "2023-09-13T22:43:49.897Z"}}, {"model": "main.instruction", "pk": 5, "fields": {"instruction_set": 1, "text_html": "

Health

\r\n

Your earnings in this experiment are based on how healthy you keep your avatar. All avatars in this experiment have a health score \"\"displayed above them. This score ranges from zero to one hundred.

\r\n

For each second that passes, you earn this many US cents (¢):

\r\n

#cents_per_second# x your health score.

\r\n

Avatars have a metabolism that slowly reduces their health by #health_loss_per_second# points for every second that passes. If your health score reaches zero, you will stop earning money until you raise your score above zero.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 2, "timestamp": "2023-09-13T16:18:50.489Z", "updated": "2023-09-13T22:37:14.609Z"}}, {"model": "main.instruction", "pk": 6, "fields": {"instruction_set": 1, "text_html": "

Other Avatars

\r\n

Every participant in this experiment is assigned an avatar. Your avatar can interact with other nearby avatars by right-clicking \"\"  on them. Move your avatar close to another avatar and interact with it. The other avatar must be within the circle surrounding your avatar to interact. You can move resources to another avatar like you can move resources to a house.

\r\n

You can attack another avatar by pressing the Attack  button at the bottom of the avatar interaction menu. Do this now. An attack will reduce the target's health by #attack_damage# points. An attack will also reduce the attacker's health by #attack_cost# points. After an attack, the target and the attacker's avatar are subject to a #cool_down_length#-second cool down where neither avatar can attack.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 6, "timestamp": "2023-09-13T16:52:18.287Z", "updated": "2023-09-13T22:48:34.719Z"}}, {"model": "main.instruction", "pk": 9, "fields": {"instruction_set": 1, "text_html": "

Chat

\r\n

You can chat with other nearby avatars using the chat box at the bottom of your screen.

\r\n

Avatars in other groups do not speak the same language as avatars in your group. This limits the available words that you can use to communicate with other groups. It is up to you to discover which words work and which words appear as a different language.

\r\n

You are free to discuss any aspects of the experiment, with the following exceptions: you may not reveal your name, discuss side payments outside the laboratory, or engage in inappropriate language (including such shorthand as ‘WTF’). If you do, you will be excused and forgo your experiment earnings.

\r\n

Every #break_frequency# periods has a #break_length#-second break phase, where chatting is enabled, but all other interactions are disabled. Metabolism and earnings are paused during the break phase.

\r\n

Continue to the next page of the instructions by pressing the Next   button.

", "page_number": 7, "timestamp": "2023-09-13T18:00:36.723Z", "updated": "2023-09-13T22:59:58.201Z"}}, {"model": "main.instruction", "pk": 10, "fields": {"instruction_set": 1, "text_html": "

Conclusion

\r\n

This is the end of the instructions. If you have any questions, please raise your hand, and a monitor will come by to answer them. If you are finished with the instructions, please click the Start   button located in the bottom right of this frame. The instructions will remain on your screen until the experiment begins. We need everyone to click the  Start   button before we can begin the experiment.

", "page_number": 8, "timestamp": "2023-09-13T18:32:07.895Z", "updated": "2023-09-13T23:01:45.560Z"}}] \ No newline at end of file From 9f430a431c0ef9e85b276a508202a07906124423 Mon Sep 17 00:00:00 2001 From: Jeffrey Kirchner Date: Sun, 17 Sep 2023 15:06:21 -0700 Subject: [PATCH 2/4] add help docs to instructions for subjects. --- main/admin.py | 14 ++++++ .../subject/subject_home_consumer.py | 1 + .../subject_home_consumer_mixins/__init__.py | 3 +- .../help_doc_subject.py | 47 +++++++++++++++++++ main/forms/__init__.py | 1 + main/forms/help_doc_subject_form_admin.py | 21 +++++++++ .../0109_helpdocssubject_and_more.py | 35 ++++++++++++++ ...0_alter_helpdocssubject_instruction_set.py | 19 ++++++++ ...bject_unique_help_doc_subject__and_more.py | 21 +++++++++ main/models/__init__.py | 1 + main/models/help_docs_subject.py | 37 +++++++++++++++ main/models/instruction_set.py | 10 +++- main/models/session_player.py | 47 ++++++++++++------- .../subject/subject_home/help_doc_subject.js | 29 ++++++++++++ .../subject/subject_home/subject_home.js | 8 ++-- .../the_stage/avatar_attack_modal.html | 8 +++- .../subject_home/the_stage/avatar_modal.html | 2 +- .../subject_home/the_stage/field_modal.html | 7 +++ .../subject_home/the_stage/house_modal.html | 13 +++-- .../subject/subject_home/the_stage/houses.js | 2 +- .../subject_home/the_stage/patch_modal.html | 7 ++- .../the_stage/the_stage_card.html | 5 +- 22 files changed, 307 insertions(+), 31 deletions(-) create mode 100644 main/consumers/subject/subject_home_consumer_mixins/help_doc_subject.py create mode 100644 main/forms/help_doc_subject_form_admin.py create mode 100644 main/migrations/0109_helpdocssubject_and_more.py create mode 100644 main/migrations/0110_alter_helpdocssubject_instruction_set.py create mode 100644 main/migrations/0111_remove_helpdocssubject_unique_help_doc_subject__and_more.py create mode 100644 main/models/help_docs_subject.py create mode 100644 main/templates/subject/subject_home/help_doc_subject.js diff --git a/main/admin.py b/main/admin.py index 9b26f4df..2a26bf70 100644 --- a/main/admin.py +++ b/main/admin.py @@ -11,6 +11,7 @@ from main.forms import SessionFormAdmin from main.forms import InstructionFormAdmin from main.forms import InstructionSetFormAdmin +from main.forms import HelpDocSubjectFormAdmin from main.models import Parameters from main.models import ParameterSet @@ -28,6 +29,7 @@ from main.models.instruction_set import InstructionSet from main.models.instruction import Instruction +from main.models.help_docs_subject import HelpDocsSubject admin.site.site_header = settings.ADMIN_SITE_HEADER @@ -279,6 +281,16 @@ class InstructionPageInline(admin.TabularInline): model = Instruction can_delete = True +#instruction set page +class HelpDocSubjectInline(admin.TabularInline): + ''' + instruction page admin screen + ''' + extra = 0 + form = HelpDocSubjectFormAdmin + model = HelpDocsSubject + can_delete = True + @admin.register(InstructionSet) class InstructionSetAdmin(admin.ModelAdmin): form = InstructionSetFormAdmin @@ -304,6 +316,7 @@ def duplicate_set(self, request, queryset): instruction_set.save() instruction_set.copy_pages(base_instruction_set.instructions) + instruction_set.copy_help_docs_subject(base_instruction_set.help_docs_subject) self.message_user(request,f'{base_instruction_set} has been duplicated', messages.SUCCESS) @@ -311,6 +324,7 @@ def duplicate_set(self, request, queryset): inlines = [ InstructionPageInline, + HelpDocSubjectInline, ] actions = [duplicate_set] diff --git a/main/consumers/subject/subject_home_consumer.py b/main/consumers/subject/subject_home_consumer.py index c60abf41..eda34c42 100644 --- a/main/consumers/subject/subject_home_consumer.py +++ b/main/consumers/subject/subject_home_consumer.py @@ -19,6 +19,7 @@ class SubjectHomeConsumer(SocketConsumerMixin, InstructionsMixin, PhaseMixin, TimeMixin, + GetHelpDocSubject, InterfaceMixin): ''' websocket methods for subject home diff --git a/main/consumers/subject/subject_home_consumer_mixins/__init__.py b/main/consumers/subject/subject_home_consumer_mixins/__init__.py index 4e8fe45a..acaefe60 100644 --- a/main/consumers/subject/subject_home_consumer_mixins/__init__.py +++ b/main/consumers/subject/subject_home_consumer_mixins/__init__.py @@ -8,4 +8,5 @@ from .intructions import InstructionsMixin from .phase import PhaseMixin from .time import TimeMixin -from .interface import InterfaceMixin \ No newline at end of file +from .interface import InterfaceMixin +from .help_doc_subject import GetHelpDocSubject \ No newline at end of file diff --git a/main/consumers/subject/subject_home_consumer_mixins/help_doc_subject.py b/main/consumers/subject/subject_home_consumer_mixins/help_doc_subject.py new file mode 100644 index 00000000..d11b1d00 --- /dev/null +++ b/main/consumers/subject/subject_home_consumer_mixins/help_doc_subject.py @@ -0,0 +1,47 @@ +import logging +import json + +from asgiref.sync import sync_to_async + +from django.core.serializers.json import DjangoJSONEncoder + +from main.models import Session +from main.models import SessionPlayer +from main.models import SessionEvent + +import main + +class GetHelpDocSubject(): + ''' + Get help doc subject mixin for subject home consumer + ''' + async def help_doc_subject(self, event): + ''' + take help doc request + ''' + + try: + message_text = event["message_text"] + title = message_text["title"] + + session_player = await SessionPlayer.objects.select_related('session','session__parameter_set', 'session__parameter_set__instruction_set').aget(player_key=self.connection_uuid) + instruction_set = session_player.session.parameter_set.instruction_set + help_doc_subject = await instruction_set.help_docs_subject.all().aget(title=title) + text = await sync_to_async(session_player.process_instruction_text, thread_sensitive=False)(help_doc_subject.text) + + except Exception as e: + text = "Help doc not found" + + await SessionEvent.objects.acreate(session_id=session_player.session.id, + session_player_id=session_player.id, + type="help_doc", + period_number=session_player.session.world_state["current_period"], + time_remaining=session_player.session.world_state["time_remaining"], + data=title) + + + result = {"value" : "success", + "result" : {"text" : text}} + + await self.send_message(message_to_self=result, message_to_subjects=None, message_to_staff=None, + message_type=event['type'], send_to_client=True, send_to_group=False) \ No newline at end of file diff --git a/main/forms/__init__.py b/main/forms/__init__.py index 0afd6a0c..85a991c3 100644 --- a/main/forms/__init__.py +++ b/main/forms/__init__.py @@ -24,6 +24,7 @@ from .session_form_admin import SessionFormAdmin from .instruction_set_form_admin import InstructionSetFormAdmin from .instruction_form_admin import InstructionFormAdmin +from .help_doc_subject_form_admin import HelpDocSubjectFormAdmin from .end_game_form import EndGameForm from .interaction_form import InteractionForm diff --git a/main/forms/help_doc_subject_form_admin.py b/main/forms/help_doc_subject_form_admin.py new file mode 100644 index 00000000..58c84f52 --- /dev/null +++ b/main/forms/help_doc_subject_form_admin.py @@ -0,0 +1,21 @@ +''' +help doc subject form admin screen +''' +from django import forms +from main.models import Instruction +from tinymce.widgets import TinyMCE + +class HelpDocSubjectFormAdmin(forms.ModelForm): + ''' + help doc subject form admin screen + ''' + + title = forms.CharField(label='Title', + widget=forms.TextInput(attrs={"width":"300px"})) + + text = forms.CharField(label='Page HTML Text', + widget=TinyMCE(attrs={"rows":20, "cols":200, "plugins": "link image code"})) + + class Meta: + model=Instruction + fields = ('title', 'text') \ No newline at end of file diff --git a/main/migrations/0109_helpdocssubject_and_more.py b/main/migrations/0109_helpdocssubject_and_more.py new file mode 100644 index 00000000..b6aac1b0 --- /dev/null +++ b/main/migrations/0109_helpdocssubject_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.3 on 2023-09-17 19:40 + +from django.db import migrations, models +import django.db.models.deletion +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0108_alter_parameterset_instruction_set'), + ] + + operations = [ + migrations.CreateModel( + name='HelpDocsSubject', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(default='', max_length=300, verbose_name='Title')), + ('text', tinymce.models.HTMLField(default='', max_length=100000, verbose_name='Help Doc Text')), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('instruction_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='help_docs_subjects', to='main.instructionset')), + ], + options={ + 'verbose_name': 'Help Doc Subject', + 'verbose_name_plural': 'Help Docs Subject', + 'ordering': ['title'], + }, + ), + migrations.AddConstraint( + model_name='helpdocssubject', + constraint=models.UniqueConstraint(fields=('instruction_set', 'title'), name='unique_help_doc_subject '), + ), + ] diff --git a/main/migrations/0110_alter_helpdocssubject_instruction_set.py b/main/migrations/0110_alter_helpdocssubject_instruction_set.py new file mode 100644 index 00000000..258d84f6 --- /dev/null +++ b/main/migrations/0110_alter_helpdocssubject_instruction_set.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2023-09-17 21:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0109_helpdocssubject_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='helpdocssubject', + name='instruction_set', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='help_docs_subject', to='main.instructionset'), + ), + ] diff --git a/main/migrations/0111_remove_helpdocssubject_unique_help_doc_subject__and_more.py b/main/migrations/0111_remove_helpdocssubject_unique_help_doc_subject__and_more.py new file mode 100644 index 00000000..d1cebc99 --- /dev/null +++ b/main/migrations/0111_remove_helpdocssubject_unique_help_doc_subject__and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.3 on 2023-09-17 21:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0110_alter_helpdocssubject_instruction_set'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='helpdocssubject', + name='unique_help_doc_subject ', + ), + migrations.AddConstraint( + model_name='helpdocssubject', + constraint=models.UniqueConstraint(fields=('instruction_set', 'title'), name='unique_help_doc_subject'), + ), + ] diff --git a/main/models/__init__.py b/main/models/__init__.py index 17d68e7d..34ca5a7d 100644 --- a/main/models/__init__.py +++ b/main/models/__init__.py @@ -7,6 +7,7 @@ from .instruction_set import InstructionSet from .instruction import Instruction +from .help_docs_subject import HelpDocsSubject from .parameter_set import ParameterSet from .parameter_set_group import ParameterSetGroup diff --git a/main/models/help_docs_subject.py b/main/models/help_docs_subject.py new file mode 100644 index 00000000..ec2fd6ac --- /dev/null +++ b/main/models/help_docs_subject.py @@ -0,0 +1,37 @@ +''' +help document subject +''' +from tinymce.models import HTMLField +from django.db import models + +from main.models import InstructionSet + +class HelpDocsSubject(models.Model): + ''' + help document + ''' + instruction_set = models.ForeignKey(InstructionSet, on_delete=models.CASCADE, related_name="help_docs_subject") + + title = models.CharField(verbose_name = 'Title', max_length = 300, default="") + text = HTMLField(verbose_name = 'Help Doc Text', max_length = 100000, default="") + + timestamp = models.DateTimeField(auto_now_add=True) + updated= models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = 'Help Doc Subject' + verbose_name_plural = 'Help Docs Subject' + ordering = ['title'] + constraints = [ + models.UniqueConstraint(fields=['instruction_set', 'title'], name='unique_help_doc_subject'), + ] + + def __str__(self): + return self.title + + def json(self): + return{ + "id":self.id, + "title":self.title, + "text":self.text, + } \ No newline at end of file diff --git a/main/models/instruction_set.py b/main/models/instruction_set.py index 32fca1ea..a28ffc0f 100644 --- a/main/models/instruction_set.py +++ b/main/models/instruction_set.py @@ -48,6 +48,15 @@ def copy_pages(self, i_set): instructions.append(main.models.Instruction(instruction_set=self, text_html=i.text_html, page_number=i.page_number)) main.models.Instruction.objects.bulk_create(instructions) + + def copy_help_docs_subject(self, i_set): + + help_docs_subject = [] + + for i in i_set.all(): + help_docs_subject.append(main.models.HelpDocsSubject(instruction_set=self, title=i.title, text=i.text)) + + main.models.HelpDocsSubject.objects.bulk_create(help_docs_subject) #return json object of class def json(self): @@ -76,7 +85,6 @@ def json_min(self): return{ "id" : self.id, - "label" : self.label, } \ No newline at end of file diff --git a/main/models/session_player.py b/main/models/session_player.py index 8f439c67..b1a12978 100644 --- a/main/models/session_player.py +++ b/main/models/session_player.py @@ -100,34 +100,45 @@ def get_instruction_set(self): return a proccessed list of instructions to the subject ''' - parameter_set = self.parameter_set_player.parameter_set.json() - parameter_set_player = parameter_set["parameter_set_players"][str(self.parameter_set_player.id)] - group_name = parameter_set["parameter_set_groups"][str(parameter_set_player["parameter_set_group"])]["name"] + instruction_pages = [i.json() for i in self.parameter_set_player.parameter_set.instruction_set.instructions.all()] for i in instruction_pages: - i["text_html"] = i["text_html"].replace("#player_count-1#", str(len(parameter_set["parameter_set_players"])-1)) - i["text_html"] = i["text_html"].replace("#id_label#", parameter_set_player["id_label"]) - i["text_html"] = i["text_html"].replace("#cents_per_second#", str(round_half_away_from_zero(parameter_set["cents_per_second"],3))) - i["text_html"] = i["text_html"].replace("#health_loss_per_second#", str(round_half_away_from_zero(parameter_set["health_loss_per_second"],3))) - i["text_html"] = i["text_html"].replace("#healthpoints_6_6#", str(main.globals.convert_goods_to_health(6,6,0,parameter_set))) - i["text_html"] = i["text_html"].replace("#healthpoints_10_2#", str(main.globals.convert_goods_to_health(10,2,0,parameter_set))) - i["text_html"] = i["text_html"].replace("#night_length#", str(parameter_set["night_length"])) - i["text_html"] = i["text_html"].replace("#heath_gain_per_sleep_second#", str(round_half_away_from_zero(parameter_set["heath_gain_per_sleep_second"],3))) - i["text_html"] = i["text_html"].replace("#attack_cost#", str(round_half_away_from_zero(parameter_set["attack_cost"],3))) - i["text_html"] = i["text_html"].replace("#attack_damage#", str(round_half_away_from_zero(parameter_set["attack_damage"],3))) - i["text_html"] = i["text_html"].replace("#cool_down_length#", str(parameter_set["cool_down_length"])) - i["text_html"] = i["text_html"].replace("#number_of_groups#", str(len(parameter_set["parameter_set_groups"]))) - i["text_html"] = i["text_html"].replace("#group_name#", group_name) - i["text_html"] = i["text_html"].replace("#break_frequency#", str(parameter_set["break_frequency"])) - i["text_html"] = i["text_html"].replace("#break_length#", str(parameter_set["break_length"])) + i["text_html"] = self.process_instruction_text(i["text_html"]) instructions = self.parameter_set_player.parameter_set.instruction_set.json() instructions["instruction_pages"] = instruction_pages return instructions + def process_instruction_text(self, text): + ''' + process instruction text + ''' + + parameter_set = self.parameter_set_player.parameter_set.json() + parameter_set_player = parameter_set["parameter_set_players"][str(self.parameter_set_player.id)] + group_name = parameter_set["parameter_set_groups"][str(parameter_set_player["parameter_set_group"])]["name"] + + text = text.replace("#player_count-1#", str(len(parameter_set["parameter_set_players"])-1)) + text = text.replace("#id_label#", parameter_set_player["id_label"]) + text = text.replace("#cents_per_second#", str(round_half_away_from_zero(parameter_set["cents_per_second"],3))) + text = text.replace("#health_loss_per_second#", str(round_half_away_from_zero(parameter_set["health_loss_per_second"],3))) + text = text.replace("#healthpoints_6_6#", str(main.globals.convert_goods_to_health(6,6,0,parameter_set))) + text = text.replace("#healthpoints_10_2#", str(main.globals.convert_goods_to_health(10,2,0,parameter_set))) + text = text.replace("#night_length#", str(parameter_set["night_length"])) + text = text.replace("#heath_gain_per_sleep_second#", str(round_half_away_from_zero(parameter_set["heath_gain_per_sleep_second"],3))) + text = text.replace("#attack_cost#", str(round_half_away_from_zero(parameter_set["attack_cost"],3))) + text = text.replace("#attack_damage#", str(round_half_away_from_zero(parameter_set["attack_damage"],3))) + text = text.replace("#cool_down_length#", str(parameter_set["cool_down_length"])) + text = text.replace("#number_of_groups#", str(len(parameter_set["parameter_set_groups"]))) + text = text.replace("#group_name#", group_name) + text = text.replace("#break_frequency#", str(parameter_set["break_frequency"])) + text = text.replace("#break_length#", str(parameter_set["break_length"])) + + return text + def get_survey_link(self): ''' get survey link diff --git a/main/templates/subject/subject_home/help_doc_subject.js b/main/templates/subject/subject_home/help_doc_subject.js new file mode 100644 index 00000000..f2093299 --- /dev/null +++ b/main/templates/subject/subject_home/help_doc_subject.js @@ -0,0 +1,29 @@ +/** + * send request for help doc + * @param title : string + */ +send_load_help_doc_subject(title){ + app.working = true; + app.help_text = "Loading ..."; + + app.help_modal.show(); + + app.send_message("help_doc_subject", {title : title}); +}, + +/** + * take help text load request + * @param message_data : json + */ +take_load_help_doc_subject(message_data){ + + if(message_data.value == "success") + { + app.help_text = message_data.result.text; + } + else + { + app.help_text = message_data.message; + } +}, + diff --git a/main/templates/subject/subject_home/subject_home.js b/main/templates/subject/subject_home/subject_home.js index 80c4929c..7a55620a 100644 --- a/main/templates/subject/subject_home/subject_home.js +++ b/main/templates/subject/subject_home/subject_home.js @@ -92,6 +92,7 @@ var app = Vue.createApp({ instruction_pages_show_scroll : false, // modals + help_modal : null, end_game_modal : null, avatar_modal : null, avatar_attack_modal : null, @@ -166,8 +167,8 @@ var app = Vue.createApp({ case "get_session": app.take_get_session(message_data); break; - case "help_doc": - app.take_load_help_doc(message_data); + case "help_doc_subject": + app.take_load_help_doc_subject(message_data); break; case "update_start_experiment": app.take_update_start_experiment(message_data); @@ -273,6 +274,7 @@ var app = Vue.createApp({ app.field_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('field_modal'), {keyboard: false}) app.house_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('house_modal'), {keyboard: false}) app.patch_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('patch_modal'), {keyboard: false}) + app.help_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('help_modal'), {keyboard: false}) document.getElementById('end_game_modal').addEventListener('hidden.bs.modal', app.hide_end_game_modal); document.getElementById('avatar_modal').addEventListener('hidden.bs.modal', app.hide_avatar_modal); @@ -635,7 +637,7 @@ var app = Vue.createApp({ {%include "subject/subject_home/the_stage/barriers.js"%} {%include "subject/subject_home/the_stage/emoji.js"%} {%include "subject/subject_home/the_stage/patch.js"%} - {%include "js/help_doc.js"%} + {%include "subject/subject_home/help_doc_subject.js"%} /** clear form error messages */ diff --git a/main/templates/subject/subject_home/the_stage/avatar_attack_modal.html b/main/templates/subject/subject_home/the_stage/avatar_attack_modal.html index e6f041b1..7dd097df 100644 --- a/main/templates/subject/subject_home/the_stage/avatar_attack_modal.html +++ b/main/templates/subject/subject_home/the_stage/avatar_attack_modal.html @@ -12,7 +12,13 @@