diff --git a/src/content/models/__init__.py b/src/content/models/__init__.py new file mode 100644 index 000000000..5a49b519e --- /dev/null +++ b/src/content/models/__init__.py @@ -0,0 +1,43 @@ +from .base_content import ( + BasePage, + BasePageQuerySet, + ContentOwnerMixin, + ContentPage, + ContentPageQuerySet, + Theme, +) +from .blog import BlogIndex, BlogPost +# from .events import EventPage, EventsHome +from .navigation import NavigationPage +from .search import ( + SearchExclusionPageLookUp, + SearchFieldsMixin, + SearchKeywordOrPhrase, + SearchKeywordOrPhraseQuerySet, + SearchPinPageLookUp, +) + + +__all__ = [ + # base_content + "BasePage", + "BasePageQuerySet", + "ContentOwnerMixin", + "ContentPage", + "ContentPageQuerySet", + "Theme", + # blog + "BlogIndex", + "BlogPost", + # events + # "EventPage", + # "EventsHome", + # navigation + "NavigationPage", + # search + "SearchExclusionPageLookUp", + "SearchFieldsMixin", + "SearchKeywordOrPhrase", + "SearchKeywordOrPhraseQuerySet", + "SearchPinPageLookUp", +] diff --git a/src/content/models.py b/src/content/models/base_content.py similarity index 69% rename from src/content/models.py rename to src/content/models/base_content.py index 54208f1c9..c8e936f9f 100644 --- a/src/content/models.py +++ b/src/content/models/base_content.py @@ -3,8 +3,6 @@ from typing import Optional from django.contrib.auth import get_user_model -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q, Subquery from django.forms import widgets @@ -24,10 +22,9 @@ from wagtail.snippets.models import register_snippet from wagtail.utils.decorators import cached_classmethod -import dw_design_system.dwds.components as dwds_blocks from content import blocks as content_blocks + from content.utils import ( - get_search_content_for_block, manage_excluded, manage_pinned, truncate_words_and_chars, @@ -36,9 +33,14 @@ from extended_search.index import DWIndexedField as IndexedField from extended_search.index import Indexed, RelatedFields from peoplefinder.widgets import PersonChooser -from search.utils import split_query from user.models import User as UserModel +from .search import ( + SearchExclusionPageLookUp, + SearchFieldsMixin, + SearchPinPageLookUp, +) + logger = logging.getLogger(__name__) @@ -258,77 +260,6 @@ def get_all_subclasses(cls): return subclasses -class SearchFieldsMixin(models.Model): - """ - Specific fields and settings to manage search. Extra fields are generally - defined to make custom and specific indexing as defined in /docs/search.md - - Usage: - - Add this mixin to a model - - Define the fields that should be indexed in search_stream_fields. - Example: ["body"] - - Add SearchFieldsMixin.indexed_fields to the Model's indexed_fields list - - Run self._generate_search_field_content() in full_clean() to generate - search fields - """ - - class Meta: - abstract = True - - search_stream_fields: list[str] = [] - - search_title = models.CharField( - max_length=255, - ) - search_headings = models.TextField( - blank=True, - null=True, - ) - search_content = models.TextField( - blank=True, - null=True, - ) - - def _generate_search_field_content(self): - self.search_title = self.title - search_headings = [] - search_content = [] - - for stream_field_name in self.search_stream_fields: - stream_field = getattr(self, stream_field_name) - for stream_child in stream_field: - block_search_headings, block_search_content = ( - get_search_content_for_block(stream_child.block, stream_child.value) - ) - search_headings += block_search_headings - search_content += block_search_content - - self.search_headings = " ".join(search_headings) - self.search_content = " ".join(search_content) - - indexed_fields = [ - IndexedField( - "search_title", - tokenized=True, - explicit=True, - fuzzy=True, - boost=5.0, - ), - IndexedField( - "search_headings", - tokenized=True, - explicit=True, - fuzzy=True, - boost=3.0, - ), - IndexedField( - "search_content", - tokenized=True, - explicit=True, - ), - ] - - class ContentPage(SearchFieldsMixin, BasePage): objects = PageManager.from_queryset(ContentPageQuerySet)() @@ -518,115 +449,3 @@ def save(self, *args, **kwargs): manage_pinned(self, self.pinned_phrases) return super().save(*args, **kwargs) - - -class SearchKeywordOrPhrase(models.Model): - keyword_or_phrase = models.CharField(max_length=1000) - # TODO: Remove historical records. - history = HistoricalRecords() - - -class SearchKeywordOrPhraseQuerySet(models.QuerySet): - def filter_by_query(self, query): - query_parts = split_query(query) - - return self.filter(search_keyword_or_phrase__keyword_or_phrase__in=query_parts) - - -class NavigationPage(SearchFieldsMixin, BasePage): - template = "content/navigation_page.html" - - search_stream_fields: list[str] = ["primary_elements", "secondary_elements"] - - primary_elements = StreamField( - [ - ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), - ], - blank=True, - ) - - secondary_elements = StreamField( - [ - ("dw_curated_page_links", dwds_blocks.CustomPageLinkListBlock()), - ("dw_tagged_page_list", dwds_blocks.TaggedPageListBlock()), - ("dw_cta", dwds_blocks.CTACardBlock()), - ("dw_engagement_card", dwds_blocks.EngagementCardBlock()), - ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), - ], - blank=True, - ) - - content_panels = BasePage.content_panels + [ - FieldPanel("primary_elements"), - FieldPanel("secondary_elements"), - ] - - indexed_fields = SearchFieldsMixin.indexed_fields + [ - IndexedField("primary_elements"), - IndexedField("secondary_elements"), - ] - - def get_template(self, request, *args, **kwargs): - return self.template - - def get_context(self, request, *args, **kwargs): - context = super().get_context(request, *args, **kwargs) - - return context - - def full_clean(self, *args, **kwargs): - self._generate_search_field_content() - super().full_clean(*args, **kwargs) - - -class SearchExclusionPageLookUp(models.Model): - objects = SearchKeywordOrPhraseQuerySet.as_manager() - - search_keyword_or_phrase = models.ForeignKey( - SearchKeywordOrPhrase, - on_delete=models.CASCADE, - ) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() - content_object = GenericForeignKey("content_type", "object_id") - # TODO: Remove historical records. - history = HistoricalRecords() - - -class SearchPinPageLookUp(models.Model): - objects = SearchKeywordOrPhraseQuerySet.as_manager() - - search_keyword_or_phrase = models.ForeignKey( - SearchKeywordOrPhrase, - on_delete=models.CASCADE, - ) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() - content_object = GenericForeignKey("content_type", "object_id") - # TODO: Remove historical records. - history = HistoricalRecords() - - -class BlogIndex(BasePage): - template = "content/blog_index.html" - subpage_types = [ - "content.BlogPost", - ] - is_creatable = False - - def get_template(self, request, *args, **kwargs): - return self.template - - def get_context(self, request, *args, **kwargs): - context = super().get_context(request, *args, **kwargs) - context["children"] = self.get_children().live().public().order_by("title") - return context - - -class BlogPost(ContentPage): - template = "content/blog_post.html" - subpage_types = [] - is_creatable = True - - def get_template(self, request, *args, **kwargs): - return self.template diff --git a/src/content/models/blog.py b/src/content/models/blog.py new file mode 100644 index 000000000..b866526aa --- /dev/null +++ b/src/content/models/blog.py @@ -0,0 +1,31 @@ +import logging + +from .base_content import BasePage, ContentPage + + +logger = logging.getLogger(__name__) + + +class BlogIndex(BasePage): + template = "content/blog_index.html" + subpage_types = [ + "content.BlogPost", + ] + is_creatable = False + + def get_template(self, request, *args, **kwargs): + return self.template + + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + context["children"] = self.get_children().live().public().order_by("title") + return context + + +class BlogPost(ContentPage): + template = "content/blog_post.html" + subpage_types = [] + is_creatable = True + + def get_template(self, request, *args, **kwargs): + return self.template diff --git a/src/content/models/events.py b/src/content/models/events.py new file mode 100644 index 000000000..bafc07f7f --- /dev/null +++ b/src/content/models/events.py @@ -0,0 +1,214 @@ +from datetime import datetime, timedelta + +from dateutil.relativedelta import relativedelta +from django.db import models +from django.http import Http404 +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.utils import timezone +from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel +from wagtail.contrib.routable_page.models import RoutablePageMixin, route + +from .base_content import BasePage, ContentPage +from core.models import fields +from events import types +from events.utils import get_event_datetime_display_string + + +# class EventsHome(RoutablePageMixin, BasePage): +# template = "events/events_home.html" +# show_in_menus = True +# is_creatable = False +# subpage_types = ["content.EventPage"] + +# def get_template(self, request, *args, **kwargs): +# return self.template + +# @route(r"^(?P\d{4})/$", name="month_year") +# def year_events(self, request, year): +# return redirect( +# self.full_url + self.reverse_subpage("month_events", args=(year, 1)) +# ) + +# @route(r"^(?P\d{4})/(?P\d{1,2})/$", name="month_events") +# def month_events(self, request, year, month): +# year = int(year) +# month = int(month) + +# if month < 1 or month > 12: +# raise Http404 + +# filter_date = datetime(int(year), int(month), 1) + +# return TemplateResponse( +# request, +# self.get_template(request), +# self.get_context(request, filter_date=filter_date), +# ) + +# def get_context(self, request, *args, **kwargs): +# from events.filters import EventsFilters + +# context = super().get_context(request, *args, **kwargs) + +# filter_date = kwargs.get("filter_date", timezone.now()).date() + +# month_start = filter_date.replace(day=1) + +# previous_month = month_start - relativedelta(months=1) +# next_month = month_start + relativedelta(months=1) + +# month_end = month_start.replace(month=next_month.month) + +# events = ( +# EventPage.objects.live() +# .public() +# .filter( +# event_start__gte=month_start, +# event_start__lt=month_end, +# ) +# .order_by("event_start") +# ) + +# current_month_start = timezone.now().date().replace(day=1) + +# page_title_prefix = "What's on in" +# if filter_date < current_month_start: +# page_title_prefix = "What happened in" + +# # Filtering events +# events_filters = EventsFilters(request.GET, queryset=events) +# events = events_filters.qs + +# context.update( +# events_filters=events_filters, +# page_title=f"{page_title_prefix} {month_start.strftime('%B %Y')}", +# upcoming_events=events.filter( +# event_start__gte=timezone.now().date(), +# ), +# past_events=events.filter( +# event_start__lt=timezone.now().date(), +# ), +# current_month=month_start, +# next_month=next_month, +# previous_month=previous_month, +# ) +# return context + + +# class EventPage(ContentPage): +# is_creatable = True +# parent_page_types = ["content.EventsHome"] +# template = "events/event_page.html" + +# event_start = models.DateTimeField( +# help_text="Start date/time of the event.", +# ) +# event_end = models.DateTimeField( +# help_text="End date/time of the event.", +# ) +# online_event_url = fields.URLField( +# blank=True, +# null=True, +# verbose_name="Online event link", +# help_text="If the event is online, you can add a link here for others to join.", +# ) +# offline_event_url = fields.URLField( +# blank=True, +# null=True, +# verbose_name="In person registration link", +# help_text="If the event is in person, you can add a link here for registration.", +# ) +# submit_questions_url = fields.URLField( +# blank=True, +# null=True, +# verbose_name="Submit questions link", +# help_text="Link to a page for others to submit their questions.", +# ) +# event_recording_url = fields.URLField( +# blank=True, +# null=True, +# verbose_name="View event recording link", +# help_text="Optional link to a page for others to view the recorded event.", +# ) +# event_type = models.CharField( +# choices=types.EventType.choices, +# default=types.EventType.ONLINE, +# ) +# audience = models.CharField( +# choices=types.EventAudience.choices, +# blank=True, +# null=True, +# ) +# location = models.ForeignKey( +# "peoplefinder.UkStaffLocation", +# on_delete=models.SET_NULL, +# blank=True, +# null=True, +# help_text="If you don't select a location the page will show 'Location: To be confirmed'", +# ) +# room = models.CharField( +# blank=True, +# null=True, +# ) +# room_capacity = models.IntegerField( +# blank=True, +# null=True, +# ) + +# content_panels = ContentPage.content_panels + [ +# MultiFieldPanel( +# [ +# FieldRowPanel( +# [ +# FieldPanel("event_start"), +# FieldPanel("event_end"), +# ] +# ), +# ], +# heading="Date/Time details", +# ), +# FieldPanel("event_type"), +# MultiFieldPanel( +# [ +# FieldPanel("location"), +# FieldRowPanel( +# [ +# FieldPanel("room"), +# FieldPanel("room_capacity"), +# ] +# ), +# FieldPanel("offline_event_url"), +# ], +# heading="In person details", +# ), +# MultiFieldPanel( +# [ +# FieldPanel("online_event_url"), +# ], +# heading="Online details", +# ), +# FieldPanel("audience"), +# FieldPanel("submit_questions_url"), +# FieldPanel("event_recording_url"), +# ] + +# def get_template(self, request, *args, **kwargs): +# return self.template + +# def get_context(self, request, *args, **kwargs): +# context = super().get_context(request, *args, **kwargs) + +# context.update( +# is_online=self.event_type == types.EventType.ONLINE, +# is_in_person=self.event_type == types.EventType.IN_PERSON, +# is_hybrid=self.event_type == types.EventType.HYBRID, +# event_date_range=get_event_datetime_display_string(self), +# ) + +# return context + +# @property +# def is_past_event(self) -> bool: +# adjusted_datetime = self.event_end + timedelta(hours=1) +# return timezone.now() > adjusted_datetime diff --git a/src/content/models/navigation.py b/src/content/models/navigation.py new file mode 100644 index 000000000..258c46f7a --- /dev/null +++ b/src/content/models/navigation.py @@ -0,0 +1,53 @@ +from wagtail.admin.panels import FieldPanel +from wagtail.fields import StreamField + +import dw_design_system.dwds.components as dwds_blocks +from content.models import BasePage +from .search import SearchFieldsMixin +from extended_search.index import DWIndexedField as IndexedField + + +class NavigationPage(SearchFieldsMixin, BasePage): + template = "content/navigation_page.html" + + search_stream_fields: list[str] = ["primary_elements", "secondary_elements"] + + primary_elements = StreamField( + [ + ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), + ], + blank=True, + ) + + secondary_elements = StreamField( + [ + ("dw_curated_page_links", dwds_blocks.CustomPageLinkListBlock()), + ("dw_tagged_page_list", dwds_blocks.TaggedPageListBlock()), + ("dw_cta", dwds_blocks.CTACardBlock()), + ("dw_engagement_card", dwds_blocks.EngagementCardBlock()), + ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), + ], + blank=True, + ) + + content_panels = BasePage.content_panels + [ + FieldPanel("primary_elements"), + FieldPanel("secondary_elements"), + ] + + indexed_fields = SearchFieldsMixin.indexed_fields + [ + IndexedField("primary_elements"), + IndexedField("secondary_elements"), + ] + + def get_template(self, request, *args, **kwargs): + return self.template + + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + + return context + + def full_clean(self, *args, **kwargs): + self._generate_search_field_content() + super().full_clean(*args, **kwargs) diff --git a/src/content/models/search.py b/src/content/models/search.py new file mode 100644 index 000000000..990da0b97 --- /dev/null +++ b/src/content/models/search.py @@ -0,0 +1,126 @@ +import logging + +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models +from simple_history.models import HistoricalRecords + + +from content.utils import get_search_content_for_block +from extended_search.index import DWIndexedField as IndexedField +from search.utils import split_query + + +logger = logging.getLogger(__name__) + + +class SearchFieldsMixin(models.Model): + """ + Specific fields and settings to manage search. Extra fields are generally + defined to make custom and specific indexing as defined in /docs/search.md + + Usage: + - Add this mixin to a model + - Define the fields that should be indexed in search_stream_fields. + Example: ["body"] + - Add SearchFieldsMixin.indexed_fields to the Model's indexed_fields list + - Run self._generate_search_field_content() in full_clean() to generate + search fields + """ + + class Meta: + abstract = True + + search_stream_fields: list[str] = [] + + search_title = models.CharField( + max_length=255, + ) + search_headings = models.TextField( + blank=True, + null=True, + ) + search_content = models.TextField( + blank=True, + null=True, + ) + + def _generate_search_field_content(self): + self.search_title = self.title + search_headings = [] + search_content = [] + + for stream_field_name in self.search_stream_fields: + stream_field = getattr(self, stream_field_name) + for stream_child in stream_field: + block_search_headings, block_search_content = ( + get_search_content_for_block(stream_child.block, stream_child.value) + ) + search_headings += block_search_headings + search_content += block_search_content + + self.search_headings = " ".join(search_headings) + self.search_content = " ".join(search_content) + + indexed_fields = [ + IndexedField( + "search_title", + tokenized=True, + explicit=True, + fuzzy=True, + boost=5.0, + ), + IndexedField( + "search_headings", + tokenized=True, + explicit=True, + fuzzy=True, + boost=3.0, + ), + IndexedField( + "search_content", + tokenized=True, + explicit=True, + ), + ] + + +class SearchKeywordOrPhrase(models.Model): + keyword_or_phrase = models.CharField(max_length=1000) + # TODO: Remove historical records. + history = HistoricalRecords() + + +class SearchKeywordOrPhraseQuerySet(models.QuerySet): + def filter_by_query(self, query): + query_parts = split_query(query) + + return self.filter(search_keyword_or_phrase__keyword_or_phrase__in=query_parts) + + +class SearchExclusionPageLookUp(models.Model): + objects = SearchKeywordOrPhraseQuerySet.as_manager() + + search_keyword_or_phrase = models.ForeignKey( + SearchKeywordOrPhrase, + on_delete=models.CASCADE, + ) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + # TODO: Remove historical records. + history = HistoricalRecords() + + +class SearchPinPageLookUp(models.Model): + objects = SearchKeywordOrPhraseQuerySet.as_manager() + + search_keyword_or_phrase = models.ForeignKey( + SearchKeywordOrPhrase, + on_delete=models.CASCADE, + ) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + # TODO: Remove historical records. + history = HistoricalRecords() diff --git a/src/content/oldmodels.py b/src/content/oldmodels.py new file mode 100644 index 000000000..cb86b05f8 --- /dev/null +++ b/src/content/oldmodels.py @@ -0,0 +1,632 @@ +# import html +# import logging +# from typing import Optional + +# from django.contrib.auth import get_user_model +# from django.contrib.contenttypes.fields import GenericForeignKey +# from django.contrib.contenttypes.models import ContentType +# from django.db import models +# from django.db.models import Q, Subquery +# from django.forms import widgets +# from django.utils import timezone +# from django.utils.html import strip_tags +# from simple_history.models import HistoricalRecords +# from wagtail.admin.panels import ( +# FieldPanel, +# InlinePanel, +# ObjectList, +# TabbedInterface, +# TitleFieldPanel, +# ) +# from wagtail.admin.widgets.slug import SlugInput +# from wagtail.fields import StreamField +# from wagtail.models import Page, PageManager, PageQuerySet +# from wagtail.snippets.models import register_snippet +# from wagtail.utils.decorators import cached_classmethod + +# import dw_design_system.dwds.components as dwds_blocks +# from content import blocks as content_blocks +# from content.utils import ( +# get_search_content_for_block, +# manage_excluded, +# manage_pinned, +# truncate_words_and_chars, +# ) +# from content.validators import validate_description_word_count +# from extended_search.index import DWIndexedField as IndexedField +# from extended_search.index import Indexed, RelatedFields +# from peoplefinder.widgets import PersonChooser +# from search.utils import split_query +# from user.models import User as UserModel + + +# logger = logging.getLogger(__name__) + +# User = get_user_model() + +# RICH_TEXT_FEATURES = [ +# "ol", +# "ul", +# "link", +# "document-link", +# "anchor-identifier", +# ] + + +# def strip_tags_with_spaces(string): +# spaced = string.replace("><", "> <") +# return strip_tags(spaced) + + +# @register_snippet +# class Theme(models.Model): +# title = models.CharField(max_length=255) +# slug = models.SlugField(max_length=255) +# summary = models.CharField(max_length=255) +# history = HistoricalRecords() + +# def __str__(self): +# return self.title + +# panels = [ +# FieldPanel("title"), +# FieldPanel("summary"), +# ] + +# class Meta: +# ordering = ["-title"] + + +# class BasePageQuerySet(PageQuerySet): +# def restricted_q(self, restriction_type): +# from wagtail.models import BaseViewRestriction, PageViewRestriction + +# if isinstance(restriction_type, str): +# restriction_type = [ +# restriction_type, +# ] + +# RESTRICTION_CHOICES = BaseViewRestriction.RESTRICTION_CHOICES +# types = [t for t, _ in RESTRICTION_CHOICES if t in restriction_type] + +# q = Q() +# for restriction in ( +# PageViewRestriction.objects.filter(restriction_type__in=types) +# .select_related("page") +# .all() +# ): +# q |= self.descendant_of_q(restriction.page, inclusive=True) + +# return q if q else Q(pk__in=[]) + +# def public_or_login(self): +# return self.exclude(self.restricted_q(["password", "groups"])) + +# def pinned_q(self, query): +# pinned = SearchPinPageLookUp.objects.filter_by_query(query) + +# return Q(pk__in=Subquery(pinned.values("object_id"))) + +# def pinned(self, query): +# return self.filter(self.pinned_q(query)) + +# def not_pinned(self, query): +# return self.exclude(self.pinned_q(query)) + +# def exclusions_q(self, query): +# exclusions = SearchExclusionPageLookUp.objects.filter_by_query(query) + +# return Q(pk__in=Subquery(exclusions.values("object_id"))) + +# def exclusions(self, query): +# return self.filter(self.exclusions_q(query)) + +# def annotate_with_total_views(self): +# return self.annotate( +# total_views=models.Sum( +# "interactions_recentpageviews__count", +# distinct=True, +# ) +# ) + +# def annotate_with_unique_views_all_time(self): +# return self.annotate( +# unique_views_all_time=models.Count( +# "interactions_recentpageviews", +# distinct=True, +# ) +# ) + +# def annotate_with_unique_views_past_month(self): +# return self.annotate( +# unique_views_past_month=models.Count( +# "interactions_recentpageviews", +# filter=Q( +# interactions_recentpageviews__updated_at__gte=timezone.now() +# - timezone.timedelta(weeks=4) +# ), +# distinct=True, +# ) +# ) + +# def order_by_most_recent_unique_views_past_month(self): +# return self.annotate_with_unique_views_past_month().order_by( +# "-unique_views_past_month" +# ) + + +# class BasePage(Page, Indexed): +# class Meta: +# permissions = [ +# ("view_info_page", "Can view the info page in the Wagtail admin"), +# ] + +# objects = PageManager.from_queryset(BasePageQuerySet)() + +# legacy_path = models.CharField( +# max_length=500, +# blank=True, +# null=True, +# ) + +# promote_panels = [] +# content_panels = [ +# TitleFieldPanel("title"), +# ] + +# @property +# def published_date(self): +# return self.last_published_at + +# def get_first_publisher(self) -> Optional[UserModel]: +# """Return the first publisher of the page or None.""" +# first_revision_with_user = ( +# self.revisions.exclude(user=None).order_by("created_at", "id").first() +# ) + +# if first_revision_with_user: +# return first_revision_with_user.user +# else: +# return None + +# @property +# def days_since_last_published(self): +# if self.last_published_at: +# result = timezone.now() - self.last_published_at +# return result.days +# return None + + +# class ContentPageQuerySet(BasePageQuerySet): +# def annotate_with_comment_count(self): +# return self.annotate(comment_count=models.Count("comments")) + + +# class ContentOwnerMixin(models.Model): +# content_owner = models.ForeignKey( +# "peoplefinder.Person", +# on_delete=models.SET_NULL, +# null=True, +# blank=False, +# ) +# content_contact_email = models.EmailField( +# help_text="Contact email shown on article, this could be the content owner or a team inbox", +# null=True, +# blank=False, +# ) + +# content_owner_panels = [ +# FieldPanel("content_owner", widget=PersonChooser), +# FieldPanel("content_contact_email"), +# ] + +# # This should be imported in the model that uses this mixin, e.g: Network.indexed_fields = [ ... ] + ContentOwnerMixin.indexed_fields +# indexed_fields = [ +# RelatedFields( +# "content_owner", +# [ +# IndexedField("first_name", explicit=True), +# IndexedField("preferred_first_name", explicit=True), +# IndexedField("last_name", explicit=True), +# ], +# ), +# IndexedField("content_contact_email", explicit=True), +# ] + +# @cached_classmethod +# def get_edit_handler(cls): +# return TabbedInterface( +# [ +# ObjectList(cls.content_panels, heading="Content"), +# ObjectList(cls.promote_panels, heading="Promote"), +# ObjectList(cls.content_owner_panels, heading="Content owner"), +# ] +# ).bind_to_model(cls) + +# class Meta: +# abstract = True + +# @classmethod +# def get_all_subclasses(cls): +# subclasses = [] +# direct_subclasses = cls.__subclasses__() + +# for direct_subclass in direct_subclasses: +# subclasses.append(direct_subclass) +# subclasses.extend(direct_subclass.get_all_subclasses()) + +# return subclasses + + +# class SearchFieldsMixin(models.Model): +# """ +# Specific fields and settings to manage search. Extra fields are generally +# defined to make custom and specific indexing as defined in /docs/search.md + +# Usage: +# - Add this mixin to a model +# - Define the fields that should be indexed in search_stream_fields. +# Example: ["body"] +# - Add SearchFieldsMixin.indexed_fields to the Model's indexed_fields list +# - Run self._generate_search_field_content() in full_clean() to generate +# search fields +# """ + +# class Meta: +# abstract = True + +# search_stream_fields: list[str] = [] + +# search_title = models.CharField( +# max_length=255, +# ) +# search_headings = models.TextField( +# blank=True, +# null=True, +# ) +# search_content = models.TextField( +# blank=True, +# null=True, +# ) + +# def _generate_search_field_content(self): +# self.search_title = self.title +# search_headings = [] +# search_content = [] + +# for stream_field_name in self.search_stream_fields: +# stream_field = getattr(self, stream_field_name) +# for stream_child in stream_field: +# block_search_headings, block_search_content = ( +# get_search_content_for_block(stream_child.block, stream_child.value) +# ) +# search_headings += block_search_headings +# search_content += block_search_content + +# self.search_headings = " ".join(search_headings) +# self.search_content = " ".join(search_content) + +# indexed_fields = [ +# IndexedField( +# "search_title", +# tokenized=True, +# explicit=True, +# fuzzy=True, +# boost=5.0, +# ), +# IndexedField( +# "search_headings", +# tokenized=True, +# explicit=True, +# fuzzy=True, +# boost=3.0, +# ), +# IndexedField( +# "search_content", +# tokenized=True, +# explicit=True, +# ), +# ] + + +# class ContentPage(SearchFieldsMixin, BasePage): +# objects = PageManager.from_queryset(ContentPageQuerySet)() + +# is_creatable = False +# show_in_menus = True +# search_stream_fields = ["body"] + +# legacy_guid = models.CharField( +# blank=True, null=True, max_length=255, help_text="""Wordpress GUID""" +# ) + +# legacy_content = models.TextField( +# blank=True, null=True, help_text="""Legacy content, pre-conversion""" +# ) + +# description = models.TextField( +# blank=True, +# null=True, +# help_text="Please use this field only to add a description for an event.", +# validators=[validate_description_word_count], +# ) + +# preview_image = models.ForeignKey( +# "wagtailimages.Image", +# null=True, +# blank=True, +# on_delete=models.SET_NULL, +# related_name="+", +# ) + +# body = StreamField( +# [ +# ("heading2", content_blocks.Heading2Block()), +# ("heading3", content_blocks.Heading3Block()), +# ("heading4", content_blocks.Heading4Block()), +# ("heading5", content_blocks.Heading5Block()), +# ( +# "text_section", +# content_blocks.TextBlock( +# blank=True, +# features=RICH_TEXT_FEATURES, +# help_text="""Some text to describe what this section is about (will be +# displayed above the list of child pages)""", +# ), +# ), +# ("image", content_blocks.ImageBlock()), +# ( +# "embed_video", +# content_blocks.EmbedVideoBlock(help_text="""Embed a video"""), +# ), +# ( +# "media", +# content_blocks.InternalMediaBlock( +# help_text="""Link to a media block""" +# ), +# ), +# ( +# "data_table", +# content_blocks.DataTableBlock( +# help_text="""ONLY USE THIS FOR TABLULAR DATA, NOT FOR FORMATTING""" +# ), +# ), +# ], +# use_json_field=True, +# ) + +# excerpt = models.CharField( +# max_length=700, +# blank=True, +# null=True, +# help_text=( +# "A summary of the page to be shown in search results. (making this" +# " field empty will result in an autogenerated excerpt)" +# ), +# ) + +# pinned_phrases = models.CharField( +# blank=True, +# null=True, +# max_length=1000, +# help_text="A comma separated list of pinned keywords and phrases. " +# "Do not use quotes for phrases. The page will be pinned " +# "to the first page of search results for these terms.", +# ) + +# excluded_phrases = models.CharField( +# blank=True, +# null=True, +# max_length=1000, +# help_text="A comma separated list of excluded keywords and phrases. " +# "Do not use quotes for phrases. The page will be removed " +# "from search results for these terms", +# ) + +# # +# # Topics +# # This would ideally belong on PageWithTopics, but the Network model uses it +# # and it's not worth the effort to refactor it. +# # + +# @property +# def topics(self): +# from working_at_dit.models import Topic + +# # This needs to be a list comprehension to work nicely with modelcluster. +# topic_ids = [page_topic.topic.pk for page_topic in self.page_topics.all()] +# return Topic.objects.filter(pk__in=topic_ids) + +# @property +# def topic_titles(self): +# return [topic.title for topic in self.topics] + +# # +# # Search +# # Specific fields and settings to manage search. Extra fields are generally +# # defined to make custom and specific indexing as defined in /docs/search.md +# # + +# indexed_fields = SearchFieldsMixin.indexed_fields + [ +# IndexedField( +# "excerpt", +# tokenized=True, +# explicit=True, +# boost=2.0, +# ), +# IndexedField("is_creatable", filter=True), +# IndexedField( +# "description", +# tokenized=True, +# explicit=True, +# ), +# ] + +# # +# # Wagtail admin configuration +# # + +# subpage_types = [] + +# content_panels = BasePage.content_panels + [ +# FieldPanel("description"), +# FieldPanel("body"), +# FieldPanel("excerpt", widget=widgets.Textarea), +# FieldPanel("preview_image"), +# InlinePanel("tagged_items", label="Tags"), +# ] + +# promote_panels = [ +# FieldPanel("slug", widget=SlugInput), +# FieldPanel("show_in_menus"), +# FieldPanel("pinned_phrases"), +# FieldPanel("excluded_phrases"), +# ] + +# def get_context(self, request, *args, **kwargs): +# context = super().get_context(request, *args, **kwargs) + +# tag_set = [] +# # TODO: Enable when we want to show tags to users. +# # for tagged_item in self.tagged_items.select_related("tag").all(): +# # tag_set.append(tagged_item.tag) +# context["tag_set"] = tag_set + +# return context + +# def full_clean(self, *args, **kwargs): +# self._generate_search_field_content() +# if self.excerpt is None: +# self._generate_excerpt() + +# super().full_clean(*args, **kwargs) + +# def _generate_excerpt(self): +# content = "".join( +# [str(b.value) for b in self.body if b.block_type == "text_section"] +# ) +# self.excerpt = truncate_words_and_chars( +# html.unescape(strip_tags_with_spaces(content)), 40, 700 +# ) + +# def save(self, *args, **kwargs): +# if self.excerpt is None: +# self._generate_excerpt() + +# if self.id: +# manage_excluded(self, self.excluded_phrases) +# manage_pinned(self, self.pinned_phrases) + +# return super().save(*args, **kwargs) + + +# class SearchKeywordOrPhrase(models.Model): +# keyword_or_phrase = models.CharField(max_length=1000) +# # TODO: Remove historical records. +# history = HistoricalRecords() + + +# class SearchKeywordOrPhraseQuerySet(models.QuerySet): +# def filter_by_query(self, query): +# query_parts = split_query(query) + +# return self.filter(search_keyword_or_phrase__keyword_or_phrase__in=query_parts) + + +# class NavigationPage(SearchFieldsMixin, BasePage): +# template = "content/navigation_page.html" + +# search_stream_fields: list[str] = ["primary_elements", "secondary_elements"] + +# primary_elements = StreamField( +# [ +# ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), +# ], +# blank=True, +# ) + +# secondary_elements = StreamField( +# [ +# ("dw_curated_page_links", dwds_blocks.CustomPageLinkListBlock()), +# ("dw_tagged_page_list", dwds_blocks.TaggedPageListBlock()), +# ("dw_cta", dwds_blocks.CTACardBlock()), +# ("dw_engagement_card", dwds_blocks.EngagementCardBlock()), +# ("dw_navigation_card", dwds_blocks.NavigationCardBlock()), +# ], +# blank=True, +# ) + +# content_panels = BasePage.content_panels + [ +# FieldPanel("primary_elements"), +# FieldPanel("secondary_elements"), +# ] + +# indexed_fields = SearchFieldsMixin.indexed_fields + [ +# IndexedField("primary_elements"), +# IndexedField("secondary_elements"), +# ] + +# def get_template(self, request, *args, **kwargs): +# return self.template + +# def get_context(self, request, *args, **kwargs): +# context = super().get_context(request, *args, **kwargs) + +# return context + +# def full_clean(self, *args, **kwargs): +# self._generate_search_field_content() +# super().full_clean(*args, **kwargs) + + +# class SearchExclusionPageLookUp(models.Model): +# objects = SearchKeywordOrPhraseQuerySet.as_manager() + +# search_keyword_or_phrase = models.ForeignKey( +# SearchKeywordOrPhrase, +# on_delete=models.CASCADE, +# ) +# content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) +# object_id = models.PositiveIntegerField() +# content_object = GenericForeignKey("content_type", "object_id") +# # TODO: Remove historical records. +# history = HistoricalRecords() + + +# class SearchPinPageLookUp(models.Model): +# objects = SearchKeywordOrPhraseQuerySet.as_manager() + +# search_keyword_or_phrase = models.ForeignKey( +# SearchKeywordOrPhrase, +# on_delete=models.CASCADE, +# ) +# content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) +# object_id = models.PositiveIntegerField() +# content_object = GenericForeignKey("content_type", "object_id") +# # TODO: Remove historical records. +# history = HistoricalRecords() + + +# class BlogIndex(BasePage): +# template = "content/blog_index.html" +# subpage_types = [ +# "content.BlogPost", +# ] +# is_creatable = False + +# def get_template(self, request, *args, **kwargs): +# return self.template + +# def get_context(self, request, *args, **kwargs): +# context = super().get_context(request, *args, **kwargs) +# context["children"] = self.get_children().live().public().order_by("title") +# return context + + +# class BlogPost(ContentPage): +# template = "content/blog_post.html" +# subpage_types = [] +# is_creatable = True + +# def get_template(self, request, *args, **kwargs): +# return self.template diff --git a/src/core/tasks.py b/src/core/tasks.py index 823c4768f..75de83517 100644 --- a/src/core/tasks.py +++ b/src/core/tasks.py @@ -5,6 +5,7 @@ from peoplefinder.services.uk_staff_locations import UkStaffLocationService + @celery_app.task(bind=True) def debug_task(self): print("Request: {0!r}".format(self.request)) # noqa diff --git a/src/dw_design_system/templatetags/dw_design_system.py b/src/dw_design_system/templatetags/dw_design_system.py index db766a5a8..5727418b9 100644 --- a/src/dw_design_system/templatetags/dw_design_system.py +++ b/src/dw_design_system/templatetags/dw_design_system.py @@ -7,4 +7,5 @@ @register.simple_tag(takes_context=True) def render_component(context, template_name, component_context): component_template: template.Template = template.loader.get_template(template_name) + component_context.update(request=context["request"]) return component_template.render(context=component_context) diff --git a/src/events/migrations/0004_remove_eventpage_end_time_and_more.py b/src/events/migrations/0004_remove_eventpage_end_time_and_more.py index 5e118363a..a32135d71 100644 --- a/src/events/migrations/0004_remove_eventpage_end_time_and_more.py +++ b/src/events/migrations/0004_remove_eventpage_end_time_and_more.py @@ -92,4 +92,4 @@ class Migration(migrations.Migration): model_name="eventpage", name="end_time", ), - ] + ] \ No newline at end of file diff --git a/src/events/models.py b/src/events/models.py index 6359768c3..c98319800 100644 --- a/src/events/models.py +++ b/src/events/models.py @@ -134,7 +134,7 @@ class EventPage(ContentPage): event_type = models.CharField( choices=types.EventType.choices, default=types.EventType.ONLINE, - ) + ) audience = models.CharField( choices=types.EventAudience.choices, blank=True,