diff --git a/app/apps.py b/app/apps.py index a8afd70a..b3c7396d 100644 --- a/app/apps.py +++ b/app/apps.py @@ -17,6 +17,10 @@ def ready(self): self.configure_posthog() self.configure_shopify() + from .slippers_autoload_components import register + + register() + def configure_shopify(self): stripe.api_key = djstripe.settings.djstripe_settings.STRIPE_SECRET_KEY stripe.api_version = "2020-08-27" diff --git a/app/forms.py b/app/forms.py index e84eb238..b38c5c58 100644 --- a/app/forms.py +++ b/app/forms.py @@ -578,7 +578,7 @@ def update_subscription(self, *args, **kwargs): # Create a form with a field for user_id, donation_amount, and on submission add a donation product to the subscription class DonationForm(forms.Form): - user_id = forms.IntegerField(widget=forms.HiddenInput) + user_id = forms.IntegerField(widget=forms.HiddenInput, required=False) donation_amount = forms.DecimalField( min_value=0, max_value=1000, @@ -714,3 +714,18 @@ def process_request(self, *args, **kwargs): else None, }, ) + + +### V2 flow forms + + +class SelectDeliveriesForm(forms.Form): + delivery_plan_id = forms.CharField(widget=forms.HiddenInput) + + +class SelectSyllabusForm(forms.Form): + syllabus_id = forms.CharField(widget=forms.HiddenInput) + + +class SelectPaymentPlanForm(forms.Form): + payment_plan_id = forms.CharField(widget=forms.HiddenInput) diff --git a/app/management/commands/create_gift_sub.py b/app/management/commands/create_gift_sub.py index 9c7c15e3..afb2c86c 100644 --- a/app/management/commands/create_gift_sub.py +++ b/app/management/commands/create_gift_sub.py @@ -6,7 +6,7 @@ from django.core.management.base import BaseCommand from django.urls import reverse from djmoney.money import Money -from wagtail.core.models import Page +from wagtail.models import Page from app.models import MembershipPlanPage, MembershipPlanPrice, User from app.utils.python import uid diff --git a/app/migrations/0019_alter_homepage_layout.py b/app/migrations/0019_alter_homepage_layout.py index 68c86f62..64eb586f 100644 --- a/app/migrations/0019_alter_homepage_layout.py +++ b/app/migrations/0019_alter_homepage_layout.py @@ -49,7 +49,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0027_alter_homepage_layout.py b/app/migrations/0027_alter_homepage_layout.py index 45ebac1d..e6c135e5 100644 --- a/app/migrations/0027_alter_homepage_layout.py +++ b/app/migrations/0027_alter_homepage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0031_remove_informationpage_body_informationpage_layout_and_more.py b/app/migrations/0031_remove_informationpage_body_informationpage_layout_and_more.py index b2fc4198..700d4098 100644 --- a/app/migrations/0031_remove_informationpage_body_informationpage_layout_and_more.py +++ b/app/migrations/0031_remove_informationpage_body_informationpage_layout_and_more.py @@ -49,7 +49,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -977,7 +977,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0033_alter_homepage_layout_alter_informationpage_layout.py b/app/migrations/0033_alter_homepage_layout_alter_informationpage_layout.py index a70c73b8..f88d922f 100644 --- a/app/migrations/0033_alter_homepage_layout_alter_informationpage_layout.py +++ b/app/migrations/0033_alter_homepage_layout_alter_informationpage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1242,7 +1242,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0034_alter_homepage_layout_alter_informationpage_layout.py b/app/migrations/0034_alter_homepage_layout_alter_informationpage_layout.py index 228c1d7e..aef13b43 100644 --- a/app/migrations/0034_alter_homepage_layout_alter_informationpage_layout.py +++ b/app/migrations/0034_alter_homepage_layout_alter_informationpage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -367,7 +367,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0040_remove_bookindexpage_body_bookindexpage_layout.py b/app/migrations/0040_remove_bookindexpage_body_bookindexpage_layout.py index 70f0e720..136813d3 100644 --- a/app/migrations/0040_remove_bookindexpage_body_bookindexpage_layout.py +++ b/app/migrations/0040_remove_bookindexpage_body_bookindexpage_layout.py @@ -48,7 +48,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0041_user_gdpr_email_consent_alter_bookindexpage_layout_and_more.py b/app/migrations/0041_user_gdpr_email_consent_alter_bookindexpage_layout_and_more.py index 55555966..ef0a32b7 100644 --- a/app/migrations/0041_user_gdpr_email_consent_alter_bookindexpage_layout_and_more.py +++ b/app/migrations/0041_user_gdpr_email_consent_alter_bookindexpage_layout_and_more.py @@ -54,7 +54,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -896,7 +896,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1738,7 +1738,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0042_alter_bookindexpage_layout_alter_homepage_layout_and_more.py b/app/migrations/0042_alter_bookindexpage_layout_alter_homepage_layout_and_more.py index cee8851a..6b820de6 100644 --- a/app/migrations/0042_alter_bookindexpage_layout_alter_homepage_layout_and_more.py +++ b/app/migrations/0042_alter_bookindexpage_layout_alter_homepage_layout_and_more.py @@ -46,7 +46,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -888,7 +888,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1730,7 +1730,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2583,7 +2583,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0044_membershipplanpage_layout.py b/app/migrations/0044_membershipplanpage_layout.py index ad45cd9e..56ae8bcf 100644 --- a/app/migrations/0044_membershipplanpage_layout.py +++ b/app/migrations/0044_membershipplanpage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0045_alter_membershipplanpage_layout.py b/app/migrations/0045_alter_membershipplanpage_layout.py index 8c9b4391..12f68fac 100644 --- a/app/migrations/0045_alter_membershipplanpage_layout.py +++ b/app/migrations/0045_alter_membershipplanpage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0047_bookpage_layout_alter_membershipplanpage_layout.py b/app/migrations/0047_bookpage_layout_alter_membershipplanpage_layout.py index 4cc447fe..7a96875c 100644 --- a/app/migrations/0047_bookpage_layout_alter_membershipplanpage_layout.py +++ b/app/migrations/0047_bookpage_layout_alter_membershipplanpage_layout.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -886,7 +886,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0048_alter_blogpage_body_alter_bookindexpage_layout_and_more.py b/app/migrations/0048_alter_blogpage_body_alter_bookindexpage_layout_and_more.py index 1a0a7da2..35580d4b 100644 --- a/app/migrations/0048_alter_blogpage_body_alter_bookindexpage_layout_and_more.py +++ b/app/migrations/0048_alter_blogpage_body_alter_bookindexpage_layout_and_more.py @@ -65,7 +65,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -915,7 +915,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1772,7 +1772,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2622,7 +2622,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3472,7 +3472,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -4322,7 +4322,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0050_alter_bookpage_options_alter_bookindexpage_layout_and_more.py b/app/migrations/0050_alter_bookpage_options_alter_bookindexpage_layout_and_more.py index 4e03282e..352d4561 100644 --- a/app/migrations/0050_alter_bookpage_options_alter_bookindexpage_layout_and_more.py +++ b/app/migrations/0050_alter_bookpage_options_alter_bookindexpage_layout_and_more.py @@ -48,7 +48,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -908,7 +908,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1768,7 +1768,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2628,7 +2628,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3488,7 +3488,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -4348,7 +4348,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -5222,7 +5222,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0054_circleevent_alter_bookindexpage_layout_and_more.py b/app/migrations/0054_circleevent_alter_bookindexpage_layout_and_more.py index 7dee0fac..9d72ecb5 100644 --- a/app/migrations/0054_circleevent_alter_bookindexpage_layout_and_more.py +++ b/app/migrations/0054_circleevent_alter_bookindexpage_layout_and_more.py @@ -94,7 +94,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -982,7 +982,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1870,7 +1870,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2758,7 +2758,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3646,7 +3646,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -4551,7 +4551,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -5439,7 +5439,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0059_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py b/app/migrations/0059_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py index 41661da7..89deda38 100644 --- a/app/migrations/0059_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py +++ b/app/migrations/0059_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1029,7 +1029,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2014,7 +2014,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2999,7 +2999,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3984,7 +3984,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -4986,7 +4986,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -5971,7 +5971,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -7779,7 +7779,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0063_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py b/app/migrations/0063_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py index d939c544..2a968b24 100644 --- a/app/migrations/0063_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py +++ b/app/migrations/0063_alter_bookindexpage_layout_alter_bookpage_layout_and_more.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -1030,7 +1030,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -2016,7 +2016,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3002,7 +3002,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -3988,7 +3988,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -4991,7 +4991,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -5977,7 +5977,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] @@ -6966,7 +6966,7 @@ class Migration(migrations.Migration): ( "plans", wagtail.blocks.ListBlock( - app.models.wagtail.PlanBlock + app.models.blocks.PlanBlock ), ), ] diff --git a/app/migrations/0069_membershipplanpage_benefits_and_more.py b/app/migrations/0069_membershipplanpage_benefits_and_more.py new file mode 100644 index 00000000..2bdf9950 --- /dev/null +++ b/app/migrations/0069_membershipplanpage_benefits_and_more.py @@ -0,0 +1,115 @@ +# Generated by Django 4.0.8 on 2023-11-22 14:58 + +import django.contrib.postgres.fields +import django.db.models.deletion +import modelcluster.fields +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0069_log_entry_jsonfield"), + ("app", "0068_bookpage_authors_bookpage_forward_by_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="membershipplanpage", + name="benefits", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(blank=True, max_length=100), + default=list, + help_text="List of pithy beneficial features of this plan", + size=None, + ), + preserve_default=False, + ), + migrations.AddField( + model_name="membershipplanpage", + name="display_in_quiz_flow", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="membershipplanprice", + name="benefits", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(blank=True, max_length=100), + default=list, + help_text="List of pithy beneficial features of this plan", + size=None, + ), + preserve_default=False, + ), + migrations.AddField( + model_name="membershipplanprice", + name="title", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="membershipplanprice", + name="products", + field=modelcluster.fields.ParentalManyToManyField( + blank=True, + help_text="(Not used in the new flow). The stripe product that the user will be subscribed to. If multiple products are set here, then the user will be asked to pick which one they want, e.g. Classic or Contemporary books.", + to="app.lbcproduct", + ), + ), + migrations.CreateModel( + name="SyllabusPage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ("description", wagtail.fields.RichTextField(blank=True, null=True)), + ( + "book_types", + models.CharField( + choices=[ + ("classic", "classic"), + ("contemporary", "contemporary"), + ("all-books", "all-books"), + ], + default="all-books", + help_text="Used to display relevant books", + max_length=100, + ), + ), + ( + "stripe_product", + models.ForeignKey( + help_text="The stripe product that the user will be subscribed to.", + on_delete=django.db.models.deletion.CASCADE, + related_name="syllabi", + to="app.lbcproduct", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page", models.Model), + ), + migrations.AddField( + model_name="membershipplanpage", + name="syllabi", + field=modelcluster.fields.ParentalManyToManyField( + help_text="The syllabi available for this plan", + related_name="plans", + to="app.syllabuspage", + ), + ), + ] diff --git a/app/migrations/0070_alter_membershipplanpage_benefits_and_more.py b/app/migrations/0070_alter_membershipplanpage_benefits_and_more.py new file mode 100644 index 00000000..b163ed91 --- /dev/null +++ b/app/migrations/0070_alter_membershipplanpage_benefits_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0.8 on 2023-11-22 15:25 + +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("app", "0069_membershipplanpage_benefits_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="membershipplanpage", + name="benefits", + field=wagtail.fields.RichTextField( + blank=True, + help_text="List of pithy beneficial features of this plan", + null=True, + ), + ), + migrations.AlterField( + model_name="membershipplanprice", + name="benefits", + field=wagtail.fields.RichTextField( + blank=True, + help_text="List of pithy beneficial features of this plan", + null=True, + ), + ), + migrations.AlterField( + model_name="syllabuspage", + name="book_types", + field=models.CharField( + blank=True, + choices=[ + ("classic", "classic"), + ("contemporary", "contemporary"), + ("all-books", "all-books"), + ], + default="all-books", + help_text="Used to display relevant books", + max_length=100, + ), + ), + ] diff --git a/app/migrations/0071_syllabuspage_layout_and_more.py b/app/migrations/0071_syllabuspage_layout_and_more.py new file mode 100644 index 00000000..369b2b11 --- /dev/null +++ b/app/migrations/0071_syllabuspage_layout_and_more.py @@ -0,0 +1,2023 @@ +# Generated by Django 4.0.8 on 2023-11-24 17:16 + +import modelcluster.fields +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations, models + +import app.models.blocks +import app.models.wagtail + + +class Migration(migrations.Migration): + + dependencies = [ + ("app", "0070_alter_membershipplanpage_benefits_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="syllabuspage", + name="layout", + field=wagtail.fields.StreamField( + [ + ( + "membership_options", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + default="Choose your plan", + form_classname="full title", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + default="

Your subscription will begin with the most recently published book in your chosen collection.

", + required=False, + ), + ), + ( + "plans", + wagtail.blocks.ListBlock( + app.models.blocks.PlanBlock + ), + ), + ] + ), + ), + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "featured_book", + wagtail.blocks.StructBlock( + [ + ( + "book", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.BookPage"], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + help_text="This will replace the book's default description. You can use this to provide a more contextualised description of the book", + required=False, + ), + ), + ] + ), + ), + ( + "book_selection", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "books", + wagtail.blocks.ListBlock( + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.BookPage"], + ) + ), + ), + ] + ), + ), + ( + "recently_published_books", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "max_books", + wagtail.blocks.IntegerBlock( + default=4, + help_text="How many books should show up?", + ), + ), + ( + "type", + wagtail.blocks.ChoiceBlock( + choices=[ + ("classic", "classic"), + ("contemporary", "contemporary"), + ("all-books", "all-books"), + ] + ), + ), + ] + ), + ), + ( + "featured_product", + wagtail.blocks.StructBlock( + [ + ( + "product", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.MerchandisePage"], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + help_text="This will replace the product's default description. You can use this to provide a more contextualised description of the product", + required=False, + ), + ), + ] + ), + ), + ( + "product_selection", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "products", + wagtail.blocks.ListBlock( + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.MerchandisePage"], + ) + ), + ), + ] + ), + ), + ( + "full_product_list", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "max_products", + wagtail.blocks.IntegerBlock( + default=10, + help_text="How many products should show up?", + ), + ), + ] + ), + ), + ( + "hero_text", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", max_length=250 + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, required=False + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("sm", "small"), + ("md", "medium"), + ("lg", "large"), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ("heading", wagtail.blocks.CharBlock(form_classname="full title")), + ( + "richtext", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.RichTextBlock( + features=[ + "h1", + "h2", + "h3", + "h4", + "h5", + "bold", + "italic", + "link", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + "embed", + "blockquote", + ], + form_classname="full", + ), + ), + ( + "alignment", + wagtail.blocks.ChoiceBlock( + choices=[ + ("left", "left"), + ("center", "center"), + ("right", "right"), + ], + help_text="Doesn't apply when used inside a column.", + ), + ), + ] + ), + ), + ( + "list_of_heading_image_text", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "items", + wagtail.blocks.ListBlock( + app.models.blocks.ListItemBlock + ), + ), + ] + ), + ), + ( + "single_column", + wagtail.blocks.StructBlock( + [ + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "content", + wagtail.blocks.StreamBlock( + [ + ( + "hero_text", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + max_length=250, + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "sm", + "small", + ), + ( + "md", + "medium", + ), + ( + "lg", + "large", + ), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "title_image_caption", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + form_classname="full title", + max_length=250, + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + required=False + ), + ), + ( + "image_css", + wagtail.blocks.CharBlock( + max_length=500, + required=False, + ), + ), + ( + "caption", + wagtail.blocks.RichTextBlock( + max_length=500 + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "sm", + "small", + ), + ( + "md", + "medium", + ), + ( + "lg", + "large", + ), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock(), + ), + ( + "single_book", + wagtail.blocks.StructBlock( + [ + ( + "book", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=[ + "app.BookPage" + ], + ), + ) + ] + ), + ), + ( + "membership_plan", + wagtail.blocks.StructBlock( + [ + ( + "plan", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=[ + "app.MembershipPlanPage" + ], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ] + ), + ), + ( + "richtext", + wagtail.blocks.RichTextBlock( + features=[ + "h1", + "h2", + "h3", + "h4", + "h5", + "bold", + "italic", + "link", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + "embed", + "blockquote", + ] + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("sm", "small"), + ("md", "medium"), + ("lg", "large"), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "newsletter_signup", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + max_length=150, + required=False, + ), + ) + ] + ), + ), + ], + required=False, + ), + ), + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "alignment", + wagtail.blocks.ChoiceBlock( + choices=[ + ("left", "left"), + ("center", "center"), + ("right", "right"), + ], + help_text="Doesn't apply when used inside a column.", + ), + ), + ] + ), + ), + ( + "columns", + wagtail.blocks.StructBlock( + [ + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "columns", + wagtail.blocks.ListBlock( + app.models.blocks.ColumnBlock, + max_num=5, + min_num=1, + ), + ), + ] + ), + ), + ( + "events_list_and_map", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(required=False)), + ("intro", wagtail.blocks.RichTextBlock(required=False)), + ( + "number_of_events", + wagtail.blocks.IntegerBlock( + default=3, required=True + ), + ), + ] + ), + ), + ( + "events_list_block", + wagtail.blocks.StructBlock( + [ + ( + "number_of_events", + wagtail.blocks.IntegerBlock( + default=3, required=True + ), + ) + ] + ), + ), + ("syllabus_title", wagtail.blocks.StructBlock([])), + ], + blank=True, + default=app.models.wagtail.create_default_layout_syllabus, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="membershipplanpage", + name="benefits", + field=wagtail.fields.RichTextField( + blank=True, + help_text="List of pithy beneficial features of this plan. Bullet points are automatically formatted as ticks.", + null=True, + ), + ), + migrations.AlterField( + model_name="membershipplanpage", + name="display_in_quiz_flow", + field=models.BooleanField( + default=False, verbose_name="Display in v2 signup flow" + ), + ), + migrations.AlterField( + model_name="membershipplanpage", + name="layout", + field=wagtail.fields.StreamField( + [ + ( + "membership_options", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + default="Choose your plan", + form_classname="full title", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + default="

Your subscription will begin with the most recently published book in your chosen collection.

", + required=False, + ), + ), + ( + "plans", + wagtail.blocks.ListBlock( + app.models.blocks.PlanBlock + ), + ), + ] + ), + ), + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "featured_book", + wagtail.blocks.StructBlock( + [ + ( + "book", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.BookPage"], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + help_text="This will replace the book's default description. You can use this to provide a more contextualised description of the book", + required=False, + ), + ), + ] + ), + ), + ( + "book_selection", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "books", + wagtail.blocks.ListBlock( + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.BookPage"], + ) + ), + ), + ] + ), + ), + ( + "recently_published_books", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "max_books", + wagtail.blocks.IntegerBlock( + default=4, + help_text="How many books should show up?", + ), + ), + ( + "type", + wagtail.blocks.ChoiceBlock( + choices=[ + ("classic", "classic"), + ("contemporary", "contemporary"), + ("all-books", "all-books"), + ] + ), + ), + ] + ), + ), + ( + "featured_product", + wagtail.blocks.StructBlock( + [ + ( + "product", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.MerchandisePage"], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ( + "description", + wagtail.blocks.RichTextBlock( + help_text="This will replace the product's default description. You can use this to provide a more contextualised description of the product", + required=False, + ), + ), + ] + ), + ), + ( + "product_selection", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "products", + wagtail.blocks.ListBlock( + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=["app.MerchandisePage"], + ) + ), + ), + ] + ), + ), + ( + "full_product_list", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "max_products", + wagtail.blocks.IntegerBlock( + default=10, + help_text="How many products should show up?", + ), + ), + ] + ), + ), + ( + "hero_text", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", max_length=250 + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, required=False + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("sm", "small"), + ("md", "medium"), + ("lg", "large"), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ("heading", wagtail.blocks.CharBlock(form_classname="full title")), + ( + "richtext", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.RichTextBlock( + features=[ + "h1", + "h2", + "h3", + "h4", + "h5", + "bold", + "italic", + "link", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + "embed", + "blockquote", + ], + form_classname="full", + ), + ), + ( + "alignment", + wagtail.blocks.ChoiceBlock( + choices=[ + ("left", "left"), + ("center", "center"), + ("right", "right"), + ], + help_text="Doesn't apply when used inside a column.", + ), + ), + ] + ), + ), + ( + "list_of_heading_image_text", + wagtail.blocks.StructBlock( + [ + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "items", + wagtail.blocks.ListBlock( + app.models.blocks.ListItemBlock + ), + ), + ] + ), + ), + ( + "single_column", + wagtail.blocks.StructBlock( + [ + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "content", + wagtail.blocks.StreamBlock( + [ + ( + "hero_text", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + max_length=250, + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "sm", + "small", + ), + ( + "md", + "medium", + ), + ( + "lg", + "large", + ), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "title_image_caption", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + form_classname="full title", + max_length=250, + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + required=False + ), + ), + ( + "image_css", + wagtail.blocks.CharBlock( + max_length=500, + required=False, + ), + ), + ( + "caption", + wagtail.blocks.RichTextBlock( + max_length=500 + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "sm", + "small", + ), + ( + "md", + "medium", + ), + ( + "lg", + "large", + ), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock(), + ), + ( + "single_book", + wagtail.blocks.StructBlock( + [ + ( + "book", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=[ + "app.BookPage" + ], + ), + ) + ] + ), + ), + ( + "membership_plan", + wagtail.blocks.StructBlock( + [ + ( + "plan", + wagtail.blocks.PageChooserBlock( + can_choose_root=False, + page_type=[ + "app.MembershipPlanPage" + ], + ), + ), + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "bg-black text-white", + "black", + ), + ( + "bg-white", + "white", + ), + ( + "tw-bg-yellow", + "yellow", + ), + ( + "tw-bg-teal", + "teal", + ), + ( + "tw-bg-darkgreen", + "darkgreen", + ), + ( + "tw-bg-lilacgrey", + "lilacgrey", + ), + ( + "tw-bg-coral", + "coral", + ), + ( + "tw-bg-purple", + "purple", + ), + ( + "tw-bg-magenta", + "magenta", + ), + ( + "tw-bg-pink", + "pink", + ), + ( + "tw-bg-lightgreen", + "lightgreen", + ), + ], + required=False, + ), + ), + ( + "promotion_label", + wagtail.blocks.CharBlock( + help_text="Label that highlights this product", + required=False, + ), + ), + ] + ), + ), + ( + "richtext", + wagtail.blocks.RichTextBlock( + features=[ + "h1", + "h2", + "h3", + "h4", + "h5", + "bold", + "italic", + "link", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + "embed", + "blockquote", + ] + ), + ), + ( + "button", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.CharBlock( + max_length=100, + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + help_text="Pick a page or specify a URL", + required=False, + ), + ), + ( + "href", + wagtail.blocks.URLBlock( + help_text="Pick a page or specify a URL", + label="URL", + required=False, + ), + ), + ( + "size", + wagtail.blocks.ChoiceBlock( + choices=[ + ("sm", "small"), + ("md", "medium"), + ("lg", "large"), + ], + required=False, + ), + ), + ( + "style", + wagtail.blocks.ChoiceBlock( + choices=[ + ( + "btn-outline-dark", + "outlined", + ), + ( + "btn-dark text-yellow", + "filled", + ), + ], + required=False, + ), + ), + ] + ), + ), + ( + "newsletter_signup", + wagtail.blocks.StructBlock( + [ + ( + "heading", + wagtail.blocks.CharBlock( + max_length=150, + required=False, + ), + ) + ] + ), + ), + ], + required=False, + ), + ), + ( + "column_width", + wagtail.blocks.ChoiceBlock( + choices=[ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + ), + ), + ( + "alignment", + wagtail.blocks.ChoiceBlock( + choices=[ + ("left", "left"), + ("center", "center"), + ("right", "right"), + ], + help_text="Doesn't apply when used inside a column.", + ), + ), + ] + ), + ), + ( + "columns", + wagtail.blocks.StructBlock( + [ + ( + "background_color", + wagtail.blocks.ChoiceBlock( + choices=[ + ("bg-black text-white", "black"), + ("bg-white", "white"), + ("tw-bg-yellow", "yellow"), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ], + required=False, + ), + ), + ( + "columns", + wagtail.blocks.ListBlock( + app.models.blocks.ColumnBlock, + max_num=5, + min_num=1, + ), + ), + ] + ), + ), + ( + "events_list_and_map", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(required=False)), + ("intro", wagtail.blocks.RichTextBlock(required=False)), + ( + "number_of_events", + wagtail.blocks.IntegerBlock( + default=3, required=True + ), + ), + ] + ), + ), + ( + "events_list_block", + wagtail.blocks.StructBlock( + [ + ( + "number_of_events", + wagtail.blocks.IntegerBlock( + default=3, required=True + ), + ) + ] + ), + ), + ("plan_title", wagtail.blocks.StructBlock([])), + ("plan_pricing", wagtail.blocks.StructBlock([])), + ], + blank=True, + default=app.models.wagtail.create_default_layout_plan, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="membershipplanprice", + name="products", + field=modelcluster.fields.ParentalManyToManyField( + blank=True, + help_text="(For V1-only signup flow.) The stripe product that the user will be subscribed to. If multiple products are set here, then the user will be asked to pick which one they want, e.g. Classic or Contemporary books.", + to="app.lbcproduct", + verbose_name="[V1] stripe products", + ), + ), + ] diff --git a/app/migrations/0072_alter_customimage_file_alter_imagerendition_file_and_more.py b/app/migrations/0072_alter_customimage_file_alter_imagerendition_file_and_more.py new file mode 100644 index 00000000..87ffbbe8 --- /dev/null +++ b/app/migrations/0072_alter_customimage_file_alter_imagerendition_file_and_more.py @@ -0,0 +1,691 @@ +# Generated by Django 4.2 on 2023-11-24 19:54 + +import django.db.models.deletion +import djmoney.models.fields +import wagtail.images.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("app", "0071_syllabuspage_layout_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="customimage", + name="file", + field=wagtail.images.models.WagtailImageField( + height_field="height", + upload_to=wagtail.images.models.get_upload_to, + verbose_name="file", + width_field="width", + ), + ), + migrations.AlterField( + model_name="imagerendition", + name="file", + field=wagtail.images.models.WagtailImageField( + height_field="height", + storage=wagtail.images.models.get_rendition_storage, + upload_to=wagtail.images.models.get_rendition_upload_to, + width_field="width", + ), + ), + migrations.AlterField( + model_name="membershipplanprice", + name="price_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("VED", "Bolívar Soberano"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Rial"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone"), + ("SLE", "Sierra Leonean New Leone"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="GBP", + editable=False, + max_length=3, + ), + ), + migrations.AlterField( + model_name="shippingzone", + name="rate_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("VED", "Bolívar Soberano"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Rial"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone"), + ("SLE", "Sierra Leonean New Leone"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="GBP", + editable=False, + max_length=3, + ), + ), + migrations.AlterField( + model_name="syllabuspage", + name="stripe_product", + field=models.ForeignKey( + help_text="The stripe product that the user will be subscribed to.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="syllabi", + to="app.lbcproduct", + ), + ), + ] diff --git a/app/models/blocks.py b/app/models/blocks.py index 85cfa766..d6ac3061 100644 --- a/app/models/blocks.py +++ b/app/models/blocks.py @@ -1,7 +1,34 @@ -from wagtail import blocks -from wagtail.blocks import RichTextBlock +from wagtail.blocks import CharBlock, ChoiceBlock, IntegerBlock +from wagtail.blocks import ListBlock as WagtailListBlock +from wagtail.blocks import ( + PageChooserBlock, + RichTextBlock, + StreamBlock, + StructBlock, + URLBlock, +) from wagtail.embeds.blocks import EmbedBlock from wagtail.fields import StreamField +from wagtail.images.blocks import ImageChooserBlock + +block_features = [ + "h1", + "h2", + "h3", + "h4", + "h5", + "bold", + "italic", + "link", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + "embed", + "blockquote", +] def ArticleContentStream(block_types=None, **kwargs): @@ -27,3 +54,435 @@ def ArticleContentStream(block_types=None, **kwargs): return StreamField( common_block_types + (block_types or []), use_json_field=True, **kwargs ) + + +book_types = [ + ("classic", "classic"), + ("contemporary", "contemporary"), + ("all-books", "all-books"), +] + + +class PlanTitleBlock(StructBlock): + class Meta: + template = "app/blocks/plan_title_block.html" + + +class PlanPricingBlock(StructBlock): + class Meta: + template = "app/blocks/plan_pricing_block.html" + + +class SyllabusTitleBlock(StructBlock): + class Meta: + template = "app/blocks/syllabus_title_block.html" + + +class BookTypeChoice(ChoiceBlock): + choices = book_types + + class Meta: + default = "all-books" + + +class BackgroundColourChoiceBlock(ChoiceBlock): + choices = [ + ("bg-black text-white", "black"), + ("bg-white", "white"), + # ('primary', 'primary'), + ("tw-bg-yellow", "yellow"), + # ('black', 'black'), + ("tw-bg-teal", "teal"), + ("tw-bg-darkgreen", "darkgreen"), + ("tw-bg-lilacgrey", "lilacgrey"), + ("tw-bg-coral", "coral"), + ("tw-bg-purple", "purple"), + ("tw-bg-magenta", "magenta"), + ("tw-bg-pink", "pink"), + ("tw-bg-lightgreen", "lightgreen"), + ] + + class Meta: + icon = "fa-paint" + + +class AlignmentChoiceBlock(ChoiceBlock): + choices = [ + ("left", "left"), + ("center", "center"), + ("right", "right"), + ] + + class Meta: + icon = "fa-arrows-h" + default = "center" + + +class BootstrapButtonSizeChoiceBlock(ChoiceBlock): + choices = [ + ("sm", "small"), + ("md", "medium"), + ("lg", "large"), + ] + + class Meta: + icon = "fa-arrows-alt" + default = "md" + + +class BootstrapButtonStyleChoiceBlock(ChoiceBlock): + choices = [ + ("btn-outline-dark", "outlined"), + ("btn-dark text-yellow", "filled"), + ] + + class Meta: + default = "btn-outline-dark" + + +class PlanBlock(StructBlock): + plan = PageChooserBlock( + page_type="app.membershipplanpage", + target_model="app.membershipplanpage", + can_choose_root=False, + ) + background_color = BackgroundColourChoiceBlock(required=False) + promotion_label = CharBlock( + required=False, help_text="Label that highlights this product" + ) + + def get_context(self, value, parent_context=None): + context = super().get_context(value, parent_context) + context["request_price"] = value["plan"].get_price_for_request( + parent_context.get("request") + ) + return context + + class Meta: + template = "app/blocks/membership_option_card.html" + icon = "fa-money" + + +class MembershipOptionsBlock(StructBlock): + heading = CharBlock( + required=False, + form_classname="full title", + default="Choose your plan", + ) + description = RichTextBlock( + required=False, + default="

Your subscription will begin with the most recently published book in your chosen collection.

", + ) + plans = WagtailListBlock(PlanBlock) + + class Meta: + template = "app/blocks/membership_options_grid.html" + icon = "fa-users" + + +class ButtonBlock(StructBlock): + text = CharBlock(max_length=100, required=False) + page = PageChooserBlock(required=False, help_text="Pick a page or specify a URL") + href = URLBlock( + required=False, help_text="Pick a page or specify a URL", label="URL" + ) + size = BootstrapButtonSizeChoiceBlock(required=False, default="md") + style = BootstrapButtonStyleChoiceBlock(required=False, default="btn-outline-dark") + + class Meta: + template = "app/blocks/cta.html" + + +class HeroTextBlock(StructBlock): + heading = CharBlock(max_length=250, form_classname="full title") + background_color = BackgroundColourChoiceBlock(required=False) + button = ButtonBlock(required=False) + + class Meta: + template = "app/blocks/hero_block.html" + icon = "fa fa-alphabet" + + +class ListItemBlock(StructBlock): + title = CharBlock(max_length=250, form_classname="full title") + image = ImageChooserBlock(required=False) + image_css = CharBlock(max_length=500, required=False) + caption = RichTextBlock(max_length=500) + background_color = BackgroundColourChoiceBlock(required=False) + button = ButtonBlock(required=False) + + class Meta: + template = "app/blocks/list_item_block.html" + icon = "fa fa-alphabet" + + +class ColumnWidthChoiceBlock(ChoiceBlock): + choices = [ + ("small", "small"), + ("medium", "medium"), + ("large", "large"), + ] + + class Meta: + icon = "fa-arrows-alt" + default = "small" + + +class ListBlock(StructBlock): + column_width = ColumnWidthChoiceBlock() + items = WagtailListBlock(ListItemBlock) + + class Meta: + template = "app/blocks/list_block.html" + icon = "fa fa-alphabet" + + +class FeaturedBookBlock(StructBlock): + book = PageChooserBlock( + page_type="app.bookpage", + target_model="app.bookpage", + can_choose_root=False, + ) + background_color = BackgroundColourChoiceBlock(required=False) + promotion_label = CharBlock( + required=False, help_text="Label that highlights this product" + ) + description = RichTextBlock( + required=False, + help_text="This will replace the book's default description. You can use this to provide a more contextualised description of the book", + ) + + class Meta: + template = "app/blocks/featured_book_block.html" + icon = "fa fa-book" + + +class FeaturedProductBlock(StructBlock): + product = PageChooserBlock( + page_type="app.merchandisepage", + target_model="app.merchandisepage", + can_choose_root=False, + ) + background_color = BackgroundColourChoiceBlock(required=False) + promotion_label = CharBlock( + required=False, help_text="Label that highlights this product" + ) + description = RichTextBlock( + required=False, + help_text="This will replace the product's default description. You can use this to provide a more contextualised description of the product", + ) + + class Meta: + template = "app/blocks/featured_product_block.html" + icon = "fa fa-shopping-cart" + + +class BookGridBlock(StructBlock): + column_width = ColumnWidthChoiceBlock() + + class Meta: + template = "app/blocks/book_grid_block.html" + icon = "fa fa-book" + + +class ProductGridBlock(StructBlock): + column_width = ColumnWidthChoiceBlock() + + class Meta: + template = "app/blocks/product_grid_block.html" + icon = "fa fa-shopping-cart" + + +class SelectedBooksBlock(BookGridBlock): + books = WagtailListBlock( + PageChooserBlock( + page_type="app.bookpage", + target_model="app.bookpage", + can_choose_root=False, + ) + ) + + def get_context(self, value, parent_context=None): + context = super().get_context(value, parent_context) + context["books"] = value["books"] + return context + + +class SelectedProductsBlock(ProductGridBlock): + products = WagtailListBlock( + PageChooserBlock( + page_type="app.merchandisepage", + target_model="app.merchandisepage", + can_choose_root=False, + ) + ) + + def get_context(self, value, parent_context=None): + context = super().get_context(value, parent_context) + context["products"] = list(value["products"]) + return context + + +class RecentlyPublishedBooks(BookGridBlock): + max_books = IntegerBlock(default=4, help_text="How many books should show up?") + type = BookTypeChoice(default="all-books") + + def get_context(self, value, parent_context=None): + from app.models.wagtail import BookPage + + context = super().get_context(value, parent_context) + filters = {} + if value["type"] != None and value["type"] != "all-books": + filters["type"] = value["type"] + context["books"] = ( + BookPage.objects.order_by("-published_date") + .live() + .public() + .filter(published_date__isnull=False, **filters) + .all()[: value["max_books"]] + ) + return context + + +class FullProductList(ProductGridBlock): + max_products = IntegerBlock( + default=10, help_text="How many products should show up?" + ) + + def get_context(self, value, parent_context=None): + from app.models.wagtail import MerchandisePage + + context = super().get_context(value, parent_context) + context["products"] = list( + MerchandisePage.objects.live().public().all()[: value["max_products"]] + ) + return context + + +class YourBooks(BookGridBlock): + class Meta: + template = "app/blocks/your_books_grid_block.html" + icon = "fa fa-book" + + +class SingleBookBlock(StructBlock): + book = PageChooserBlock( + page_type="app.bookpage", + target_model="app.bookpage", + can_choose_root=False, + ) + + class Meta: + template = "app/blocks/single_book_block.html" + icon = "fa fa-book" + + +class NewsletterSignupBlock(StructBlock): + heading = CharBlock(required=False, max_length=150) + + class Meta: + template = "app/blocks/newsletter_signup_block.html" + icon = "fa fa-email" + + +class ArticleText(StructBlock): + text = RichTextBlock(form_classname="full", features=block_features) + alignment = AlignmentChoiceBlock( + help_text="Doesn't apply when used inside a column." + ) + + class Meta: + template = "app/blocks/richtext.html" + + +class ColumnBlock(StructBlock): + stream_blocks = [ + ("hero_text", HeroTextBlock()), + ("title_image_caption", ListItemBlock()), + ("image", ImageChooserBlock()), + ("single_book", SingleBookBlock()), + ("membership_plan", PlanBlock()), + ("richtext", RichTextBlock(features=block_features)), + ("button", ButtonBlock()), + ("newsletter_signup", NewsletterSignupBlock()), + ] + background_color = BackgroundColourChoiceBlock(required=False) + content = StreamBlock(stream_blocks, required=False) + + +class SingleColumnBlock(ColumnBlock): + column_width = ColumnWidthChoiceBlock() + alignment = AlignmentChoiceBlock( + help_text="Doesn't apply when used inside a column." + ) + + class Meta: + template = "app/blocks/single_column_block.html" + icon = "fa fa-th-large" + + +class MultiColumnBlock(StructBlock): + background_color = BackgroundColourChoiceBlock(required=False) + columns = WagtailListBlock(ColumnBlock, min_num=1, max_num=5) + + class Meta: + template = "app/blocks/columns_block.html" + icon = "fa fa-th-large" + + +class EventsListBlock(StructBlock): + number_of_events = IntegerBlock(required=True, default=3) + + def get_context(self, value, parent_context=None): + from app.models.wagtail import MapPage + + context = super().get_context(value, parent_context) + context.update(MapPage.get_map_context()) + return context + + class Meta: + template = "app/blocks/event_list_block.html" + icon = "fa fa-calendar" + + +class EventsListAndMap(StructBlock): + title = CharBlock(required=False) + intro = RichTextBlock(required=False) + number_of_events = IntegerBlock(required=True, default=3) + + def get_context(self, value, parent_context=None): + from app.models.wagtail import MapPage + + context = super().get_context(value, parent_context) + context.update(MapPage.get_map_context()) + return context + + class Meta: + template = "app/blocks/event_list_and_map_block.html" + icon = "fa fa-map" + + +def create_streamfield(additional_blocks=None, **kwargs): + blcks = [ + ("membership_options", MembershipOptionsBlock()), + ("image", ImageChooserBlock()), + ("featured_book", FeaturedBookBlock()), + ("book_selection", SelectedBooksBlock()), + ("recently_published_books", RecentlyPublishedBooks()), + ("featured_product", FeaturedProductBlock()), + ("product_selection", SelectedProductsBlock()), + ("full_product_list", FullProductList()), + ("hero_text", HeroTextBlock()), + ("heading", CharBlock(form_classname="full title")), + ("richtext", ArticleText()), + ("list_of_heading_image_text", ListBlock()), + ("single_column", SingleColumnBlock()), + ("columns", MultiColumnBlock()), + ("events_list_and_map", EventsListAndMap()), + ("events_list_block", EventsListBlock()), + ] + + if isinstance(additional_blocks, list): + blcks += additional_blocks + + return StreamField(blcks, null=True, blank=True, use_json_field=True, **kwargs) diff --git a/app/models/settings.py b/app/models/settings.py index 1d48fb81..fdc48385 100644 --- a/app/models/settings.py +++ b/app/models/settings.py @@ -2,8 +2,8 @@ from django.utils.translation import gettext_lazy as _ from wagtail import hooks from wagtail.admin.menu import MenuItem -from wagtail.admin.panels import StreamFieldPanel -from wagtail.contrib.settings.models import BaseSetting, register_setting +from wagtail.admin.panels import FieldPanel +from wagtail.contrib.settings.models import BaseSiteSetting, register_setting from wagtail.fields import RichTextField from wagtailautocomplete.edit_handlers import AutocompletePanel @@ -16,23 +16,23 @@ @register_setting(icon="users") -class MembershipJourney(BaseSetting): +class MembershipJourney(BaseSiteSetting): welcome_content = create_streamfield() - panels = [StreamFieldPanel("welcome_content")] + panels = [FieldPanel("welcome_content")] @register_setting(icon="users") -class MemberProfilePage(BaseSetting): +class MemberProfilePage(BaseSiteSetting): profile_page_content = create_streamfield( [ ("your_book_list", YourBooks()), ] ) - panels = [StreamFieldPanel("profile_page_content")] + panels = [FieldPanel("profile_page_content")] @register_setting(icon="money") -class UpsellPlanSettings(BaseSetting): +class UpsellPlanSettings(BaseSiteSetting): class Meta: verbose_name = _("Review Fee Settings") @@ -56,14 +56,13 @@ class Meta: upgrade_membership_text = RichTextField( blank=False, - features=["bold", "italic", "blockquote"], + features=["bold", "italic", "blockquote"], default="""

You’re currently paying {{ old_price }}. Select this option if it’s all you can afford right now — that is totally OK.

Other members paying solidarity rates will make it possible for us to continue offering this, so please consider if you can afford to increase your rate or if you genuinely need to stay here.

""", ) - upsell_plan = models.ForeignKey( MembershipPlanPage, on_delete=models.SET_NULL, diff --git a/app/models/stripe.py b/app/models/stripe.py index c02b81ae..f11ea2bc 100644 --- a/app/models/stripe.py +++ b/app/models/stripe.py @@ -26,7 +26,7 @@ from app.utils.stripe import ( DONATION_PRODUCT_NAME, SHIPPING_PRODUCT_NAME, - get_donation_product, + create_donation_line_item, get_primary_product_for_djstripe_subscription, ) @@ -127,20 +127,13 @@ def upsert_regular_donation(self, amount: float, metadata: Dict[str, Any] = dict # Create a new donation SI with the amount arg plan = self.items.first().plan items.append( - { - "price_data": { - "unit_amount_decimal": int(amount * 100), - "product": get_donation_product().id, - "metadata": {**metadata}, - # Mirror details from another SI - "currency": plan.currency, - "recurring": { - "interval": plan.interval, - "interval_count": plan.interval_count, - }, - }, - "quantity": 1, - } + create_donation_line_item( + amount=amount, + interval_count=plan.interval_count, + interval=plan.interval, + currency=plan.currency, + metadata=metadata, + ) ) subscription = stripe.Subscription.modify( @@ -330,6 +323,10 @@ def next_billing_date(self): @register_snippet class LBCProduct(djstripe.models.Product): + """ + Prices are for v1 flow; not applicable to v2 flow + """ + class Meta: proxy = True @@ -360,12 +357,12 @@ def basic_price(self): autocomplete_search_field = "name" def autocomplete_label(self): - from app.models import MembershipPlanPrice + # from app.models import MembershipPlanPrice - price = MembershipPlanPrice.objects.filter(products=self).first() + # price = MembershipPlanPrice.objects.filter(products=self).first() s = getattr(self, self.autocomplete_search_field) - if price: - return f"{s} (applies to price: {price})" + # if price: + # return f"{s} (applies to price: {price})" return s def get_prices_for_country(self, iso_a2: str, **kwargs): diff --git a/app/models/wagtail.py b/app/models/wagtail.py index 9da77a14..db684850 100644 --- a/app/models/wagtail.py +++ b/app/models/wagtail.py @@ -1,14 +1,12 @@ from typing import Optional import time -import urllib.parse +import uuid from datetime import datetime -from importlib.metadata import metadata -from urllib.parse import urlencode import djstripe.models +import orjson import shopify -import stripe from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField @@ -16,72 +14,41 @@ from django.core.validators import MinValueValidator from django.db import models from django.db.models import Q -from django.http.response import Http404 -from django.shortcuts import get_object_or_404, redirect from django.templatetags.static import static -from django.urls import reverse, reverse_lazy +from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.html import strip_tags -from django_countries import countries from djmoney.models.fields import Money, MoneyField from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.models import ClusterableModel -from wagtail import blocks from wagtail.admin.panels import ( FieldPanel, FieldRowPanel, HelpPanel, InlinePanel, MultiFieldPanel, - PageChooserPanel, - StreamFieldPanel, ) from wagtail.contrib.routable_page.models import RoutablePageMixin, route -from wagtail.fields import RichTextField, StreamField -from wagtail.images.blocks import ImageChooserBlock -from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail.fields import RichTextField from wagtail.images.models import AbstractImage, AbstractRendition from wagtail.models import Orderable, Page, Site from wagtail.rich_text import get_text_for_indexing from wagtail.search import index -from wagtail.snippets.blocks import SnippetChooserBlock from wagtail.snippets.models import register_snippet from wagtailautocomplete.edit_handlers import AutocompletePanel from wagtailcache.cache import WagtailCacheMixin, cache_page from wagtailseo import utils from wagtailseo.models import SeoMixin, SeoType, TwitterCard -from app.models.blocks import ArticleContentStream +from app.models.blocks import * from app.models.circle import CircleEvent -from app.models.django import User -from app.utils import include_keys from app.utils.abstract_model_querying import abstract_page_query_filter from app.utils.cache import django_cached from app.utils.shopify import metafields_to_dict from app.utils.stripe import create_shipping_zone_metadata, get_shipping_product -import orjson from .stripe import LBCProduct, ShippingZone -block_features = [ - "h1", - "h2", - "h3", - "h4", - "h5", - "bold", - "italic", - "link", - "ol", - "ul", - "hr", - "link", - "document-link", - "image", - "embed", - "blockquote", -] - class SeoMetadataMixin(SeoMixin, Page): class Meta: @@ -157,13 +124,20 @@ class Meta: # product = SnippetChooserBlock(djstripe.models.LBCProduct) +class Interval(models.TextChoices): + year = "year" + month = "month" + week = "week" + day = "day" + + @register_snippet class MembershipPlanPrice(Orderable, ClusterableModel): # name = models.CharField(max_length=150, blank=True) plan = ParentalKey("app.MembershipPlanPage", related_name="prices") price = MoneyField( - default=Money(0, "GBP"), + default=0, max_digits=14, decimal_places=2, default_currency="GBP", @@ -171,12 +145,6 @@ class MembershipPlanPrice(Orderable, ClusterableModel): blank=False, ) - class Interval(models.TextChoices): - year = "year" - month = "month" - week = "week" - day = "day" - interval = models.CharField( max_length=10, choices=Interval.choices, @@ -187,6 +155,16 @@ class Interval(models.TextChoices): interval_count = models.IntegerField(default=1, null=False, blank=True) + ### v2 flow + title = models.CharField(max_length=150, blank=True, null=True) + benefits = RichTextField( + features=["ul"], + help_text="List of pithy beneficial features of this plan", + null=True, + blank=True, + ) + ### /v2 + description = RichTextField(null=True, blank=True) free_shipping_zones = ParentalManyToManyField( @@ -204,21 +182,13 @@ class Interval(models.TextChoices): products = ParentalManyToManyField( LBCProduct, blank=True, - help_text="The stripe product that the user will be subscribed to. If multiple products are set here, then the user will be asked to pick which one they want, e.g. Classic or Contemporary books.", + help_text="(For V1-only signup flow.) The stripe product that the user will be subscribed to. If multiple products are set here, then the user will be asked to pick which one they want, e.g. Classic or Contemporary books.", + verbose_name="[V1] stripe products", ) panels = [ - FieldRowPanel( - [ - FieldPanel("price"), - ] - ), - MultiFieldPanel( - [ - AutocompletePanel("products", target_model=LBCProduct), - ], - heading="Product", - ), + FieldPanel("title", classname="full title"), + FieldPanel("price", classname="collapsibl collapsed"), FieldRowPanel( [ FieldPanel("interval_count"), @@ -238,6 +208,14 @@ class Interval(models.TextChoices): classname="full", help_text="Displayed to visitors who are considering purchasing a plan at this price.", ), + FieldPanel("benefits"), + MultiFieldPanel( + [ + AutocompletePanel("products", target_model=LBCProduct), + ], + heading="V1 signup flow", + classname="collapsible collapsed", + ), ] @property @@ -259,6 +237,15 @@ def shipping_fee(self, zone) -> Money: return Money(0, zone.rate_currency) return zone.rate * self.deliveries_per_billing_period + def equivalent_monthly_shipping_fee(self, zone) -> Money: + if self.free_shipping_zones.filter(code=zone.code).exists(): + return Money(0, zone.rate_currency) + return ( + zone.rate + * self.deliveries_per_billing_period + / self.months_per_billing_cycle + ) + def price_including_shipping(self, zone): return self.price + self.shipping_fee(zone) @@ -313,6 +300,20 @@ def months_per_billing_cycle(self): def equivalent_monthly_price(self) -> str: return self.price / self.months_per_billing_cycle + @property + def monthly_price_string(self) -> str: + money = str(self.equivalent_monthly_price) + s = f"{money}/month" + return s + + def equivalent_monthly_price_string_including_shipping(self, zone) -> str: + money = str( + self.equivalent_monthly_price + + self.shipping_fee(zone) / self.months_per_billing_cycle + ) + s = f"{money}/month" + return s + @property def equivalent_monthly_price_string(self) -> str: money = str(self.equivalent_monthly_price) @@ -321,6 +322,16 @@ def equivalent_monthly_price_string(self) -> str: return f"{s} + p&p" return s + @property + def shipping_price_string_uk(self) -> str: + return self.shipping_fee(ShippingZone.get_for_country(iso_a2="GB")) + + @property + def price_string_including_uk_shipping(self) -> str: + return self.price_string_including_shipping( + ShippingZone.get_for_country(iso_a2="GB") + ) + def __str__(self) -> str: return f"{self.price_string} on {self.plan}" @@ -424,6 +435,16 @@ def from_si(cls, si: djstripe.models.SubscriptionItem): ).first() return current_plan_price + @property + def discount_percent(self): + if self.interval != "year": + return None + monthly_price = self.plans.first().monthly_price.price + discount_percent = ( + monthly_price - self.equivalent_monthly_price + ) / monthly_price + return discount_percent + @register_snippet class Upsell(Orderable, ClusterableModel): @@ -469,130 +490,42 @@ def url(self, country_id=None): ) -class PlanTitleBlock(blocks.StructBlock): - class Meta: - template = "app/blocks/plan_title_block.html" - - # def get_context(self, value, parent_context=None): - # context = super().get_context(value, parent_context) - # context['page'] = parent_context['page'] - # return context - - -class PlanPricingBlock(blocks.StructBlock): - class Meta: - template = "app/blocks/plan_pricing_block.html" - - # def get_context(self, value, parent_context=None): - # context = super().get_context(value, parent_context) - # context['page'] = parent_context['page'] - # return context - - -class BookTypeChoice(blocks.ChoiceBlock): - choices = [ - ("classic", "classic"), - ("contemporary", "contemporary"), - ("all-books", "all-books"), - ] - - class Meta: - default = "all-books" - - -class BackgroundColourChoiceBlock(blocks.ChoiceBlock): - choices = [ - ("bg-black text-white", "black"), - ("bg-white", "white"), - # ('primary', 'primary'), - ("tw-bg-yellow", "yellow"), - # ('black', 'black'), - ("tw-bg-teal", "teal"), - ("tw-bg-darkgreen", "darkgreen"), - ("tw-bg-lilacgrey", "lilacgrey"), - ("tw-bg-coral", "coral"), - ("tw-bg-purple", "purple"), - ("tw-bg-magenta", "magenta"), - ("tw-bg-pink", "pink"), - ("tw-bg-lightgreen", "lightgreen"), - ] - - class Meta: - icon = "fa-paint" - - -class AlignmentChoiceBlock(blocks.ChoiceBlock): - choices = [ - ("left", "left"), - ("center", "center"), - ("right", "right"), - ] - - class Meta: - icon = "fa-arrows-h" - default = "center" - +def create_default_layout_syllabus(): + return {"id": uuid.uuid4(), "type": "syllabus_title", "value": {}} -class BootstrapButtonSizeChoiceBlock(blocks.ChoiceBlock): - choices = [ - ("sm", "small"), - ("md", "medium"), - ("lg", "large"), - ] - - class Meta: - icon = "fa-arrows-alt" - default = "md" - - -class BootstrapButtonStyleChoiceBlock(blocks.ChoiceBlock): - choices = [ - ("btn-outline-dark", "outlined"), - ("btn-dark text-yellow", "filled"), - ] - - class Meta: - default = "btn-outline-dark" - -class PlanBlock(blocks.StructBlock): - plan = blocks.PageChooserBlock( - page_type="app.membershipplanpage", - target_model="app.membershipplanpage", - can_choose_root=False, +@register_snippet +class SyllabusPage(Page, Orderable, ClusterableModel): + # title + description = RichTextField(null=True, blank=True) + book_types = models.CharField( + max_length=100, + choices=book_types, + default="all-books", + help_text="Used to display relevant books", + blank=True, ) - background_color = BackgroundColourChoiceBlock(required=False) - promotion_label = blocks.CharBlock( - required=False, help_text="Label that highlights this product" + stripe_product = models.ForeignKey( + LBCProduct, + on_delete=models.SET_NULL, + blank=False, + null=True, + related_name="syllabi", + help_text="The stripe product that the user will be subscribed to.", ) - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context["request_price"] = value["plan"].get_price_for_request( - parent_context.get("request") - ) - return context - - class Meta: - template = "app/blocks/membership_option_card.html" - icon = "fa-money" - - -class MembershipOptionsBlock(blocks.StructBlock): - heading = blocks.CharBlock( - required=False, - form_classname="full title", - default="Choose your plan", - ) - description = blocks.RichTextBlock( - required=False, - default="

Your subscription will begin with the most recently published book in your chosen collection.

", + layout = create_streamfield( + [ + ("syllabus_title", SyllabusTitleBlock()), + ], + default=create_default_layout_syllabus, ) - plans = blocks.ListBlock(PlanBlock) - class Meta: - template = "app/blocks/membership_options_grid.html" - icon = "fa-users" + content_panels = Page.content_panels + [ + FieldPanel("description"), + FieldPanel("book_types"), + AutocompletePanel("stripe_product", target_model=LBCProduct), + ] class CustomImage(AbstractImage): @@ -663,8 +596,8 @@ class BlogPage(WagtailCacheMixin, ArticleSeoMixin, Page): content_panels = Page.content_panels + [ FieldPanel("intro"), - StreamFieldPanel("body", classname="full"), - ImageChooserPanel("feed_image"), + FieldPanel("body", classname="full"), + FieldPanel("feed_image"), ] seo_description_sources = ArticleSeoMixin.seo_description_sources + ["intro"] @@ -723,10 +656,9 @@ def get_args_for_page(cls, product, metafields): description=product.attributes.get("body_html"), image_url=image_urls[0] if len(images) > 0 else "", image_urls=image_urls, - cached_price=cls.get_lowest_price(product) + cached_price=cls.get_lowest_price(product), ) - @classmethod def get_root_page(cls): """ @@ -776,7 +708,7 @@ def sync_from_shopify_product_id(cls, shopify_product_id): metafields = metafields_to_dict(metafields) if cls.objects.filter(shopify_product_id=shopify_product_id).exists(): return cls.update_instance_for_product(product, metafields) - else: + else: return cls.create_instance_for_product(product, metafields) @property @@ -856,315 +788,11 @@ def get_specific_product_by_shopify_id(cls, shopify_product_id): ) -class ButtonBlock(blocks.StructBlock): - text = blocks.CharBlock(max_length=100, required=False) - page = blocks.PageChooserBlock( - required=False, help_text="Pick a page or specify a URL" - ) - href = blocks.URLBlock( - required=False, help_text="Pick a page or specify a URL", label="URL" - ) - size = BootstrapButtonSizeChoiceBlock(required=False, default="md") - style = BootstrapButtonStyleChoiceBlock(required=False, default="btn-outline-dark") - - class Meta: - template = "app/blocks/cta.html" - - -class HeroTextBlock(blocks.StructBlock): - heading = blocks.CharBlock(max_length=250, form_classname="full title") - background_color = BackgroundColourChoiceBlock(required=False) - button = ButtonBlock(required=False) - - class Meta: - template = "app/blocks/hero_block.html" - icon = "fa fa-alphabet" - - -class ListItemBlock(blocks.StructBlock): - title = blocks.CharBlock(max_length=250, form_classname="full title") - image = ImageChooserBlock(required=False) - image_css = blocks.CharBlock(max_length=500, required=False) - caption = blocks.RichTextBlock(max_length=500) - background_color = BackgroundColourChoiceBlock(required=False) - button = ButtonBlock(required=False) - - class Meta: - template = "app/blocks/list_item_block.html" - icon = "fa fa-alphabet" - - -class ColumnWidthChoiceBlock(blocks.ChoiceBlock): - choices = [ - ("small", "small"), - ("medium", "medium"), - ("large", "large"), - ] - - class Meta: - icon = "fa-arrows-alt" - default = "small" - - -class ListBlock(blocks.StructBlock): - column_width = ColumnWidthChoiceBlock() - items = blocks.ListBlock(ListItemBlock) - - class Meta: - template = "app/blocks/list_block.html" - icon = "fa fa-alphabet" - - -class FeaturedBookBlock(blocks.StructBlock): - book = blocks.PageChooserBlock( - page_type="app.bookpage", - target_model="app.bookpage", - can_choose_root=False, - ) - background_color = BackgroundColourChoiceBlock(required=False) - promotion_label = blocks.CharBlock( - required=False, help_text="Label that highlights this product" - ) - description = blocks.RichTextBlock( - required=False, - help_text="This will replace the book's default description. You can use this to provide a more contextualised description of the book", - ) - - class Meta: - template = "app/blocks/featured_book_block.html" - icon = "fa fa-book" - - -class FeaturedProductBlock(blocks.StructBlock): - product = blocks.PageChooserBlock( - page_type="app.merchandisepage", - target_model="app.merchandisepage", - can_choose_root=False, - ) - background_color = BackgroundColourChoiceBlock(required=False) - promotion_label = blocks.CharBlock( - required=False, help_text="Label that highlights this product" - ) - description = blocks.RichTextBlock( - required=False, - help_text="This will replace the product's default description. You can use this to provide a more contextualised description of the product", - ) - - class Meta: - template = "app/blocks/featured_product_block.html" - icon = "fa fa-shopping-cart" - - -class BookGridBlock(blocks.StructBlock): - column_width = ColumnWidthChoiceBlock() - - class Meta: - template = "app/blocks/book_grid_block.html" - icon = "fa fa-book" - - -class ProductGridBlock(blocks.StructBlock): - column_width = ColumnWidthChoiceBlock() - - class Meta: - template = "app/blocks/product_grid_block.html" - icon = "fa fa-shopping-cart" - - -class SelectedBooksBlock(BookGridBlock): - books = blocks.ListBlock( - blocks.PageChooserBlock( - page_type="app.bookpage", - target_model="app.bookpage", - can_choose_root=False, - ) - ) - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context["books"] = value["books"] - return context - - -class SelectedProductsBlock(ProductGridBlock): - products = blocks.ListBlock( - blocks.PageChooserBlock( - page_type="app.merchandisepage", - target_model="app.merchandisepage", - can_choose_root=False, - ) - ) - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context["products"] = list(value["products"]) - return context - - -class RecentlyPublishedBooks(BookGridBlock): - max_books = blocks.IntegerBlock( - default=4, help_text="How many books should show up?" - ) - type = BookTypeChoice(default="all-books") - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - filters = {} - if value["type"] != None and value["type"] != "all-books": - filters["type"] = value["type"] - context["books"] = ( - BookPage.objects.order_by("-published_date") - .live() - .public() - .filter(published_date__isnull=False, **filters) - .all()[: value["max_books"]] - ) - return context - - -class FullProductList(ProductGridBlock): - max_products = blocks.IntegerBlock( - default=10, help_text="How many products should show up?" - ) - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context["products"] = list( - MerchandisePage.objects.live().public().all()[: value["max_products"]] - ) - return context - - -class YourBooks(BookGridBlock): - class Meta: - template = "app/blocks/your_books_grid_block.html" - icon = "fa fa-book" - - -class SingleBookBlock(blocks.StructBlock): - book = blocks.PageChooserBlock( - page_type="app.bookpage", - target_model="app.bookpage", - can_choose_root=False, - ) - - class Meta: - template = "app/blocks/single_book_block.html" - icon = "fa fa-book" - - -class NewsletterSignupBlock(blocks.StructBlock): - heading = blocks.CharBlock(required=False, max_length=150) - - class Meta: - template = "app/blocks/newsletter_signup_block.html" - icon = "fa fa-email" - - -class ArticleText(blocks.StructBlock): - text = blocks.RichTextBlock(form_classname="full", features=block_features) - alignment = AlignmentChoiceBlock( - help_text="Doesn't apply when used inside a column." - ) - - class Meta: - template = "app/blocks/richtext.html" - - -class ColumnBlock(blocks.StructBlock): - stream_blocks = [ - ("hero_text", HeroTextBlock()), - ("title_image_caption", ListItemBlock()), - ("image", ImageChooserBlock()), - ("single_book", SingleBookBlock()), - ("membership_plan", PlanBlock()), - ("richtext", blocks.RichTextBlock(features=block_features)), - ("button", ButtonBlock()), - ("newsletter_signup", NewsletterSignupBlock()), - ] - background_color = BackgroundColourChoiceBlock(required=False) - content = blocks.StreamBlock(stream_blocks, required=False) - - -class SingleColumnBlock(ColumnBlock): - column_width = ColumnWidthChoiceBlock() - alignment = AlignmentChoiceBlock( - help_text="Doesn't apply when used inside a column." - ) - - class Meta: - template = "app/blocks/single_column_block.html" - icon = "fa fa-th-large" - - -class MultiColumnBlock(blocks.StructBlock): - background_color = BackgroundColourChoiceBlock(required=False) - columns = blocks.ListBlock(ColumnBlock, min_num=1, max_num=5) - - class Meta: - template = "app/blocks/columns_block.html" - icon = "fa fa-th-large" - - -class EventsListBlock(blocks.StructBlock): - number_of_events = blocks.IntegerBlock(required=True, default=3) - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context.update(MapPage.get_map_context()) - return context - - class Meta: - template = "app/blocks/event_list_block.html" - icon = "fa fa-calendar" - - -class EventsListAndMap(blocks.StructBlock): - title = blocks.CharBlock(required=False) - intro = blocks.RichTextBlock(required=False) - number_of_events = blocks.IntegerBlock(required=True, default=3) - - def get_context(self, value, parent_context=None): - context = super().get_context(value, parent_context) - context.update(MapPage.get_map_context()) - return context - - class Meta: - template = "app/blocks/event_list_and_map_block.html" - icon = "fa fa-map" - - -def create_streamfield(additional_blocks=None, **kwargs): - blcks = [ - ("membership_options", MembershipOptionsBlock()), - ("image", ImageChooserBlock()), - ("featured_book", FeaturedBookBlock()), - ("book_selection", SelectedBooksBlock()), - ("recently_published_books", RecentlyPublishedBooks()), - ("featured_product", FeaturedProductBlock()), - ("product_selection", SelectedProductsBlock()), - ("full_product_list", FullProductList()), - ("hero_text", HeroTextBlock()), - ("heading", blocks.CharBlock(form_classname="full title")), - ("richtext", ArticleText()), - ("list_of_heading_image_text", ListBlock()), - ("single_column", SingleColumnBlock()), - ("columns", MultiColumnBlock()), - ("events_list_and_map", EventsListAndMap()), - ("events_list_block", EventsListBlock()), - ] - - if isinstance(additional_blocks, list): - blcks += additional_blocks - - return StreamField(blcks, null=True, blank=True, use_json_field=True, **kwargs) - - @method_decorator(cache_page, name="serve") class MerchandiseIndexPage(WagtailCacheMixin, IndexPageSeoMixin, Page): show_in_menus_default = True layout = create_streamfield() - content_panels = Page.content_panels + [StreamFieldPanel("layout")] + content_panels = Page.content_panels + [FieldPanel("layout")] @method_decorator(cache_page, name="serve") @@ -1194,9 +822,7 @@ class BookPage(WagtailCacheMixin, BaseShopifyProductPage): type = models.CharField(max_length=300, blank=True) layout = create_streamfield() - content_panels = BaseShopifyProductPage.content_panels + [ - StreamFieldPanel("layout") - ] + content_panels = BaseShopifyProductPage.content_panels + [FieldPanel("layout")] @classmethod def get_root_page(cls): @@ -1220,6 +846,7 @@ def get_args_for_page(cls, product, metafields): class Meta: ordering = ["-published_date"] + def metafields_array_to_list(arg): value = [] if isinstance(arg, str): @@ -1231,10 +858,34 @@ def metafields_array_to_list(arg): else: return [] + +def create_default_layout_plan(): + return ( + {"id": uuid.uuid4(), "type": "plan_title", "value": {}}, + {"id": uuid.uuid4(), "type": "plan_pricing", "value": {}}, + ) + + @method_decorator(cache_page, name="serve") class MembershipPlanPage(WagtailCacheMixin, ArticleSeoMixin, Page): parent_page_types = ["app.HomePage"] + ### v2 signup flow fields + display_in_quiz_flow = models.BooleanField( + default=False, verbose_name="Display in v2 signup flow" + ) + benefits = RichTextField( + help_text="List of pithy beneficial features of this plan. Bullet points are automatically formatted as ticks.", + null=True, + blank=True, + ) + syllabi = ParentalManyToManyField( + SyllabusPage, + related_name="plans", + help_text="The syllabi available for this plan", + ) + ### /v2 + deliveries_per_year = models.PositiveIntegerField( default=0, validators=[MinValueValidator(0)] ) @@ -1254,17 +905,34 @@ class MembershipPlanPage(WagtailCacheMixin, ArticleSeoMixin, Page): layout = create_streamfield( [("plan_title", PlanTitleBlock()), ("plan_pricing", PlanPricingBlock())], - default=(("plan_title", {}), ("plan_pricing", {})), + default=create_default_layout_plan, ) panels = content_panels = Page.content_panels + [ - FieldPanel("deliveries_per_year"), - FieldPanel("description"), - InlinePanel("prices", min_num=1, label="Subscription Pricing Options"), - InlinePanel("upsells", heading="Upsell options", label="Upsell option"), - FieldPanel("pick_product_title", classname="full title"), - FieldPanel("pick_product_text"), - StreamFieldPanel("layout"), + FieldPanel("deliveries_per_year", heading="[v1+v2] Deliveries per year"), + InlinePanel( + "prices", min_num=1, label="Prices", classname="collapsible collapsed" + ), + MultiFieldPanel( + [ + FieldPanel("display_in_quiz_flow"), + FieldPanel("description"), + FieldPanel("benefits"), + AutocompletePanel("syllabi", target_model=SyllabusPage), + ], + heading="V2 signup flow", + classname="collapsible", + ), + MultiFieldPanel( + [ + InlinePanel("upsells", label="Upsell prices"), + FieldPanel("pick_product_title", classname="full title"), + FieldPanel("pick_product_text"), + ], + heading="V1 signup flow", + classname="collapsible collapsed", + ), + FieldPanel("layout"), ] @property @@ -1300,7 +968,9 @@ def annual_price(self) -> MembershipPlanPrice: return self.prices.filter(interval="year").order_by("interval_count").first() @property - def annual_percent_off_per_month(self) -> str: + def annual_percent_off_per_month(self): + if self.basic_price is None or self.annual_price is None: + return None return ( self.annual_price.equivalent_monthly_price - self.basic_price.price ) / self.basic_price.price @@ -1320,21 +990,21 @@ def get_context(self, request, *args, **kwargs): class HomePage(WagtailCacheMixin, IndexPageSeoMixin, RoutablePageMixin, Page): show_in_menus_default = True layout = create_streamfield() - content_panels = Page.content_panels + [StreamFieldPanel("layout")] + content_panels = Page.content_panels + [FieldPanel("layout")] @method_decorator(cache_page, name="serve") class InformationPage(WagtailCacheMixin, ArticleSeoMixin, Page): show_in_menus_default = True layout = create_streamfield() - content_panels = Page.content_panels + [StreamFieldPanel("layout")] + content_panels = Page.content_panels + [FieldPanel("layout")] @method_decorator(cache_page, name="serve") class BookIndexPage(WagtailCacheMixin, IndexPageSeoMixin, Page): show_in_menus_default = True layout = create_streamfield() - content_panels = Page.content_panels + [StreamFieldPanel("layout")] + content_panels = Page.content_panels + [FieldPanel("layout")] @method_decorator(cache_page, name="serve") diff --git a/app/settings/base.py b/app/settings/base.py index f1ad2bcf..f9778655 100644 --- a/app/settings/base.py +++ b/app/settings/base.py @@ -46,7 +46,6 @@ "django_countries", "django_gravatar", "active_link", - "wagtailfontawesome", # "wagtail_transfer", "django.contrib.gis", "django.contrib.admin", @@ -68,6 +67,7 @@ "oauth2_provider", "django.contrib.humanize", "django_dbq", + "slippers", ] IMPORT_EXPORT_USE_TRANSACTIONS = True @@ -112,8 +112,9 @@ "wagtailmenus.context_processors.wagtailmenus", # "app.context_processors.user_data", ], + "builtins": ["slippers.templatetags.slippers"], }, - }, + } ] WSGI_APPLICATION = "app.wsgi.application" @@ -458,4 +459,6 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240 \ No newline at end of file +DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240 + +SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" diff --git a/app/slippers_autoload_components.py b/app/slippers_autoload_components.py new file mode 100644 index 00000000..692127e2 --- /dev/null +++ b/app/slippers_autoload_components.py @@ -0,0 +1,76 @@ +from pathlib import Path + +from django.conf import settings +from slippers.templatetags.slippers import register_components + +SLIPPERS_SUBDIR = "components" + + +def find_slipper_dirs(): + dirs = [] + from django.template import engines + + for backend in engines.all(): + for loader in backend.engine.template_loaders: + if not hasattr(loader, "get_dirs"): + continue + for templates_dir in loader.get_dirs(): + templates_path = Path(templates_dir) + slippers_dir = templates_path / SLIPPERS_SUBDIR + if slippers_dir.exists(): + dirs.append(templates_path) + return dirs + + +def register_dir_components(dir, templates_path): + register_components( + { + template.stem: str(template.relative_to(templates_path)) + for template in dir.glob("*.html") + } + ) + + +def register(): + """ + Register discovered slippers components. + """ + import os + + from django.template import engines + + dir_path = Path(os.path.dirname(os.path.realpath(__file__))) + template_dirs = [dir_path / "templates"] + # template_dirs = find_slipper_dirs() + + slippers_dirs = [] + + for templates_dir in template_dirs: + if settings.DEBUG: + print( + "Registering slippers components from", templates_dir / SLIPPERS_SUBDIR + ) + if templates_dir.exists(): + templates_path = Path(templates_dir) + slippers_dir = templates_path / SLIPPERS_SUBDIR + register_dir_components(slippers_dir, templates_path) + slippers_dirs.append(slippers_dir) + + if settings.DEBUG: + # To support autoreload for `manage.py runserver`, also add a watch so that + # we re-run this code if new slippers templates are added + + from django.dispatch import receiver + from django.utils.autoreload import autoreload_started, file_changed + + @receiver(autoreload_started, dispatch_uid="watch_slippers_dirs") + def watch_slippers_dirs(sender, **kwargs): + for path in slippers_dirs: + sender.watch_dir(path, "*.html") + + @receiver(file_changed, dispatch_uid="slippers_template_changed") + def template_changed(sender, file_path, **kwargs): + path = Path(file_path) + if path.exists() and path.is_dir(): + # This happens when new html files are created, re-run registration + register() diff --git a/app/static/wagtailadmin.css b/app/static/wagtailadmin.css index 21d8e568..9e572129 100644 --- a/app/static/wagtailadmin.css +++ b/app/static/wagtailadmin.css @@ -1,5 +1,4 @@ :root { - --color-primary-hue: 47.5; } .alc__list-controls { diff --git a/app/templates/account/signup.html b/app/templates/account/signup.html index 97163748..8b97faf4 100644 --- a/app/templates/account/signup.html +++ b/app/templates/account/signup.html @@ -7,8 +7,11 @@ {% block modal_content %} {% get_providers as socialaccount_providers %}

{% trans "Let's get you set up" %}

- {% url_test includes="/checkout" as checkout_flow %} - {% if checkout_flow %} + {% url_test includes="/checkout" as v2_checkout_flow %} + {% url_test includes="/checkout" as v1_checkout_flow %} + {% if v2_checkout_flow %} + {% signup_header title="Let's get you set up" step="Create account" %} + {% elif v1_checkout_flow %}