diff --git a/rca/enquire_to_study/tests/test_forms.py b/rca/enquire_to_study/tests/test_forms.py index b3056e9c7..64a78e3c3 100644 --- a/rca/enquire_to_study/tests/test_forms.py +++ b/rca/enquire_to_study/tests/test_forms.py @@ -10,13 +10,21 @@ EnquiryFormSubmission, EnquiryFormSubmissionProgrammesOrderable, ) -from rca.programmes.factories import ProgrammePageFactory +from rca.programmes.factories import ( + ProgrammePageFactory, + ProgrammePageProgrammeTypeFactory, + ProgrammeTypeFactory, +) class TestEnquireToStudyForm(TestCase): def setUp(self): self.start_date = StartDateFactory(qs_code="test-code") self.enquiry_reason = EnquiryReasonFactory() + page = ProgrammePageFactory(qs_code=1) + ProgrammePageProgrammeTypeFactory( + page=page, programme_type=ProgrammeTypeFactory() + ) self.form_data = { "first_name": "Monty", "last_name": "python", @@ -25,7 +33,7 @@ def setUp(self): "country_of_residence": "GB", "city": "Bristol", "country_of_citizenship": "GB", - "programmes": [ProgrammePageFactory(qs_code=1, programme_type__pk=2).pk], + "programmes": [page.pk], "start_date": self.start_date.pk, "enquiry_reason": self.enquiry_reason.pk, "enquiry_questions": "What is your name?", diff --git a/rca/enquire_to_study/tests/test_views.py b/rca/enquire_to_study/tests/test_views.py index 0eb18ce74..e6dfa0799 100644 --- a/rca/enquire_to_study/tests/test_views.py +++ b/rca/enquire_to_study/tests/test_views.py @@ -22,7 +22,11 @@ from rca.enquire_to_study.models import EnquireToStudySettings, EnquiryFormSubmission from rca.enquire_to_study.views import EnquireToStudyFormView from rca.enquire_to_study.wagtail_hooks import EnquiryFormSubmissionAdmin -from rca.programmes.factories import ProgrammePageFactory +from rca.programmes.factories import ( + ProgrammePageFactory, + ProgrammePageProgrammeTypeFactory, + ProgrammeTypeFactory, +) class EnquireToStudyFormViewTest(TestCase): @@ -54,6 +58,10 @@ def setUp(self): email_content="Test email content", site_id=Site.objects.get().pk, ) + page = ProgrammePageFactory(qs_code=1) + ProgrammePageProgrammeTypeFactory( + page=page, programme_type=ProgrammeTypeFactory() + ) self.form_data = { "first_name": "Monty", @@ -63,7 +71,7 @@ def setUp(self): "country_of_residence": "GB", "city": "Bristol", "country_of_citizenship": "GB", - "programmes": [ProgrammePageFactory(qs_code=1, programme_type__pk=2).pk], + "programmes": [page.pk], "start_date": StartDateFactory(qs_code="test-code").pk, "enquiry_reason": EnquiryReasonFactory().pk, "enquiry_questions": "What is your name?", diff --git a/rca/programmes/factories.py b/rca/programmes/factories.py index 64d138b26..1fecdb8f5 100644 --- a/rca/programmes/factories.py +++ b/rca/programmes/factories.py @@ -2,7 +2,12 @@ import wagtail_factories from faker import Factory as FakerFactory -from .models import DegreeLevel, ProgrammePage, ProgrammeType +from .models import ( + DegreeLevel, + ProgrammePage, + ProgrammePageProgrammeType, + ProgrammeType, +) faker = FakerFactory.create() @@ -31,4 +36,10 @@ class Meta: scholarships_information = factory.Faker("text", max_nb_chars=100) search_description = factory.Faker("text", max_nb_chars=25) degree_level = factory.SubFactory(DegreeLevelFactory) - programme_type = factory.SubFactory(ProgrammeTypeFactory) + + +class ProgrammePageProgrammeTypeFactory(factory.django.DjangoModelFactory): + class Meta: + model = ProgrammePageProgrammeType + + page = factory.SubFactory(ProgrammePageFactory) diff --git a/rca/programmes/migrations/0093_programmepageprogrammetype.py b/rca/programmes/migrations/0093_programmepageprogrammetype.py new file mode 100644 index 000000000..88bed3e19 --- /dev/null +++ b/rca/programmes/migrations/0093_programmepageprogrammetype.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.16 on 2025-01-14 14:23 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("programmes", "0092_link_block_url_label"), + ] + + operations = [ + migrations.CreateModel( + name="ProgrammePageProgrammeType", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="programme_types", + to="programmes.programmepage", + ), + ), + ( + "programme_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="programmes.programmetype", + ), + ), + ], + ), + ] diff --git a/rca/programmes/migrations/0094_migrate_programme_type_to_programme_types.py b/rca/programmes/migrations/0094_migrate_programme_type_to_programme_types.py new file mode 100644 index 000000000..04f4aa7d6 --- /dev/null +++ b/rca/programmes/migrations/0094_migrate_programme_type_to_programme_types.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.16 on 2025-01-14 14:25 + +from django.db import migrations + +def migrate_programme_type_to_programme_types(apps, schema_editor): + # Get the models + ProgrammePage = apps.get_model("programmes", "ProgrammePage") + ProgrammePageProgrammeType = apps.get_model("programmes", "ProgrammePageProgrammeType") + + for page in ProgrammePage.objects.all(): + if programme_type := page.programme_type: + ProgrammePageProgrammeType.objects.create( + page_id=page.id, + programme_type=programme_type, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("programmes", "0093_programmepageprogrammetype"), + ] + + operations = [ + migrations.RunPython(migrate_programme_type_to_programme_types, reverse_code=migrations.RunPython.noop), + ] diff --git a/rca/programmes/migrations/0095_remove_programme_type_field.py b/rca/programmes/migrations/0095_remove_programme_type_field.py new file mode 100644 index 000000000..9f24b10c9 --- /dev/null +++ b/rca/programmes/migrations/0095_remove_programme_type_field.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2025-01-17 08:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("programmes", "0094_migrate_programme_type_to_programme_types"), + ] + + operations = [ + migrations.RemoveField( + model_name="programmepage", + name="programme_type", + ), + ] diff --git a/rca/programmes/models.py b/rca/programmes/models.py index 9b3d92b35..a4bf2ed14 100644 --- a/rca/programmes/models.py +++ b/rca/programmes/models.py @@ -113,6 +113,18 @@ def get_fake_slug(self): return slugify(self.display_name) +class ProgrammePageProgrammeType(models.Model): + page = ParentalKey("programmes.ProgrammePage", related_name="programme_types") + programme_type = models.ForeignKey( + "programmes.ProgrammeType", + on_delete=models.CASCADE, + ) + panels = [FieldPanel("programme_type")] + + def __str__(self): + return self.programme_type.title + + class ProgrammePageRelatedSchoolsAndResearchPages(RelatedPage): source_page = ParentalKey( "ProgrammePage", related_name="related_schools_and_research_pages" @@ -330,13 +342,6 @@ class ProgrammePage(TapMixin, ContactFieldsMixin, BasePage): degree_level = models.ForeignKey( DegreeLevel, on_delete=models.SET_NULL, blank=False, null=True, related_name="+" ) - programme_type = models.ForeignKey( - ProgrammeType, - on_delete=models.SET_NULL, - blank=False, - null=True, - related_name="+", - ) hero_image = models.ForeignKey( "images.CustomImage", null=True, @@ -615,10 +620,7 @@ class ProgrammePage(TapMixin, ContactFieldsMixin, BasePage): # Taxonomy, relationships etc FieldPanel("degree_level"), InlinePanel("subjects", label="Subjects"), - FieldPanel( - "programme_type", - help_text="Used to show content related to this programme page", - ), + InlinePanel("programme_types", label="Programme Types"), MultiFieldPanel( [ FieldPanel("hero_image"), @@ -844,7 +846,14 @@ class ProgrammePage(TapMixin, ContactFieldsMixin, BasePage): index.SearchField("scholarship_accordion_items"), index.SearchField("scholarship_information_blocks"), index.SearchField("more_information_blocks", boost=2), - index.RelatedFields("programme_type", [index.SearchField("display_name")]), + index.RelatedFields( + "programme_types", + [ + index.RelatedFields( + "programme_type", [index.SearchField("display_name")] + ) + ], + ), index.RelatedFields( "programme_locations", [index.RelatedFields("programme_location", [index.SearchField("title")])], @@ -876,7 +885,7 @@ class ProgrammePage(TapMixin, ContactFieldsMixin, BasePage): api_fields = [ # Fields for filtering and display, shared with shortcourses.ShortCoursePage. APIField("subjects"), - APIField("programme_type"), + APIField("programme_types"), APIField("related_schools_and_research_pages"), APIField( "summary", @@ -1169,7 +1178,7 @@ def get_context(self, request, *args, **kwargs): filters = [ {"id": "subjects", "title": "Subject", "items": subjects}, - {"id": "programme_type", "title": "Type", "items": programme_types}, + {"id": "programme_types", "title": "Type", "items": programme_types}, { "id": "related_schools_and_research_pages", "title": "Schools & centres", diff --git a/rca/project_styleguide/templates/patterns/molecules/relatedcontent/relatedprogrammes--large.html b/rca/project_styleguide/templates/patterns/molecules/relatedcontent/relatedprogrammes--large.html index 78e60f8b0..2ce61c070 100644 --- a/rca/project_styleguide/templates/patterns/molecules/relatedcontent/relatedprogrammes--large.html +++ b/rca/project_styleguide/templates/patterns/molecules/relatedcontent/relatedprogrammes--large.html @@ -26,7 +26,17 @@ - + {% with programme_types=related_item.programme_types.all %} + {% if related_item.degree_levels %} + + {% else %} + + {% endif %} + {% endwith %} - {% if related_item.degree_level %} - - {% elif related_item.programme_type %} - - {% elif related_item.meta %} - - {% elif related_item.get_verbose_name == 'Guide page' %} - - {% endif %} + {% with programme_types=related_item.programme_types.all %} + {% if related_item.degree_level %} + + {% elif programme_types %} + {% if related_item.booking_summary %} + + {% else %} + + {% endif %} + {% elif related_item.meta %} + + {% elif related_item.get_verbose_name == 'Guide page' %} + + {% endif %} + {% endwith %}