コード例 #1
0
ファイル: test_managers.py プロジェクト: tetelle/randomise.me
 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)
コード例 #2
0
 def setUp(self):
     super(RmTrialManagerTestCase, self).setUp()
     self.today = datetime.date.today()
     self.manager = managers.RmTrialManager()
コード例 #3
0
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
コード例 #4
0
ファイル: models.py プロジェクト: swagato6/randomise.me
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()
コード例 #5
0
ファイル: test_managers.py プロジェクト: tetelle/randomise.me
 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)