def ws_message(message): # Send to all clients editing the topology channels.Group("topology-%s" % message.channel_session['topology_id']).send({"text": message['text']}) # Send to networking_events handler networking_events_dispatcher.handle({"text": message['text'], "topology": message.channel_session['topology_id'], "client": message.channel_session['client_id']})
def advance_participant(participant): p = participant unvisited_participants = [] if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: return try: if p._current_form_page_url: resp = client.post(p._current_form_page_url, data={ constants_internal.timeout_happened: True, constants_internal.admin_secret_code: ADMIN_SECRET_CODE }, follow=True) else: resp = client.get(p._start_url(), follow=True) except: logging.exception("Failed to advance participant.") raise assert resp.status_code < 400 channels.Group('auto-advance-{}'.format(p.code)).send( {'text': json.dumps({'auto_advanced': True})})
def advance_last_place_participants(self): participants = self.get_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: try: current_form_page_url = p._current_form_page_url if current_form_page_url: resp = client.post( current_form_page_url, data={ constants_internal.timeout_happened: True, constants_internal.admin_secret_code: ADMIN_SECRET_CODE }, follow=True) # not sure why, but many users are getting HttpResponseNotFound if resp.status_code >= 400: msg = ('Submitting page {} failed, ' 'returned HTTP status code {}.'.format( current_form_page_url, resp.status_code)) content = resp.content if len(content) < 600: msg += ' response content: {}'.format(content) raise AssertionError(msg) else: # it's possible that the slowest user is on a wait page, # especially if their browser is closed. # because they were waiting for another user who then # advanced past the wait page, but they were never # advanced themselves. start_url = p._start_url() resp = client.get(start_url, follow=True) except: logging.exception("Failed to advance participants.") raise # do the auto-advancing here, # rather than in increment_index_in_pages, # because it's only needed here. channels.Group('auto-advance-{}'.format(p.code)).send( {'text': json.dumps({'auto_advanced': True})})
def send_completion_message(self, participant_pk_set): if otree.common_internal.USE_REDIS: # only necessary to submit if next page has a timeout # or if it is a wait page player_lookup = self.participant.future_player_lookup( pages_ahead=1) if player_lookup: PageClass = get_view_from_url(player_lookup.url) if (issubclass(PageClass, InGameWaitPageMixin) or PageClass.has_timeout()): otree.timeout.tasks.ensure_pages_visited.schedule(kwargs={ 'participant_pk_set': participant_pk_set, 'wait_page_index': self._index_in_pages }, delay=10) # _group_or_subsession might be deleted # in after_all_players_arrive, but it won't delete the cached model channels_group_name = self.get_channels_group_name() channels.Group(channels_group_name).send( {'text': json.dumps({'status': 'ready'})})
def before_next_page(self): # we get the list of those players who are not last ones not_last_ones = [ p for p in self.subsession.get_players() if p.last_one == False ] if len(self.subsession.get_players()) - 1 > len(not_last_ones): self.player.last_one = False else: # if this player is the last one in session... self.player.last_one = True # we set session level var to True so other players arriving to # app3.WaitPage later can go on immediately self.session.vars['gofurther'] = True # somehow the status is not updated automatically via idmap # so do save() self.session.save() # we get the index of current WaitPage via session.vars # (it is set by players arriving to app3.WaitPage) cur_wp_index = self.session.vars.get('wp_index') # if someone has already arrived the index is set and we # can get channel name using index and current session pk if cur_wp_index: curch = chname(self.session.pk, cur_wp_index) channels.Group(curch).send( {'text': json.dumps({'status': 'ready'})})
def before_next_page(self): # we get the list of those players who are already ahead of me already_passed = [p for p in self.player.get_others_in_subsession() if p.last_one == False] if len(already_passed) < len(self.player.get_others_in_subsession()): # this player is not the last one self.player.last_one = False else: # if this player is the last one in session... self.player.last_one = True # calculate payoffs self.group.set_payoffs() # we set session level var to True so other players arriving to # results.WaitPage later can go on immediately self.session.vars['two_thirds_all_done'] = True # send a message to other players who may be waiting on the results.TwoThirdsWaitPage wp_index = self.session.vars.get('two_thirds_wait_page_index') if wp_index: # for convenience, we use the "group_by_arrival_time" channel, # created by setting group_by_arrival_time = True in results.TwoThirdsWaitPage channel = channel_name(self.session.pk, wp_index) channels.Group(channel).send( {'text': json.dumps({'status': 'ready'})} )
def run(self): request = self.args[0] page = request.path response = self.fn(*self.args, **self.kwargs) cache.set("page_{}".format(page), response.content, 12 * 60 * 60) channels.Group('render_page_{}'.format( urllib.parse.unquote(page).replace('/', '_').replace( '&', '_'))).send({'text': 'DONE'})
def get_pad(document_pk): group = channels.Group("pad" + str(document_pk)) if not hasattr(group.channel_layer, "pads"): group.channel_layer.pads = {} pads = group.channel_layer.pads document = get_object_or_404(Document, pk=document_pk) data = document.original.read().decode("utf-8") return pads.setdefault(document_pk, pad_ns.Pad(data))
def _increment_index_in_pages(self): # when is this not the case? assert self._index_in_pages == self.participant._index_in_pages self._record_page_completion_time() # we should allow a user to move beyond the last page if it's mturk # also in general maybe we should show the 'out of sequence' page # the timeout record is irrelevant at this point, delete it # wait pages don't have a has_timeout attribute if hasattr(self, 'has_timeout') and self.has_timeout(): PageTimeout.objects.filter( participant=self.participant, page_index=self.participant._index_in_pages).delete() # this is causing crashes because of the weird DB issue # ParticipantToPlayerLookup.objects.filter( # participant=self.participant.pk, # page_index=self.participant._index_in_pages).delete() # we skip any page that is a sequence page where is_displayed # evaluates to False to eliminate unnecessary redirection for page_index in range( # go to max_page_index+2 because range() skips the last index # and it's possible to go to max_page_index + 1 (OutOfRange) self._index_in_pages + 1, self.participant._max_page_index + 2): self.participant._index_in_pages = page_index if page_index == self.participant._max_page_index + 1: # break and go to OutOfRangeNotification break url = self.participant._url_i_should_be_on() Page = get_view_from_url(url) page = Page() if not hasattr(page, 'is_displayed'): break page.set_attributes(self.participant) if page.is_displayed(): break # if it's a wait page, record that they visited # but don't run after_all_players_arrive if hasattr(page, '_register_wait_page_visit'): completion = page._register_wait_page_visit() if completion: participant_pk_set = set( page._group_or_subsession.player_set.values_list( 'participant__pk', flat=True)) page.send_completion_message(participant_pk_set) channels.Group('auto-advance-{}'.format(self.participant.code)).send({ 'text': json.dumps( {'new_index_in_pages': self.participant._index_in_pages}) })
def ws_connect(message): if not message.user.is_authenticated(): logger.error("Request user is not authenticated to use websocket.") message.reply_channel.send({"close": True}) return else: message.reply_channel.send({"accept": True}) data = urlparse.parse_qs(message.content['query_string']) inventory_id = parse_inventory_id(data) topology_ids = list(TopologyInventory.objects.filter(inventory_id=inventory_id).values_list('pk', flat=True)) topology_id = None if len(topology_ids) > 0: topology_id = topology_ids[0] if topology_id is not None: topology = Topology.objects.get(pk=topology_id) else: topology = Topology(name="topology", scale=1.0, panX=0, panY=0) topology.save() TopologyInventory(inventory_id=inventory_id, topology_id=topology.pk).save() topology_id = topology.pk message.channel_session['topology_id'] = topology_id channels.Group("topology-%s" % topology_id).add(message.reply_channel) client = Client() client.save() message.channel_session['client_id'] = client.pk channels.Group("client-%s" % client.pk).add(message.reply_channel) message.reply_channel.send({"text": json.dumps(["id", client.pk])}) message.reply_channel.send({"text": json.dumps(["topology_id", topology_id])}) topology_data = transform_dict(dict(id='topology_id', name='name', panX='panX', panY='panY', scale='scale', link_id_seq='link_id_seq', device_id_seq='device_id_seq'), topology.__dict__) message.reply_channel.send({"text": json.dumps(["Topology", topology_data])}) send_snapshot(message.reply_channel, topology_id)
def advance_last_place_participants(self): participants = self.get_participants() # in case some participants haven't started unvisited_participants = [] for p in participants: if p._index_in_pages == 0: unvisited_participants.append(p) client.get(p._start_url(), follow=True) if unvisited_participants: # that's it -- just visit the start URL, advancing by 1 return last_place_page_index = min([p._index_in_pages for p in participants]) last_place_participants = [ p for p in participants if p._index_in_pages == last_place_page_index ] for p in last_place_participants: try: if p._current_form_page_url: resp = client.post( p._current_form_page_url, data={ constants_internal.auto_submit: True, constants_internal.admin_secret_code: ADMIN_SECRET_CODE }, follow=True) else: # it's possible that the slowest user is on a wait page, # especially if their browser is closed. # because they were waiting for another user who then # advanced past the wait page, but they were never # advanced themselves. resp = client.get(p._start_url(), follow=True) except: logging.exception("Failed to advance participants.") raise assert resp.status_code < 400 # do the auto-advancing here, # rather than in increment_index_in_pages, # because it's only needed here. channels.Group('auto-advance-{}'.format(p.code)).send( {'text': json.dumps({'auto_advanced': True})})
def send_stream_group(self): """ Sends a notification of us to our groups's stream. """ data = { "id": str(self.id), "type": "thread", "num_messages": self.num_messages, "num_top_level_messages": self.num_top_level_messages, } channels.Group("stream-group-%s" % self.group.id).send({ "content": json.dumps(data), })
def send_completion_message(self, participant_pk_set): if otree.common_internal.USE_REDIS: # 2016-11-15: we used to only ensure the next page is visited # if the next page has a timeout, or if it's a wait page # but this is not reliable because next page might be skipped anyway, # and we don't know what page will actually be shown next to the user. otree.timeout.tasks.ensure_pages_visited.schedule( kwargs={'participant_pk_set': participant_pk_set}, delay=10) # _group_or_subsession might be deleted # in after_all_players_arrive, but it won't delete the cached model channels_group_name = self.get_channels_group_name() channels.Group(channels_group_name).send( {'text': json.dumps({'status': 'ready'})})
def receive(self, text=None, bytes=None, **kwargs): self.clean_kwargs() player = self.get_player() group = self.get_group() player.leave_auction() player.save() group.remaining_bidders() group.save() channels.Group( group.get_channel_group_name() ).send( {'text': json.dumps( {'num': group.num_in_auction, })} )
def post(request): """ Receives a chat message. """ if request.POST.get("message"): channels.Group("stream-chat").send({ "content": json.dumps({ "type": "chat", "message": request.POST["message"], "author": request.POST.get("author", "anonymous"), }), }) return JsonResponse({"posted": True}) return redirect("/chat/")
def post(self, request, *args, **kwargs): num_participants = int(request.POST['num_participants']) session_config_name = request.POST['session_config_name'] bot_case_number = int(request.POST['bot_case_number']) session = create_session(session_config_name=session_config_name, num_participants=num_participants, bot_case_number=bot_case_number, force_browser_bots=True) BrowserBotsLauncherSessionCode.objects.update_or_create( # i don't know why the update_or_create arg is called 'defaults' # because it will update even if the instance already exists # maybe for consistency with get_or_create defaults={'code': session.code}) channels.Group('browser_bot_wait').send( {'text': json.dumps({'status': 'session_ready'})}) return HttpResponse(session.code)
def chat_receive(message): """Handles a websocket message by saving it and broadcasting it to all connected users. Connected to "chat.receive". """ # Save the message in the database group_slug = message.content['group'] chatMessage = Message.objects.create( user=message.content['user'], group=get_object_or_404(catalog.models.Group, slug=group_slug), text=message.content['text'] ) # Escape the text before sending it, so that no XSS injection is possible # But we don't need to escape it before saving in database, since Django # escapes everything chatMessage.text = html.escape(chatMessage.text) # Send the message to the group (i.e. all users connected on the group chat) channels.Group("chat" + group_slug).send({'text': chatMessage.dump_json()})
def send_stream_thread(self): """ Sends a notification of us to our thread's stream. """ data = { "id": str(self.id), "thread_id": str(self.thread_id), } if self.parent: data['type'] = "reply" data['discussion_id'] = str(self.parent_id) data['html'] = htmlmin.minify(self.reply_html()) else: data['type'] = "discussion" data['html'] = htmlmin.minify(self.discussion_html()) channels.Group("stream-thread-%s" % self.thread.id).send({ "content": json.dumps(data), })
def getCourses(courses, path): if type(courses) != list: return [] API = OsirisApi() coursesinfo = [] for i, course in enumerate(courses): info = API.course(course) if info is not None: for c in info: staff = c.pop('responsiblestaff') owner = c.pop('owner') c['teacher'] = staff['name'] c['teachermail'] = staff['email'] c['group'] = owner['group'] coursesinfo.append(c) channels.Group('render_page_{}'.format(path.replace( '&', '_'))).send( {'text': str(floor(((i + 1) / len(courses)) * 100))}) return coursesinfo
def receive(self, content, multiplexer, **kwargs): # Subscribe user to kit measurement updates if "kit" not in content: multiplexer.send({"error": "Kit to subscribe to not given."}) return try: kit = backend.models.Kit.objects.get(username=content['kit']) if not self.message.user.has_perm( 'backend.subscribe_to_kit_measurements_websocket', kit): multiplexer.send({ "error": "Kit not found or you do not have access to it." }) return channels.Group("kit-measurements-%s" % kit.username).add( multiplexer.reply_channel) multiplexer.send({"action": "subscribe", "kit": kit.username}) except: multiplexer.send( {"error": "Kit not found or you do not have access to it."})
def get_user_group(user): return channels.Group("pad_user_" + user.netid)
def get_chat_from_message(message): """Returns the channel group to which this message belongs.""" return channels.Group("chat" + message.channel_session['group'])
def send_completion_message(*, session_code, participant_code): group_name = channel_utils.browser_bots_launcher_group(session_code) # don't need to put in JSON since it's just a participant code channels.Group(group_name).send({'text': participant_code})
def runEverySecond(): if group_model_exists(): # Groups are hidden started once all group members reach Wait invisible_wait_groups = Group.objects.filter(hidden_start=True) for g in invisible_wait_groups: if g.hidden_time_till > 0: g.hidden_time_till = g.hidden_time_till - 1 g.save() else: g.start = True g.hidden_start = False g.save() active_wait_groups = Group.objects.filter(activated=False, start=True) for g in active_wait_groups: if g.time_till > 0: g.time_till = g.time_till - 1 # Save commands are necessary for the database to change g.save() channels.Group(g.get_channel_group_name()).send({ 'text': json.dumps({ 'started': True, 'activated': False, 'time_till': g.time_till, 'over': False, 'activate_exit': False, }) }) if g.time_till == 0: # Group is activated once timer hits 0 g.activated = True g.start = False g.price = g.start_price g.price_float = g.start_price g.save() channels.Group(g.get_channel_group_name()).send({ 'text': json.dumps({ 'started': True, 'activated': True, 'time_till': g.time_till, 'activate_exit': True, 'over': False, }) }) # The auction is live in activated groups activated_groups = Group.objects.filter(activated=True, auction_over=False) for g in activated_groups: g.remaining_bidders() g.save() if g.price < g.fH and g.num_in_auction > 1: g.price += g.increment_size g.save() channels.Group(g.get_channel_group_name()).send({ 'text': json.dumps({ 'price': g.price, 'expense': g.price * g.group_quantity, 'num': g.num_in_auction, 'over': False, 'activated': True, 'activate_exit': False, 'started': True }) }) if int(g.price) == g.fH or g.num_in_auction <= 1: g.auction_over = True g.save() channels.Group(g.get_channel_group_name()).send({ 'text': json.dumps({ 'price': g.price, 'expense': g.price * g.group_quantity, # Sometimes the incorrect number of remaining bidders is displayed for some reason 'num': g.num_in_auction, 'over': True, 'started': True, 'activated': True, 'activate_exit': False }) })
def send_completion_message(self): channels.Group('browser-bots-client-{}'.format( self.session.code)).send({'text': self.participant.code})
def dispatch(self, request, *args, **kwargs): if self.wait_for_all_groups: self._group_or_subsession = self.subsession else: self._group_or_subsession = self.group if self._is_ready(): return self._response_when_ready() # take a lock because we set "waiting for" list here with lock_on_this_code_path(): unvisited_participants = self._tally_unvisited() if unvisited_participants: # only skip the wait page if there are still # unvisited participants. otherwise, you need to # mark the page as completed. # see _increment_index_in_pages, which needs to # handle this case also if not self.is_displayed(): return self._response_when_ready() self.participant.is_on_wait_page = True return self._get_wait_page() else: try: if self.wait_for_all_groups: completion = CompletedSubsessionWaitPage( page_index=self._index_in_pages, session_pk=self.session.pk) else: completion = CompletedGroupWaitPage( page_index=self._index_in_pages, group_pk=self.group.pk, session_pk=self.session.pk) completion.save() # if the record already exists # (enforced through unique_together) except django.db.IntegrityError: self.participant.is_on_wait_page = True return self._get_wait_page() try: # need to check this before deleting # reference to self.player is_displayed = self.is_displayed() # in case there is a timeout on the next page, we # should ensure the next pages are visited promptly # TODO: can we make this run only if next page is a # timeout page? # or if a player is auto playing. # we could instead make this request the current page # URL, but it's different for each player # _group_or_subsession might be deleted # in after_all_players_arrive, so calculate this first participant_pk_set = set([ p.participant.pk for p in self._group_or_subsession.player_set.all() ]) # _group_or_subsession might be deleted # in after_all_players_arrive, so calculate this first channels_group_name = self.channels_group_name() # block users from accessing self.player inside # after_all_players_arrive, because conceptually # there is no single player in this context # (method is executed once for the whole group) player = self.player del self.player # make sure we get the most up-to-date player objects # e.g. if they were queried in is_displayed(), # then they could be out of date # but don't delete the current player from cache # because we need it to be saved at the end import idmap.tls cache = getattr(idmap.tls._tls, 'idmap_cache', {}) for p in list(cache.get(self.PlayerClass, {}).values()): if p != player: self.PlayerClass.flush_cached_instance(p) # if any player can skip the wait page, # then we shouldn't run after_all_players_arrive # because if some players are able to proceed to the next page # before after_all_players_arrive is run, # then after_all_players_arrive is probably not essential. # often, there are some wait pages that all players skip, # because they should only be shown in certain rounds. # maybe the fields that after_all_players_arrive depends on # are null # something to think about: ideally, should we check if # all players skipped, or any player skipped? # as a shortcut, we just check if is_displayed is true # for the last player. if is_displayed: self.after_all_players_arrive() except Exception as e: completion.delete() raise e self.player = player if otree.common_internal.USE_REDIS: # 2015-07-27: # why not check if the next page has_timeout? otree.timeout.tasks.ensure_pages_visited.schedule(kwargs={ 'participant_pk_set': participant_pk_set, 'wait_page_index': self._index_in_pages, }, delay=10) completion.after_all_players_arrive_run = True completion.save() # send a message to the channel to move forward # this should happen at the very end, channels.Group(channels_group_name).send( {'text': json.dumps({'status': 'ready'})}) # we can assume it's ready because # even if it wasn't created, that means someone else # created it, and therefore that whole code block # finished executing (including the after_all_players_arrive) # inside the transaction return self._response_when_ready()
def _increment_index_in_pages(self): # when is this not the case? assert self._index_in_pages == self.participant._index_in_pages self._record_page_completion_time() # we should allow a user to move beyond the last page if it's mturk # also in general maybe we should show the 'out of sequence' page # the timeout record is irrelevant at this point, delete it # wait pages don't have a has_timeout attribute if hasattr(self, 'has_timeout') and self.has_timeout(): PageTimeout.objects.filter( participant_pk=self.participant.pk, page_index=self.participant._index_in_pages).delete() # this is causing crashes because of the weird DB issue # ParticipantToPlayerLookup.objects.filter( # participant_pk=self.participant.pk, # page_index=self.participant._index_in_pages).delete() # performance optimization: # we skip any page that is a sequence page where is_displayed # evaluates to False to eliminate unnecessary redirection views_module = otree.common_internal.get_views_module( self.subsession._meta.app_config.name) pages = views_module.page_sequence assert self.__class__ in pages pages_to_jump_by = 1 indexes = list(range(self.player._index_in_game_pages + 1, len(pages))) for target_index in indexes: Page = pages[target_index] # FIXME: are there other attributes? should i do As_view, # or simulate the # request? page = Page() page.player = self.player page.group = self.group page.subsession = self.subsession page.session = self.session # don't skip wait pages # because the user has to pass through them # so we record that they visited if hasattr(Page, 'is_displayed') and not page.is_displayed(): if hasattr(Page, '_tally_unvisited'): page._index_in_pages = self._index_in_pages + pages_to_jump_by if page.wait_for_all_groups: page._group_or_subsession = self.subsession else: page._group_or_subsession = self.group with lock_on_this_code_path(): unvisited_participants = page._tally_unvisited() # don't count myself; i only need to visit this page # if everybody else already passed it unvisited_participants.discard(self.participant.id) if not unvisited_participants: # if it's the last person # because they need to complete the wait page # don't skip past the wait page break pages_to_jump_by += 1 else: break self.player._index_in_game_pages += pages_to_jump_by self.participant._index_in_pages += pages_to_jump_by channels.Group('auto-advance-{}'.format(self.participant.code)).send({ 'text': json.dumps( {'new_index_in_pages': self.participant._index_in_pages}) })
def get_pad_group(document): """Returns the channel group to which this document belongs.""" return channels.Group("pad" + document)
def waiting(message, page): message.reply_channel.send({'accept': True}) channels.Group('render_page_{}'.format(page.replace('&', '_'))).add( message.reply_channel)
def ws_disconnect(message): if 'topology_id' in message.channel_session: channels.Group("topology-%s" % message.channel_session['topology_id']).discard(message.reply_channel)