def test_active(self): "Return the currently active models" manager = managers.RmTrialManager() with patch.object(manager, 'filter') as pfilt: manager.active() pfilt.assert_called_once_with(start_date__lte=self.today, finish_date__gte=self.today)
def setUp(self): super(RmTrialManagerTestCase, self).setUp() self.today = datetime.date.today() self.manager = managers.RmTrialManager()
class Trial(models.Model): """ An individual trial that we are running. """ ANYONE = 'an' INVITATION = 'in' RECRUITMENT_CHOICES = ( (ANYONE, 'Anyone can join'), (INVITATION, "Only people I've invited can join") ) IMMEDIATE = 'im' HOURS = 'ho' DATE = 'da' INSTRUCTION_CHOICES = ( (IMMEDIATE, 'Straight away after randomisation'), (HOURS, 'X hours after randomisation'), (DATE, 'On this date...') ) DAILY = 'da' WEEKLY = 'we' MONTHLY = 'mo' FREQ_CHOICES = ( (DAILY, 'Once per day'), (WEEKLY, 'Once per week'), (MONTHLY, 'Once per month') ) HELP_PART = """Who can participate in this trial? (Everyone? People with an infant under 6 months? People who binge drink Alcohol?)""" HELP_A = """These are the instructions that will be sent to the group who get the intervention""" HELP_B = """These are the instructions that will be sent to the control group""" HELP_START = "The date you would like your trial to start" HELP_FINISH = "The date you would like your trial to finish" # Step 1 title = models.CharField(max_length=200, blank=True, null=True) # Step 2 reporting_freq = models.CharField("Reporting frequency", max_length=200, choices=FREQ_CHOICES, default=DAILY) # Step 3 min_participants = models.IntegerField() max_participants = models.IntegerField() # Step 4 recruitment = models.CharField(max_length=2, choices=RECRUITMENT_CHOICES, default=ANYONE) # Step 5 description = models.TextField(blank=True, null=True) group_a_desc = models.TextField("Intervention Group description", blank=True, null=True) group_b_desc = models.TextField("Control Group description", blank=True, null=True) # Step 6 group_a = models.TextField("Intervention Group Instructions", help_text=HELP_A) group_b = models.TextField("Control Group Instructions", help_text=HELP_B) instruction_delivery = models.TextField(max_length=2, choices=INSTRUCTION_CHOICES, default=IMMEDIATE) instruction_hours_after = models.IntegerField(blank=True, null=True) instruction_date = models.DateField(blank=True, null=True) group_a_expected = models.IntegerField(blank=True, null=True) group_b_impressed = models.IntegerField(blank=True, null=True) start_date = models.DateField(help_text=HELP_START) finish_date = models.DateField(help_text=HELP_FINISH) finished = models.BooleanField(default=False, editable=False) owner = models.ForeignKey(settings.AUTH_USER_MODEL) featured = models.BooleanField(default=False) recruiting = models.BooleanField(default=True) participants = models.TextField(help_text=HELP_PART, blank=True, null=True) is_edited = models.BooleanField(default=False) private = models.BooleanField(default=False) objects = managers.RmTrialManager() def __unicode__(self): """ Nice printing representation """ return '<{0}>'.format(self.title) def get_absolute_url(self): return reverse('trial-detail', kwargs={'pk': self.pk}) def save(self): """ Check for recruiting status Return: None Exceptions: None """ self.recruiting = self.can_join() return super(Trial, self).save() def results(self): """ Return the results of this trial Return: dict Exceptions: None """ avg = lambda g: self.report_set.filter( group__name=g).aggregate(Avg('score')).values()[0] intervention_group = avg('A') control_group = avg('B') return [dict(name='Intervention Group', avg=intervention_group), dict(name='Control Group', avg=control_group)] @property def started(self): """ Property to determine whether this trial has started or not. Return: bool Exceptions: None """ if self.start_date is None: return False if self.start_date <= datetime.date.today(): return True return False @property def finished(self): """ Predicate property to determine whether this trial is finished. Return: bool Exceptions: None """ if self.finish_date < td(): return True return False @property def active(self): """ Property to determine whether this trial is active today. Return: bool Exceptions: None """ if not self.start_date or not self.finish_date: return False if self.start_date <= td() and self.finish_date >= td(): return True return False @property def is_invitation_only(self): """ Predicate property to determine whether the trial is invitation_only. Return: None Exceptions: None """ return self.recruitment == self.INVITATION def related(self): """ Get trials possibly related to this one. Return: Queryset Exceptions: None """ return Trial.objects.exclude(pk=self.pk)[:5] def time_remaining(self): """ How much time is between now and the end of the trial? Return: timedelta or str Exceptions: None """ if self.finish_date < td(): return 'finished' return self.finish_date - td() def can_join(self): """ Predicate method to determine whether users are able to join this trial. We decide that a trial is unjoinable if it's finish date has passed, or if it's max participants limit has been met. """ if self.finish_date < td(): return False if self.participant_set.count() >= self.max_participants: return False return True def needs(self): """ How many participants does this trial need? Return: bool Exceptions: None """ return (self.min_participants - self.participant_set.count() ) def needs_participants(self): """ Does this trial need participants? Return: bool Exceptions: None """ return self.needs() > 0 def ensure_groups(self): """ Ensure that the groups for this trial exist. """ groupa = Group.objects.get_or_create(trial=self, name=Group.GROUP_A)[0] groupb = Group.objects.get_or_create(trial=self, name=Group.GROUP_B)[0] return groupa, groupb def join(self, user): """ Add a user to our trial. Make sure that the trial has groups, then randomly assign USER to one of those groups. Ensure that we haven't gone over the max_participants level, raising TooManyParticipantsError if we have. Ensure that this user hasn't already joined the trial, raising AlreadyJoinedError if we have. Ensure that this user doesn't own the trial, raising TrialOwnerError if they do. Ensure that this trial isn't already finished, raising TrialFinishedError if it is. If nobody has joined yet, we go to Group A, else Group A if the groups are equal, else Group B. """ if self.owner == user: raise exceptions.TrialOwnerError() today = datetime.date.today() if self.finish_date < today: raise exceptions.TrialFinishedError() if Participant.objects.filter(trial=self, user=user).count() > 0: raise exceptions.AlreadyJoinedError() if self.participant_set.count() >= self.max_participants: raise exceptions.TooManyParticipantsError() part = Participant(trial=self, user=user).randomise() part.save() if self.instruction_delivery == self.IMMEDIATE: part.send_instructions() if self.instruction_delivery == self.HOURS: eta = datetime.utcnow() + datetime.timedelta(seconds=60*60*self.instruction_hours_after) tasks.instruct_later.apply_async((participant.pk), eta=eta) return def randomise(self): """ Randomise the participants of this trial. If we have already randomised the participants, raise AlreadyRandomisedError. Return: None Exceptions: AlreadyRandomisedError """ if self.participant_set.filter(group__isnull=False).count() > 0: raise exceptions.AlreadyRandomisedError() groupa, groupb = self.ensure_groups() for participant in self.participant_set.all(): participant.group = random.choice([groupa, groupb]) participant.save() return def send_instructions(self): """ Email the participants of this trial with their instructions. Return: None Exceptions: - TrialFinishedError: The trial has finished - TrialNotStartedError: The trial is yet to start """ if self.start_date is not None and self.start_date > td(): raise exceptions.TrialNotStartedError() if self.finish_date is not None and self.finish_date < td(): raise exceptions.TrialFinishedError() for participant in self.participant_set.all(): participant.send_instructions() return
class Trial(VotableMixin, models.Model): """ An individual trial that we are running. """ ANYONE = 'an' INVITATION = 'in' RECRUITMENT_CHOICES = ((ANYONE, 'Anyone can join'), (INVITATION, "Only people I've invited can join")) IMMEDIATE = 'im' HOURS = 'ho' DATE = 'da' ON_DEMAND = 'de' INSTRUCTION_CHOICES = ( (IMMEDIATE, 'Straight away after randomisation'), # (HOURS, 'X hours after randomisation'), (DATE, 'On this date...'), (ON_DEMAND, 'On Demand')) ONCE = 'on' WHENEVER = 'wh' DATED = 'da' REGULARLY = 're' OFFLINE = 'of' REPORT_STYLE_CHOICES = ( (ONCE, 'Once only'), (WHENEVER, 'Whenever they want'), (DATED, 'On date x'), # (REGULARLY, 'Regularly') ) DAILY = 'da' WEEKLY = 'we' MONTHLY = 'mo' FREQ_CHOICES = ((DAILY, 'Once per day'), (WEEKLY, 'Once per week'), (MONTHLY, 'Once per month')) MANUALLY = 'ma' REPORT_NUM = 're' ENDING_CHOICES = ((MANUALLY, 'Manually'), (REPORT_NUM, 'After X have reported'), (DATED, 'On date Y')) HELP_PART = """Who can participate in this trial? (Everyone? People with an infant under 6 months? People who binge drink Alcohol?)""" HELP_A = """These are the instructions that will be sent to group A""" HELP_B = """These are the instructions that will be sent to group B""" # Step 1 title = models.CharField(max_length=200, blank=True, null=True) # Step 2 reporting_style = models.CharField("Reporting Style", max_length=2, choices=REPORT_STYLE_CHOICES, default=ONCE) reporting_freq = models.CharField("Reporting frequency", max_length=2, choices=FREQ_CHOICES, default=DAILY) reporting_date = models.DateField('Reporting date', blank=True, null=True) # Step 3 min_participants = models.IntegerField( "I think I need a minimum of x participants for a meaningful answer") # Step 4 recruitment = models.CharField(max_length=2, choices=RECRUITMENT_CHOICES, default=ANYONE) # Step 5 description = models.TextField(blank=True, null=True) image = thumbnail.ImageField(upload_to='uploads', blank=True, null=True) secret_info = models.TextField(blank=True, null=True) # Step 6 group_a = models.TextField("Group A Instructions", help_text=HELP_A) group_b = models.TextField("Group B Instructions", help_text=HELP_B) instruction_delivery = models.CharField(max_length=2, choices=INSTRUCTION_CHOICES, default=IMMEDIATE) instruction_hours_after = models.IntegerField(blank=True, null=True) instruction_date = models.DateField(blank=True, null=True) # Step 7 ending_style = models.CharField(max_length=2, choices=ENDING_CHOICES, default=MANUALLY) ending_reports = models.IntegerField(blank=True, null=True) ending_date = models.DateField(blank=True, null=True) # Currently unused power calcs group_a_expected = models.IntegerField(blank=True, null=True) group_b_impressed = models.IntegerField(blank=True, null=True) # popularity votes = generic.GenericRelation(Vote) # Metadata owner = models.ForeignKey(settings.AUTH_USER_MODEL) n1trial = models.BooleanField(default=False) offline = models.BooleanField(default=False) featured = models.BooleanField(default=False) stopped = models.BooleanField(default=False) is_edited = models.BooleanField(default=False) created = models.DateTimeField(default=lambda: datetime.datetime.now()) private = models.BooleanField(default=False) hide = models.NullBooleanField(default=False, blank=True, null=True) parent = models.ForeignKey('self', blank=True, null=True, related_name='child') # Currently unused advanced user participants participants = models.TextField(help_text=HELP_PART, blank=True, null=True) objects = managers.RmTrialManager() def __unicode__(self): """ Nice printing representation """ return u'#({0}) {1}'.format(self.pk, self.title) def get_absolute_url(self): return reverse('trial-detail', kwargs={'pk': self.pk}) def save(self): """ Check for recruiting status Return: None Exceptions: None """ if self.recruitment == self.INVITATION: self.private = True return super(Trial, self).save() def image_url(self): """ Return the url for SELF.image or None Return: str or None Exceptions: None """ if not self.image: return return settings.MEDIA_URL + self.image.file.name.split('/')[-1] def results(self): """ Return the results of this trial Return: dict Exceptions: None """ anal = self.trialanalysis_set.get() return [ dict(name='Group A', avg=anal.meana), dict(name='Group B', avg=anal.meanb) ] @property def started(self): """ Property to determine whether this trial has started or not. Return: bool Exceptions: None """ if self.start_date is None: return False if self.start_date <= datetime.date.today(): return True return False @property def finished(self): """ Predicate property to determine whether this trial is finished. Return: bool Exceptions: None """ if self.stopped: return True return False @property def active(self): """ Property to determine whether this trial is active today. Return: bool Exceptions: None """ if not self.start_date or not self.finish_date: return False if self.start_date <= td() and self.finish_date >= td(): return True return False @property def is_invitation_only(self): """ Predicate property to determine whether the trial is invitation_only. Return: None Exceptions: None """ return self.recruitment == self.INVITATION def main_outcome(self): """ Return the trial's main outcome """ try: return self.variable_set.all()[0] except IndexError: return [] def related(self): """ Get trials possibly related to this one. Return: Queryset Exceptions: None """ return Trial.objects.exclude(pk=self.pk)[:5] def time_remaining(self): """ How much time is between now and the end of the trial? Return: timedelta or str Exceptions: None """ return 'No longer appropriate' def can_join(self): """ Predicate method to determine whether users are able to join this trial. We decide that a trial is unjoinable if it's finish date has passed, or if it's max participants limit has been met. """ if self.stopped == True or self.n1trial == True: return False return True def needs(self): """ How many participants does this trial need? Return: bool Exceptions: None """ return (self.min_participants - self.participant_set.count()) def needs_participants(self): """ Does this trial need participants? Return: bool Exceptions: None """ return self.needs() > 0 def ensure_groups(self): """ Ensure that the groups for this trial exist. """ groupa = Group.objects.get_or_create(trial=self, name=Group.GROUP_A)[0] groupb = Group.objects.get_or_create(trial=self, name=Group.GROUP_B)[0] return groupa, groupb def join(self, user): """ Add a user to our trial. Make sure that the trial has groups, then randomly assign USER to one of those groups. Ensure that this user hasn't already joined the trial, raising AlreadyJoinedError if we have. Ensure that this trial isn't already finished, raising TrialFinishedError if it is. If nobody has joined yet, we go to Group A, else Group A if the groups are equal, else Group B. """ if self.stopped: raise exceptions.TrialFinishedError() if Participant.objects.filter(trial=self, user=user).count() > 0: raise exceptions.AlreadyJoinedError() part = Participant(trial=self, user=user).randomise() part.save() if self.instruction_delivery == self.IMMEDIATE: part.send_instructions() if self.instruction_delivery == self.HOURS: eta = datetime.utcnow() + datetime.timedelta( seconds=60 * 60 * self.instruction_hours_after) tasks.instruct_later.apply_async((participant.pk), eta=eta) return def randomise(self): """ Randomise the participants of this trial. If we have already randomised the participants, raise AlreadyRandomisedError. Return: None Exceptions: AlreadyRandomisedError """ if self.participant_set.filter(group__isnull=False).count() > 0: raise exceptions.AlreadyRandomisedError() groupa, groupb = self.ensure_groups() for participant in self.participant_set.all(): participant.group = random.choice([groupa, groupb]) participant.save() return def send_instructions(self): """ Email the participants of this trial with their instructions. Return: None Exceptions: - TrialFinishedError: The trial has finished - TrialNotStartedError: The trial is yet to start """ # if self.start_date is not None and self.start_date > td(): # raise exceptions.TrialNotStartedError() if self.stopped: raise exceptions.TrialFinishedError() for participant in self.participant_set.all(): participant.send_instructions() return def stop(self): """ Stop this trial please. Return: None Exceptions: None """ self.stopped = True self.save() TrialAnalysis.report_on(self) if self.offline: return for participant in self.participant_set.exclude(user=self.owner): participant.send_ended_notification() return def num_reports(self): """ the number of completed reports Return: int Exceptions: None """ return self.report_set.exclude(date__isnull=True).count()
def test_starting_today(self): "Return trials that start today" manager = managers.RmTrialManager() with patch.object(manager, 'filter') as pfilt: manager.starting_today() pfilt.assert_called_once_with(start_date=self.today)