diff --git a/wagtail_ab_testing/models.py b/wagtail_ab_testing/models.py index 5d3121c..6e016b4 100644 --- a/wagtail_ab_testing/models.py +++ b/wagtail_ab_testing/models.py @@ -19,7 +19,7 @@ class AbTestManager(models.Manager): def get_current_for_page(self, page): - return self.get_queryset().filter(page=page).exclude(status__in=[AbTest.Status.CANCELLED, AbTest.Status.COMPLETED]).first() + return self.get_queryset().filter(page=page).exclude(status__in=[AbTest.STATUS_CANCELLED, AbTest.STATUS_COMPLETED]).first() class AbTest(models.Model): @@ -30,30 +30,45 @@ class AbTest(models.Model): the `.variant_revision` field contains the changes that are being tested. """ - class Status(models.TextChoices): - DRAFT = 'draft', __('Draft') - RUNNING = 'running', __('Running') - PAUSED = 'paused', __('Paused') - CANCELLED = 'cancelled', __('Cancelled') - - # These two sound similar, but there's a difference: - # 'Finished' means that we've reached the sample size and testing has stopped - # but the user still needs to decide whether to publish the variant version - # or revert back to the control. - # Once they've decided and that action has taken place, the test status is - # updated to 'Completed'. - FINISHED = 'finished', __('Finished') - COMPLETED = 'completed', __('Completed') - - class Version(models.TextChoices): - CONTROL = 'control', __('Control') - VARIANT = 'variant', __('Variant') - - class CompletionAction(models.TextChoices): - # See docstring of the .complete() method for descriptions - DO_NOTHING = 'do-nothing', "Do nothing" - REVERT = 'revert', "Revert to control" - PUBLISH = 'publisn', "Publish variant" + STATUS_DRAFT = 'draft' + STATUS_RUNNING = 'running' + STATUS_PAUSED = 'paused' + STATUS_CANCELLED = 'cancelled' + # These two sound similar, but there's a difference: + # 'Finished' means that we've reached the sample size and testing has stopped + # but the user still needs to decide whether to publish the variant version + # or revert back to the control. + # Once they've decided and that action has taken place, the test status is + # updated to 'Completed'. + STATUS_FINISHED = 'finished' + STATUS_COMPLETED = 'completed' + + STATUS_CHOICES = [ + (STATUS_DRAFT, __('Draft')), + (STATUS_RUNNING, __('Running')), + (STATUS_PAUSED, __('Paused')), + (STATUS_CANCELLED, __('Cancelled')), + (STATUS_FINISHED, __('Finished')), + (STATUS_COMPLETED, __('Completed')), + ] + + VERSION_CONTROL = 'control' + VERSION_VARIANT = 'variant' + + VERSION_CHOICES = [ + (VERSION_CONTROL, __('Control')), + (VERSION_VARIANT, __('Variant')), + ] + + COMPLETION_ACTION_DO_NOTHING = 'do-nothing' + COMPLETION_ACTION_REVERT = 'revert' + COMPLETION_ACTION_PUBLISH = 'publish' + + COMPLETION_ACTION_CHOICES = [ + (COMPLETION_ACTION_DO_NOTHING, "Do nothing"), + (COMPLETION_ACTION_REVERT, "Revert"), + (COMPLETION_ACTION_PUBLISH, "Publish"), + ] page = models.ForeignKey('wagtailcore.Page', on_delete=models.CASCADE, related_name='ab_tests') name = models.CharField(max_length=255) @@ -63,8 +78,8 @@ class CompletionAction(models.TextChoices): goal_page = models.ForeignKey('wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') sample_size = models.PositiveIntegerField(validators=[MinValueValidator(1)]) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='+') - status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT) - winning_version = models.CharField(max_length=9, null=True, choices=Version.choices) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_DRAFT) + winning_version = models.CharField(max_length=9, null=True, choices=VERSION_CHOICES) first_started_at = models.DateTimeField(null=True) # Because an admin can pause/resume tests, we need to make sure we record the amount of time it has been running @@ -87,13 +102,13 @@ def start(self): """ Starts/unpauses the test. """ - if self.status in [self.Status.DRAFT, self.Status.PAUSED]: + if self.status in [self.STATUS_DRAFT, self.STATUS_PAUSED]: self.current_run_started_at = timezone.now() - if self.status == self.Status.DRAFT: + if self.status == self.STATUS_DRAFT: self.first_started_at = self.current_run_started_at - self.status = self.Status.RUNNING + self.status = self.STATUS_RUNNING self.save(update_fields=['status', 'current_run_started_at', 'first_started_at']) @@ -101,8 +116,8 @@ def pause(self): """ Pauses the test. """ - if self.status == self.Status.RUNNING: - self.status = self.Status.PAUSED + if self.status == self.STATUS_RUNNING: + self.status = self.STATUS_PAUSED if self.current_run_started_at is not None: self.previous_run_duration += timezone.now() - self.current_run_started_at @@ -118,7 +133,7 @@ def get_results_url(self): Afterwards, we need to send them to a separate view as the page editor returns to normal. """ - if self.status in [AbTest.Status.COMPLETED, AbTest.Status.CANCELLED]: + if self.status in [AbTest.STATUS_COMPLETED, AbTest.STATUS_CANCELLED]: return reverse('wagtail_ab_testing:results', args=[self.page_id, self.id]) else: @@ -130,7 +145,7 @@ def total_running_duration(self): """ duration = self.previous_run_duration - if self.status == self.Status.RUNNING: + if self.status == self.STATUS_RUNNING: duration += timezone.now() - self.current_run_started_at return duration @@ -139,7 +154,7 @@ def cancel(self): """ Cancels the test. """ - self.status = self.Status.CANCELLED + self.status = self.STATUS_CANCELLED self.save(update_fields=['status']) @@ -153,7 +168,7 @@ def finish(self): publish the variant). This decision is set using the .complete() method. """ - self.status = self.Status.FINISHED + self.status = self.STATUS_FINISHED self.winning_version = self.check_for_winner() self.save(update_fields=['status', 'winning_version']) @@ -164,28 +179,28 @@ def complete(self, action, user=None): Completes the test and carries out the specificed action. Actions can be: - - AbTest.CompletionAction.DO_NOTHING - This just completes + - AbTest.COMPLETION_ACTION_DO_NOTHING - This just completes the test but does nothing to the page. The control will remain the published version and the variant will be in draft. - - AbTest.CompletionAction.REVERT - This completes the test + - AbTest.COMPLETION_ACTION_REVERT - This completes the test and also creates a new revision to revert the content back to what it was in the control while the test was taking place. - - AbTest.CompletionAction.PUBLISH - This completes the test + - AbTest.COMPLETION_ACTION_PUBLISH - This completes the test and also publishes the variant revision. """ - self.status = self.Status.COMPLETED + self.status = self.STATUS_COMPLETED self.save(update_fields=['status']) - if action == AbTest.CompletionAction.DO_NOTHING: + if action == AbTest.COMPLETION_ACTION_DO_NOTHING: pass - elif action == AbTest.CompletionAction.REVERT: + elif action == AbTest.COMPLETION_ACTION_REVERT: # Create a new revision with the content of the live page and publish it self.page.save_revision(user=user, log_action='wagtail.revert').publish(user=user) - elif action == AbTest.CompletionAction.PUBLISH: + elif action == AbTest.COMPLETION_ACTION_PUBLISH: self.variant_revision.publish(user=user) def add_participant(self, version=None): @@ -194,8 +209,8 @@ def add_participant(self, version=None): """ # Get current numbers of participants for each version stats = self.hourly_logs.aggregate( - control_participants=Sum('participants', filter=Q(version=self.Version.CONTROL)), - variant_participants=Sum('participants', filter=Q(version=self.Version.VARIANT)), + control_participants=Sum('participants', filter=Q(version=self.VERSION_CONTROL)), + variant_participants=Sum('participants', filter=Q(version=self.VERSION_VARIANT)), ) control_participants = stats['control_participants'] or 0 variant_participants = stats['variant_participants'] or 0 @@ -203,15 +218,15 @@ def add_participant(self, version=None): # Create an equal number of participants for each version if version is None: if variant_participants > control_participants: - version = self.Version.CONTROL + version = self.VERSION_CONTROL elif variant_participants < control_participants: - version = self.Version.VARIANT + version = self.VERSION_VARIANT else: version = random.choice([ - self.Version.CONTROL, - self.Version.VARIANT, + self.VERSION_CONTROL, + self.VERSION_VARIANT, ]) # Add new participant to statistics model @@ -240,7 +255,7 @@ def check_for_winner(self): """ Performs a Chi-Squared test to check if there is a clear winner. - Returns Version.CONTROL or Version.VARIANT if there is one. Otherwise, it returns None. + Returns VERSION_CONTROL or VERSION_VARIANT if there is one. Otherwise, it returns None. For more information on what the Chi-Squared test does, see: https://www.evanmiller.org/ab-testing/chi-squared.html @@ -248,10 +263,10 @@ def check_for_winner(self): """ # Fetch stats from database stats = self.hourly_logs.aggregate( - control_participants=Sum('participants', filter=Q(version=self.Version.CONTROL)), - control_conversions=Sum('conversions', filter=Q(version=self.Version.CONTROL)), - variant_participants=Sum('participants', filter=Q(version=self.Version.VARIANT)), - variant_conversions=Sum('conversions', filter=Q(version=self.Version.VARIANT)), + control_participants=Sum('participants', filter=Q(version=self.VERSION_CONTROL)), + control_conversions=Sum('conversions', filter=Q(version=self.VERSION_CONTROL)), + variant_participants=Sum('participants', filter=Q(version=self.VERSION_VARIANT)), + variant_conversions=Sum('conversions', filter=Q(version=self.VERSION_VARIANT)), ) control_participants = stats['control_participants'] or 0 control_conversions = stats['control_conversions'] or 0 @@ -284,9 +299,9 @@ def check_for_winner(self): # There is a clear winner! # Return the one with the highest success rate if (control_conversions / control_participants) > (variant_conversions / variant_participants): - return self.Version.CONTROL + return self.VERSION_CONTROL else: - return self.Version.VARIANT + return self.VERSION_VARIANT def get_status_description(self): """ @@ -294,16 +309,16 @@ def get_status_description(self): """ status = self.get_status_display() - if self.status == AbTest.Status.RUNNING: + if self.status == AbTest.STATUS_RUNNING: participants = self.hourly_logs.aggregate(participants=Sum('participants'))['participants'] or 0 completeness_percentange = int((participants * 100) / self.sample_size) return status + f" ({completeness_percentange}%)" - elif self.status in [AbTest.Status.FINISHED, AbTest.Status.COMPLETED]: - if self.winning_version == AbTest.Version.CONTROL: + elif self.status in [AbTest.STATUS_FINISHED, AbTest.STATUS_COMPLETED]: + if self.winning_version == AbTest.VERSION_CONTROL: return status + " (" + _("Control won") + ")" - elif self.winning_version == AbTest.Version.VARIANT: + elif self.winning_version == AbTest.VERSION_VARIANT: return status + " (" + _("Variant won") + ")" else: @@ -315,7 +330,7 @@ def get_status_description(self): class AbTestHourlyLog(models.Model): ab_test = models.ForeignKey(AbTest, on_delete=models.CASCADE, related_name='hourly_logs') - version = models.CharField(max_length=9, choices=AbTest.Version.choices) + version = models.CharField(max_length=9, choices=AbTest.VERSION_CHOICES) date = models.DateField() # UTC hour. Values range from 0 to 23 hour = models.PositiveSmallIntegerField() @@ -385,8 +400,8 @@ class Meta: @receiver(page_unpublished) def cancel_on_page_unpublish(instance, **kwargs): - for ab_test in AbTest.objects.filter(page=instance, status__in=[AbTest.Status.DRAFT, AbTest.Status.RUNNING, AbTest.Status.PAUSED]): + for ab_test in AbTest.objects.filter(page=instance, status__in=[AbTest.STATUS_DRAFT, AbTest.STATUS_RUNNING, AbTest.STATUS_PAUSED]): ab_test.cancel() - for ab_test in AbTest.objects.filter(page=instance, status=AbTest.Status.FINISHED): - ab_test.complete(AbTest.CompletionAction.DO_NOTHING) + for ab_test in AbTest.objects.filter(page=instance, status=AbTest.STATUS_FINISHED): + ab_test.complete(AbTest.COMPLETION_ACTION_DO_NOTHING) diff --git a/wagtail_ab_testing/test/tests/test_abtest_model.py b/wagtail_ab_testing/test/tests/test_abtest_model.py index 8e0e4df..7ee4df9 100644 --- a/wagtail_ab_testing/test/tests/test_abtest_model.py +++ b/wagtail_ab_testing/test/tests/test_abtest_model.py @@ -24,12 +24,12 @@ def setUp(self): def test_finish(self): self.ab_test.finish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.FINISHED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_FINISHED) def test_cancel(self): self.ab_test.cancel() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) def test_add_participant(self): version = self.ab_test.add_participant() @@ -44,19 +44,19 @@ def test_add_participant(self): self.assertEqual(log.conversions, 0) def test_log_conversion(self): - self.ab_test.log_conversion(AbTest.Version.CONTROL) + self.ab_test.log_conversion(AbTest.VERSION_CONTROL) # This should've created a history log log = self.ab_test.hourly_logs.get() self.assertEqual(log.date, datetime.date(2020, 11, 4)) self.assertEqual(log.hour, 22) - self.assertEqual(log.version, AbTest.Version.CONTROL) + self.assertEqual(log.version, AbTest.VERSION_CONTROL) self.assertEqual(log.participants, 0) self.assertEqual(log.conversions, 1) # Now add another - self.ab_test.log_conversion(AbTest.Version.CONTROL) + self.ab_test.log_conversion(AbTest.VERSION_CONTROL) log.refresh_from_db() @@ -66,7 +66,7 @@ def test_log_conversion(self): def set_up_test(self, control_participants, control_conversions, variant_participants, variant_conversions): AbTestHourlyLog.objects.create( ab_test=self.ab_test, - version=AbTest.Version.CONTROL, + version=AbTest.VERSION_CONTROL, date=datetime.date(2020, 11, 4), hour=22, participants=control_participants, @@ -75,7 +75,7 @@ def set_up_test(self, control_participants, control_conversions, variant_partici AbTestHourlyLog.objects.create( ab_test=self.ab_test, - version=AbTest.Version.VARIANT, + version=AbTest.VERSION_VARIANT, date=datetime.date(2020, 11, 4), hour=22, participants=variant_participants, @@ -90,22 +90,22 @@ def test_check_for_winner_no_data(self): def test_check_control_clearly_wins(self): self.set_up_test(100, 80, 100, 20) - self.assertEqual(self.ab_test.check_for_winner(), AbTest.Version.CONTROL) + self.assertEqual(self.ab_test.check_for_winner(), AbTest.VERSION_CONTROL) def test_check_variantarly_wins(self): self.set_up_test(100, 20, 100, 80) - self.assertEqual(self.ab_test.check_for_winner(), AbTest.Version.VARIANT) + self.assertEqual(self.ab_test.check_for_winner(), AbTest.VERSION_VARIANT) def test_control_just_wins(self): self.set_up_test(100, 64, 100, 50) - self.assertEqual(self.ab_test.check_for_winner(), AbTest.Version.CONTROL) + self.assertEqual(self.ab_test.check_for_winner(), AbTest.VERSION_CONTROL) def test_variantt_wins(self): self.set_up_test(100, 50, 100, 64) - self.assertEqual(self.ab_test.check_for_winner(), AbTest.Version.VARIANT) + self.assertEqual(self.ab_test.check_for_winner(), AbTest.VERSION_VARIANT) def test_close_leaning_control(self): self.set_up_test(100, 62, 100, 50) @@ -122,7 +122,7 @@ def test_confidence_improves_with_more_participants(self): # we can be more confident with a slight difference if there are more paricipants self.set_up_test(1000, 550, 1000, 500) - self.assertEqual(self.ab_test.check_for_winner(), AbTest.Version.CONTROL) + self.assertEqual(self.ab_test.check_for_winner(), AbTest.VERSION_CONTROL) class TestAutoCancelOnUnpublish(TestCase): @@ -139,55 +139,55 @@ def setUp(self): ) def test_unpublish_draft(self): - self.ab_test.status = AbTest.Status.DRAFT + self.ab_test.status = AbTest.STATUS_DRAFT self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) def test_unpublish_running(self): - self.ab_test.status = AbTest.Status.RUNNING + self.ab_test.status = AbTest.STATUS_RUNNING self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) def test_unpublish_paused(self): - self.ab_test.status = AbTest.Status.PAUSED + self.ab_test.status = AbTest.STATUS_PAUSED self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) def test_unpublish_finished(self): - self.ab_test.status = AbTest.Status.FINISHED + self.ab_test.status = AbTest.STATUS_FINISHED self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.COMPLETED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_COMPLETED) def test_unpublish_completed(self): - self.ab_test.status = AbTest.Status.COMPLETED + self.ab_test.status = AbTest.STATUS_COMPLETED self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.COMPLETED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_COMPLETED) def test_unpublish_cancelled(self): - self.ab_test.status = AbTest.Status.CANCELLED + self.ab_test.status = AbTest.STATUS_CANCELLED self.ab_test.save() self.home_page.unpublish() self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) diff --git a/wagtail_ab_testing/test/tests/test_add_abtest.py b/wagtail_ab_testing/test/tests/test_add_abtest.py index ac34bf4..a465a4b 100644 --- a/wagtail_ab_testing/test/tests/test_add_abtest.py +++ b/wagtail_ab_testing/test/tests/test_add_abtest.py @@ -71,37 +71,37 @@ def _create_abtest(self, status): ) def test_with_existing_draft_abtest(self): - self._create_abtest(AbTest.Status.DRAFT) + self._create_abtest(AbTest.STATUS_DRAFT) response = self.get(self.page.id) self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) def test_with_existing_running_abtest(self): - self._create_abtest(AbTest.Status.RUNNING) + self._create_abtest(AbTest.STATUS_RUNNING) response = self.get(self.page.id) self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) def test_with_existing_paused_abtest(self): - self._create_abtest(AbTest.Status.PAUSED) + self._create_abtest(AbTest.STATUS_PAUSED) response = self.get(self.page.id) self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) def test_with_existing_cancelled_abtest(self): - self._create_abtest(AbTest.Status.CANCELLED) + self._create_abtest(AbTest.STATUS_CANCELLED) response = self.get(self.page.id) self.assertEqual(response.status_code, 200) def test_with_existing_finished_abtest(self): - self._create_abtest(AbTest.Status.FINISHED) + self._create_abtest(AbTest.STATUS_FINISHED) response = self.get(self.page.id) self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) def test_with_existing_completed_abtest(self): - self._create_abtest(AbTest.Status.COMPLETED) + self._create_abtest(AbTest.STATUS_COMPLETED) response = self.get(self.page.id) self.assertEqual(response.status_code, 200) @@ -183,7 +183,7 @@ def test_post_add_form(self): self.assertIsNone(ab_test.goal_page) self.assertEqual(ab_test.sample_size, 100) self.assertEqual(ab_test.created_by, self.user) - self.assertEqual(ab_test.status, AbTest.Status.DRAFT) + self.assertEqual(ab_test.status, AbTest.STATUS_DRAFT) def test_post_add_form_start(self): response = self.client.post(reverse('wagtail_ab_testing:add_ab_test_form', args=[self.page.id]), { @@ -197,7 +197,7 @@ def test_post_add_form_start(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) ab_test = AbTest.objects.get() - self.assertEqual(ab_test.status, AbTest.Status.RUNNING) + self.assertEqual(ab_test.status, AbTest.STATUS_RUNNING) def test_post_add_form_start_without_publish_permission(self): self.moderators_group.page_permissions.filter(permission_type='publish').delete() @@ -213,4 +213,4 @@ def test_post_add_form_start_without_publish_permission(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) ab_test = AbTest.objects.get() - self.assertEqual(ab_test.status, AbTest.Status.DRAFT) + self.assertEqual(ab_test.status, AbTest.STATUS_DRAFT) diff --git a/wagtail_ab_testing/test/tests/test_compare_draft.py b/wagtail_ab_testing/test/tests/test_compare_draft.py index 7950dfb..92113bc 100644 --- a/wagtail_ab_testing/test/tests/test_compare_draft.py +++ b/wagtail_ab_testing/test/tests/test_compare_draft.py @@ -20,7 +20,7 @@ def setUp(self): page=self.page, name="Test", variant_revision=self.page.get_latest_revision(), - status=AbTest.Status.RUNNING, + status=AbTest.STATUS_RUNNING, sample_size=100, ) diff --git a/wagtail_ab_testing/test/tests/test_progress.py b/wagtail_ab_testing/test/tests/test_progress.py index c3111c7..8931e58 100644 --- a/wagtail_ab_testing/test/tests/test_progress.py +++ b/wagtail_ab_testing/test/tests/test_progress.py @@ -34,7 +34,7 @@ def setUp(self): page=self.page, name="Test", variant_revision=revision, - status=AbTest.Status.RUNNING, + status=AbTest.STATUS_RUNNING, sample_size=100, ) @@ -45,7 +45,7 @@ def test_get_progress(self): self.assertTemplateUsed(response, "wagtail_ab_testing/progress.html") def test_post_start(self): - self.ab_test.status = AbTest.Status.DRAFT + self.ab_test.status = AbTest.STATUS_DRAFT self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -56,7 +56,7 @@ def test_post_start(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.RUNNING) + self.assertEqual(self.ab_test.status, AbTest.STATUS_RUNNING) def test_post_pause(self): response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -67,10 +67,10 @@ def test_post_pause(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.PAUSED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_PAUSED) def test_post_restart(self): - self.ab_test.status = AbTest.Status.PAUSED + self.ab_test.status = AbTest.STATUS_PAUSED self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -81,7 +81,7 @@ def test_post_restart(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.RUNNING) + self.assertEqual(self.ab_test.status, AbTest.STATUS_RUNNING) def test_post_end(self): response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -92,12 +92,12 @@ def test_post_end(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.CANCELLED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_CANCELLED) def test_post_start_without_publish_permission(self): self.moderators_group.page_permissions.filter(permission_type='publish').delete() - self.ab_test.status = AbTest.Status.DRAFT + self.ab_test.status = AbTest.STATUS_DRAFT self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -108,7 +108,7 @@ def test_post_start_without_publish_permission(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.DRAFT) + self.assertEqual(self.ab_test.status, AbTest.STATUS_DRAFT) def test_post_pause_without_publish_permission(self): self.moderators_group.page_permissions.filter(permission_type='publish').delete() @@ -121,12 +121,12 @@ def test_post_pause_without_publish_permission(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.RUNNING) + self.assertEqual(self.ab_test.status, AbTest.STATUS_RUNNING) def test_post_restart_without_publish_permission(self): self.moderators_group.page_permissions.filter(permission_type='publish').delete() - self.ab_test.status = AbTest.Status.PAUSED + self.ab_test.status = AbTest.STATUS_PAUSED self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -136,7 +136,7 @@ def test_post_restart_without_publish_permission(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.PAUSED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_PAUSED) def test_post_end_without_publish_permission(self): self.moderators_group.page_permissions.filter(permission_type='publish').delete() @@ -148,10 +148,10 @@ def test_post_end_without_publish_permission(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.RUNNING) + self.assertEqual(self.ab_test.status, AbTest.STATUS_RUNNING) def test_post_end_when_finished(self): - self.ab_test.status = AbTest.Status.FINISHED + self.ab_test.status = AbTest.STATUS_FINISHED self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -162,12 +162,12 @@ def test_post_end_when_finished(self): self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.COMPLETED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_COMPLETED) self.assertEqual(self.ab_test.page.title, "Test") self.assertTrue(self.ab_test.page.has_unpublished_changes) def test_post_select_control(self): - self.ab_test.status = AbTest.Status.FINISHED + self.ab_test.status = AbTest.STATUS_FINISHED self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -177,12 +177,12 @@ def test_post_select_control(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.COMPLETED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_COMPLETED) self.assertEqual(self.ab_test.page.title, "Test") self.assertFalse(self.ab_test.page.has_unpublished_changes) def test_post_select_variant(self): - self.ab_test.status = AbTest.Status.FINISHED + self.ab_test.status = AbTest.STATUS_FINISHED self.ab_test.save() response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.page.id]), { @@ -192,6 +192,6 @@ def test_post_select_variant(self): self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.page.id])) self.ab_test.refresh_from_db() - self.assertEqual(self.ab_test.status, AbTest.Status.COMPLETED) + self.assertEqual(self.ab_test.status, AbTest.STATUS_COMPLETED) self.assertEqual(self.ab_test.page.title, "Test updated") self.assertFalse(self.ab_test.page.has_unpublished_changes) diff --git a/wagtail_ab_testing/test/tests/test_report.py b/wagtail_ab_testing/test/tests/test_report.py index 0d899ad..dcd6f81 100644 --- a/wagtail_ab_testing/test/tests/test_report.py +++ b/wagtail_ab_testing/test/tests/test_report.py @@ -30,7 +30,7 @@ def setUp(self): page=self.page, name="Test", variant_revision=self.page.get_latest_revision(), - status=AbTest.Status.RUNNING, + status=AbTest.STATUS_RUNNING, sample_size=100, ) diff --git a/wagtail_ab_testing/test/tests/test_results.py b/wagtail_ab_testing/test/tests/test_results.py index 013f197..950513d 100644 --- a/wagtail_ab_testing/test/tests/test_results.py +++ b/wagtail_ab_testing/test/tests/test_results.py @@ -30,7 +30,7 @@ def setUp(self): page=self.page, name="Test", variant_revision=self.page.get_latest_revision(), - status=AbTest.Status.COMPLETED, + status=AbTest.STATUS_COMPLETED, sample_size=100, ) diff --git a/wagtail_ab_testing/test/tests/test_serve.py b/wagtail_ab_testing/test/tests/test_serve.py index 9f11517..e2dcfff 100644 --- a/wagtail_ab_testing/test/tests/test_serve.py +++ b/wagtail_ab_testing/test/tests/test_serve.py @@ -20,7 +20,7 @@ def setUp(self): def test_serves_control(self): # Add a participant for variant # This will make the new participant use control to balance the numbers - self.ab_test.add_participant(AbTest.Version.VARIANT) + self.ab_test.add_participant(AbTest.VERSION_VARIANT) response = self.client.get('/') self.assertEqual(response.status_code, 200) @@ -28,12 +28,12 @@ def test_serves_control(self): self.assertContains(response, "Welcome to your new Wagtail site!") self.assertNotContains(response, "Changed title") - self.assertEqual(self.client.session[f'wagtail-ab-testing_{self.ab_test.id}_version'], AbTest.Version.CONTROL) + self.assertEqual(self.client.session[f'wagtail-ab-testing_{self.ab_test.id}_version'], AbTest.VERSION_CONTROL) def test_serves_variant(self): # Add a participant for control # This will make the new participant use variant to balance the numbers - self.ab_test.add_participant(AbTest.Version.CONTROL) + self.ab_test.add_participant(AbTest.VERSION_CONTROL) response = self.client.get('/') self.assertEqual(response.status_code, 200) @@ -41,12 +41,12 @@ def test_serves_variant(self): self.assertNotContains(response, "Welcome to your new Wagtail site!") self.assertContains(response, "Changed title") - self.assertEqual(self.client.session[f'wagtail-ab-testing_{self.ab_test.id}_version'], AbTest.Version.VARIANT) + self.assertEqual(self.client.session[f'wagtail-ab-testing_{self.ab_test.id}_version'], AbTest.VERSION_VARIANT) def test_doesnt_track_bots(self): # Add a participant for control # This will make it serve the variant if it does incorrectly decide to track the user - self.ab_test.add_participant(AbTest.Version.CONTROL) + self.ab_test.add_participant(AbTest.VERSION_CONTROL) response = self.client.get( '/', @@ -62,7 +62,7 @@ def test_doesnt_track_bots(self): def test_doesnt_track_dnt_users(self): # Add a participant for control # This will make it serve the variant if it does incorrectly decide to track the user - self.ab_test.add_participant(AbTest.Version.CONTROL) + self.ab_test.add_participant(AbTest.VERSION_CONTROL) response = self.client.get('/', HTTP_DNT='1') self.assertEqual(response.status_code, 200) diff --git a/wagtail_ab_testing/views.py b/wagtail_ab_testing/views.py index 49f606e..a3edc25 100644 --- a/wagtail_ab_testing/views.py +++ b/wagtail_ab_testing/views.py @@ -149,7 +149,7 @@ def is_shown(self, request, context): if not context['user_page_permissions'].for_page(context['ab_test'].page).can_publish(): return False - return context['ab_test'].status == AbTest.Status.DRAFT + return context['ab_test'].status == AbTest.STATUS_DRAFT class RestartAbTestMenuItem(ActionMenuItem): @@ -160,7 +160,7 @@ def is_shown(self, request, context): if not context['user_page_permissions'].for_page(context['ab_test'].page).can_publish(): return False - return context['ab_test'].status == AbTest.Status.PAUSED + return context['ab_test'].status == AbTest.STATUS_PAUSED class EndAbTestMenuItem(ActionMenuItem): @@ -171,7 +171,7 @@ def is_shown(self, request, context): if not context['user_page_permissions'].for_page(context['ab_test'].page).can_publish(): return False - return context['ab_test'].status in [AbTest.Status.DRAFT, AbTest.Status.RUNNING, AbTest.Status.PAUSED] + return context['ab_test'].status in [AbTest.STATUS_DRAFT, AbTest.STATUS_RUNNING, AbTest.STATUS_PAUSED] class PauseAbTestMenuItem(ActionMenuItem): @@ -182,7 +182,7 @@ def is_shown(self, request, context): if not context['user_page_permissions'].for_page(context['ab_test'].page).can_publish(): return False - return context['ab_test'].status == AbTest.Status.RUNNING + return context['ab_test'].status == AbTest.STATUS_RUNNING class AbTestActionMenu: @@ -232,10 +232,10 @@ def media(self): def get_progress_and_results_common_context(request, page, ab_test): # Fetch stats from database stats = ab_test.hourly_logs.aggregate( - control_participants=Sum('participants', filter=Q(version=AbTest.Version.CONTROL)), - control_conversions=Sum('conversions', filter=Q(version=AbTest.Version.CONTROL)), - variant_participants=Sum('participants', filter=Q(version=AbTest.Version.VARIANT)), - variant_conversions=Sum('conversions', filter=Q(version=AbTest.Version.VARIANT)), + control_participants=Sum('participants', filter=Q(version=AbTest.VERSION_CONTROL)), + control_conversions=Sum('conversions', filter=Q(version=AbTest.VERSION_CONTROL)), + variant_participants=Sum('participants', filter=Q(version=AbTest.VERSION_VARIANT)), + variant_conversions=Sum('conversions', filter=Q(version=AbTest.VERSION_VARIANT)), ) control_participants = stats['control_participants'] or 0 control_conversions = stats['control_conversions'] or 0 @@ -245,7 +245,7 @@ def get_progress_and_results_common_context(request, page, ab_test): current_sample_size = control_participants + variant_participants estimated_completion_date = None - if ab_test.status == AbTest.Status.RUNNING and current_sample_size: + if ab_test.status == AbTest.STATUS_RUNNING and current_sample_size: running_duration_days = ab_test.total_running_duration().days if running_duration_days > 0: @@ -260,7 +260,7 @@ def get_progress_and_results_common_context(request, page, ab_test): date = None for log in ab_test.hourly_logs.order_by('date', 'hour'): # Accumulate the conversions - if log.version == AbTest.Version.CONTROL: + if log.version == AbTest.VERSION_CONTROL: control += log.conversions else: variant += log.conversions @@ -291,9 +291,9 @@ def get_progress_and_results_common_context(request, page, ab_test): 'variant_conversions': variant_conversions, 'variant_participants': variant_participants, 'variant_conversions_percent': int(variant_conversions / variant_participants * 100) if variant_participants else 0, - 'control_is_winner': ab_test.winning_version == AbTest.Version.CONTROL, - 'variant_is_winner': ab_test.winning_version == AbTest.Version.VARIANT, - 'unclear_winner': ab_test.status in [AbTest.Status.FINISHED, ab_test.Status.COMPLETED] and ab_test.winning_version is None, + 'control_is_winner': ab_test.winning_version == AbTest.VERSION_CONTROL, + 'variant_is_winner': ab_test.winning_version == AbTest.VERSION_VARIANT, + 'unclear_winner': ab_test.status in [AbTest.STATUS_FINISHED, ab_test.STATUS_COMPLETED] and ab_test.winning_version is None, 'estimated_completion_date': estimated_completion_date, 'chart_data': json.dumps({ 'x': 'x', @@ -313,7 +313,7 @@ def progress(request, page, ab_test): if 'action-start-ab-test' in request.POST or 'action-restart-ab-test' in request.POST: if page_perms.can_publish(): - if ab_test.status in [AbTest.Status.DRAFT, AbTest.Status.PAUSED]: + if ab_test.status in [AbTest.STATUS_DRAFT, AbTest.STATUS_PAUSED]: ab_test.start() messages.success(request, _("The A/B test has been started.")) @@ -324,12 +324,12 @@ def progress(request, page, ab_test): elif 'action-end-ab-test' in request.POST: if page_perms.can_publish(): - if ab_test.status in [AbTest.Status.DRAFT, AbTest.Status.RUNNING, AbTest.Status.PAUSED]: + if ab_test.status in [AbTest.STATUS_DRAFT, AbTest.STATUS_RUNNING, AbTest.STATUS_PAUSED]: ab_test.cancel() messages.success(request, _("The A/B test has been ended.")) - elif ab_test.status == AbTest.Status.FINISHED: - ab_test.complete(AbTest.CompletionAction.DO_NOTHING, user=request.user) + elif ab_test.status == AbTest.STATUS_FINISHED: + ab_test.complete(AbTest.COMPLETION_ACTION_DO_NOTHING, user=request.user) else: messages.error(request, _("The A/B test has already ended.")) else: @@ -337,7 +337,7 @@ def progress(request, page, ab_test): elif 'action-pause-ab-test' in request.POST: if page_perms.can_publish(): - if ab_test.status == AbTest.Status.RUNNING: + if ab_test.status == AbTest.STATUS_RUNNING: ab_test.pause() messages.success(request, _("The A/B test has been paused.")) @@ -347,8 +347,8 @@ def progress(request, page, ab_test): messages.error(request, _("You must have permission to publish in order to pause an A/B test.")) elif 'action-select-control' in request.POST: - if ab_test.status == AbTest.Status.FINISHED: - ab_test.complete(AbTest.CompletionAction.REVERT, user=request.user) + if ab_test.status == AbTest.STATUS_FINISHED: + ab_test.complete(AbTest.COMPLETION_ACTION_REVERT, user=request.user) messages.success(request, _("The page has been reverted back to the control version.")) @@ -356,9 +356,9 @@ def progress(request, page, ab_test): messages.error(request, _("The A/B test cannot be paused because it is not running.")) elif 'action-select-variant' in request.POST: - if ab_test.status == AbTest.Status.FINISHED: + if ab_test.status == AbTest.STATUS_FINISHED: # TODO Permission check? - ab_test.complete(AbTest.CompletionAction.PUBLISH, user=request.user) + ab_test.complete(AbTest.COMPLETION_ACTION_PUBLISH, user=request.user) messages.success(request, _("The variant version has been published.")) diff --git a/wagtail_ab_testing/wagtail_hooks.py b/wagtail_ab_testing/wagtail_hooks.py index e20887f..7c86dab 100644 --- a/wagtail_ab_testing/wagtail_hooks.py +++ b/wagtail_ab_testing/wagtail_hooks.py @@ -111,7 +111,7 @@ def check_for_running_ab_test(request, page): def before_serve_page(page, request, serve_args, serve_kwargs): # Check if there are any running tests on the page try: - test = AbTest.objects.get(page=page, status=AbTest.Status.DRAFT) + test = AbTest.objects.get(page=page, status=AbTest.STATUS_DRAFT) except AbTest.DoesNotExist: return @@ -124,7 +124,7 @@ def before_serve_page(page, request, serve_args, serve_kwargs): request.session[f'wagtail-ab-testing_{test.id}_version'] = test.add_participant() # If the user is visiting the variant version, serve that from the revision - if request.session[f'wagtail-ab-testing_{test.id}_version'] == AbTest.Version.VARIANT: + if request.session[f'wagtail-ab-testing_{test.id}_version'] == AbTest.VERSION_VARIANT: return test.variant_revision.as_page_object().serve(request, *serve_args, **serve_kwargs)