def get_invitations_count(request): """ API endpoint that returns the potential participant count based on the selected experiment metadata and Institution """ form = SessionInviteForm(request.POST or None) if form.is_valid(): session_pk_list = request.POST.get('session_pk_list').split(",") experiment_metadata_ids = ExperimentSession.objects.filter( pk__in=session_pk_list).values_list('experiment_metadata__pk', flat=True) if len(set(experiment_metadata_ids)) == 1: # only allow invitations for sessions of a single ExperimentMetadata type # get the experiment metadata pk of any session (This is ensured as it is a constraint) potential_participants = Participant.objects.invitation_eligible( experiment_metadata_ids[0], gender=form.cleaned_data.get('gender'), institution=form.cleaned_data.get('affiliated_institution'), port_of_mars=form.cleaned_data.get('port_of_mars'), only_undergrad=form.cleaned_data.get('only_undergrad')) return JsonResponse({ 'success': True, 'invitesCount': len(potential_participants) }) return JsonResponse({ 'success': False, 'invitesCount': 0, 'errors': form.errors })
def submit_decision(request, experiment_id=None): form = SingleIntegerDecisionForm(request.POST or None) experiment = get_object_or_404(Experiment, pk=experiment_id) if form.is_valid(): participant_group_id = form.cleaned_data['participant_group_id'] pgr = get_object_or_404(ParticipantGroupRelationship, pk=participant_group_id) harvest_decision = form.cleaned_data['integer_decision'] submitted = form.cleaned_data['submitted'] logger.debug("pgr %s harvested %s - final submission? %s", pgr, harvest_decision, submitted) with transaction.atomic(): round_data = experiment.current_round_data set_harvest_decision(pgr, harvest_decision, round_data, submitted=submitted) message = "%s harvested %s trees" experiment.log(message % (pgr.participant, harvest_decision)) response_dict = { 'success': True, 'message': message % (pgr.participant_handle, harvest_decision), } return JsonResponse(response_dict) else: logger.debug("invalid form: %s", form) for field in form: if field.errors: logger.debug("field %s errors %s", field, field.errors) return JsonResponse({'success': False})
def like(request): form = LikeForm(request.POST or None) if form.is_valid(): participant_group_id = form.cleaned_data['participant_group_id'] target_id = form.cleaned_data['target_id'] participant_group_relationship = get_object_or_404( ParticipantGroupRelationship.objects.select_related( 'participant__user', 'group__experiment'), pk=participant_group_id, participant=request.user.participant) target = get_object_or_404(ParticipantRoundDataValue, pk=target_id) # FIXME: either needs a uniqueness constraint to ensure that duplicates don't get created or add guards when we # retrieve them to only send back the latest one (feels hacky). See # https://bitbucket.org/virtualcommons/vcweb/issue/59/get_or_create-issues-for-likes round_data = participant_group_relationship.current_round_data Like.objects.create( round_data=round_data, participant_group_relationship=participant_group_relationship, target_data_value=target) logger.debug("Participant %s liked %s", participant_group_relationship, target) return JsonResponse({'success': True}) else: logger.debug("invalid form: %s from request: %s", form, request) return JsonResponse({'success': False, 'message': 'Invalid like post'})
def post_comment(request): form = CommentForm(request.POST or None) if form.is_valid(): participant_group_id = form.cleaned_data['participant_group_id'] target_id = form.cleaned_data['target_id'] message = form.cleaned_data['message'] participant_group_relationship = get_object_or_404( ParticipantGroupRelationship.objects.select_related( 'participant__user', 'group__experiment'), pk=participant_group_id, participant=request.user.participant, ) target = get_object_or_404(ParticipantRoundDataValue, pk=target_id) Comment.objects.create( string_value=message, round_data=participant_group_relationship.current_round_data, participant_group_relationship=participant_group_relationship, target_data_value=target) logger.debug("Participant %s commented '%s' on %s", participant_group_relationship.participant, message, target) return JsonResponse({ 'success': True, 'viewModel': LighterprintsViewModel.create( participant_group_relationship).to_dict() }) else: logger.debug("invalid form: %s from request: %s", form.errors, request) return JsonResponse({ 'success': False, 'message': 'Invalid post comment' })
def post_chat_message(request): form = ChatForm(request.POST or None) if form.is_valid(): participant_group_id = form.cleaned_data['participant_group_id'] message = form.cleaned_data['message'] pgr = get_object_or_404( ParticipantGroupRelationship.objects.select_related( 'participant__user'), pk=participant_group_id, participant=request.user.participant, ) chat_message = ChatMessage.objects.create( string_value=message, participant_group_relationship=pgr) logger.debug("%s: %s", pgr.participant, chat_message) # FIXME: optimize, only retrieving the latest group activity since the last checkin time group_activity = GroupActivity( pgr, limit=LighterprintsViewModel.activity_limit) return JsonResponse({ 'success': True, 'viewModel': { 'groupActivity': group_activity.all_activities } }) return JsonResponse({ 'success': False, 'message': "Invalid chat message post" })
def perform_activity(request): form = ActivityForm(request.POST or None) if form.is_valid(): activity_id = form.cleaned_data['activity_id'] participant_group_id = form.cleaned_data['participant_group_id'] logger.debug("%s request to perform activity %s", participant_group_id, activity_id) participant_group_relationship = get_object_or_404( ParticipantGroupRelationship.objects.select_related( 'participant__user', 'group__experiment'), pk=participant_group_id) if participant_group_relationship.participant == request.user.participant: activity = get_object_or_404(Activity, pk=activity_id) performed_activity = do_activity( activity=activity, participant_group_relationship=participant_group_relationship) experiment = participant_group_relationship.experiment if performed_activity is not None: participant_group_relationship.set_first_visit() return JsonResponse({ 'success': True, 'viewModel': LighterprintsViewModel.create( participant_group_relationship, experiment).to_dict() }) else: message = "Activity was not available at this time" else: message = "Unauthorized access logged for %s" % participant_group_relationship logger.warning( "authenticated user %s tried to perform activity %s as %s", request.user, activity_id, participant_group_relationship) return JsonResponse({'success': False, 'response': message})
def add_participant(request, pk=None): participant_email = request.POST.get('participantEmail') participant = get_object_or_404(Participant.objects.select_related('user'), user__email=participant_email) es = get_object_or_404(ExperimentSession, pk=pk) # First check that the experiment session has already been completed if es.scheduled_end_date > datetime.now(): logger.debug("%s tried to add participant %s to pending experiment session %s - ignoring", request.user, participant, pk) return JsonResponse({'success': False, 'error': "You can't manually add a participant to a pending experiment session."}) # there should only be a single invitation for a given experiment session and a given participant try: invitation = es.invitation_set.get(participant=participant) except Invitation.DoesNotExist: logger.debug("%s tried to add participant %s without an invitation to experiment session %s. Creating a new one.", request.user, participant, pk) # create an invitation for that user invitation = Invitation.objects.create(participant=participant, experiment_session=es, sender=request.user) except Invitation.MultipleObjectsReturned: logger.error("Experiment session %s had multiple invitations created for participant %s", es, participant) invitation = es.invitation_set.first() # third and final check, the participant must not have already signed up for the experiment session if invitation.signup_set.exists(): logger.debug("%s tried to add participant %s to experiment session %s but they are already signed up", request.user, participant, pk) return JsonResponse({'success': False, 'error': 'Participant is already signed up for this experiment session'}) else: ParticipantSignup.objects.create(invitation=invitation, attendance=ParticipantSignup.ATTENDANCE.participated) return JsonResponse({'success': True})
def manage_experiment_session(request, pk): form = ExperimentSessionForm(request.POST or None, pk=pk, user=request.user) if form.is_valid(): es = form.save() return JsonResponse({'success': True, 'session': es}) return JsonResponse({'success': False, 'errors': form.errors})
def invite_email_preview(request): """ Generates preview email for the provided invitation details """ form = SessionInviteForm(request.POST or None) message = "Please fill in all the form fields to preview the invitation email." if form.is_valid(): session_ids = request.POST.get('session_pk_list').split(",") invitation_text = form.cleaned_data.get('invitation_text') plaintext_content = InvitationEmail(request).get_plaintext_content(invitation_text, session_ids) html_content = markdown.markdown(plaintext_content) return JsonResponse({'success': True, 'content': html_content}) return JsonResponse({'success': False, 'message': message})
def get_session_events(request): """ Returns the list of Experiment sessions that fall within the given range, Used by the subject pool calendar to display experiment sessions falling within the date range shown """ from_date = request.GET.get('from', None) to_date = request.GET.get('to', None) criteria = dict() if to_date: criteria.update( scheduled_end_date__gte=timestamp_to_datetime(from_date)) queryset = ExperimentSession.objects.select_related( 'experiment_metadata').filter(**criteria) objects_body = [] for event in queryset: index = event.pk % 20 # for color selection field = { "id": event.pk, "title": event.experiment_metadata.title, "url": "session/detail/event/" + str(event.pk), "class": "event-color-" + str(index), "start": datetime_to_timestamp(event.scheduled_date), "end": datetime_to_timestamp(event.scheduled_end_date), "capacity": event.capacity } objects_body.append(field) objects_head = {"success": True, "result": objects_body} return JsonResponse(objects_head)
def get_view_model(request, experiment_id=None): experiment = get_object_or_404(Experiment.objects.select_related( 'experiment_metadata', 'experiment_configuration'), pk=experiment_id) pgr = experiment.get_participant_group_relationship( request.user.participant) return JsonResponse(ViewModel(pgr, experiment=experiment).to_dict())
def get_view_model(request, participant_group_id=None): if participant_group_id is None: # check in the request query parameters as well participant_group_id = request.GET.get('participant_group_id') pgr = get_object_or_404(ParticipantGroupRelationship.objects.select_related('participant__user', 'group__experiment'), pk=participant_group_id) if pgr.participant != request.user.participant: # check that authenticated participant is the same as the participant whose data is being requested logger.warning("user %s tried to access view model for %s", request.user.participant, pgr) raise PermissionDenied("You don't appear to have permission to access this experiment.") return JsonResponse({'success': True, 'viewModel': LighterprintsViewModel.create(pgr).to_dict()})
def send_invitations(request): """ Sends out invitation emails to random participants which match the required invitation criteria, using the provided email subject and message. Also returns the total number of invites sent. """ user = request.user form = SessionInviteForm(request.POST or None) if form.is_valid(): invitation_subject = form.cleaned_data.get('invitation_subject') invitation_text = form.cleaned_data.get('invitation_text') # use currently logged in experimenter as the sender of the # invitations. from_email = user.email session_pk_list = request.POST.get('session_pk_list').split(",") invitation_count = form.cleaned_data.get('number_of_people') affiliated_institution = form.cleaned_data.get( 'affiliated_institution') experiment_sessions = ExperimentSession.objects.filter( pk__in=session_pk_list) experiment_metadata_pk_list = experiment_sessions.values_list( 'experiment_metadata__pk', flat=True) if len(set(experiment_metadata_pk_list)) == 1: # get the experiment metadata pk of any session, as all sessions selected by experimenter to send # invitations belong to same experiment metadata (This has to be ensured as it is a constraint) experiment_metadata_pk = experiment_metadata_pk_list[0] potential_participants = list( Participant.objects.invitation_eligible( experiment_metadata_pk, institution=affiliated_institution, only_undergrad=form.cleaned_data.get('only_undergrad'), gender=form.cleaned_data.get('gender'))) potential_participants_count = len(potential_participants) final_participants = None if potential_participants_count == 0: final_participants = [] message = "There are no more eligible participants that can be invited for this experiment." else: if potential_participants_count < invitation_count: # less candidate participants than the number of requested # participants, use them all final_participants = potential_participants else: final_participants = random.sample(potential_participants, invitation_count) message = "Invitations were sent to %s / %s participants." % ( len(final_participants), invitation_count) today = timezone.now() invitations = [] recipient_list = [settings.DEFAULT_FROM_EMAIL] for participant in final_participants: recipient_list.append(participant.email) invitations.extend([ Invitation(participant=participant, experiment_session=es, date_created=today, sender=user) for es in experiment_sessions ]) Invitation.objects.bulk_create(invitations) if settings.ENVIRONMENT.is_production: ie = InvitationEmail(request) plaintext_content = ie.get_plaintext_content( invitation_text, session_pk_list) html_content = markdown.markdown(plaintext_content) msg = EmailMultiAlternatives(subject=invitation_subject, body=plaintext_content, from_email=from_email, to=[from_email], bcc=recipient_list) msg.attach_alternative(html_content, "text/html") msg.send() else: logger.debug( "Sending invitation emails in non-production environment is disabled: %s", recipient_list) return JsonResponse({ 'success': True, 'message': message, 'invitesCount': len(final_participants) }) else: return JsonResponse({ 'success': False, 'message': "Please select experiment sessions from the same experiment to send invitations." }) # Form is not valid return JsonResponse({'success': False, 'errors': form.errors})