diff --git a/archive/models.py b/archive/models.py index 0630733e2..651a6dda3 100644 --- a/archive/models.py +++ b/archive/models.py @@ -195,9 +195,9 @@ def get_paginated_articles(self, context, objects, video_section, request): def get_order_objects(self, order, objects, videos_section): if videos_section == False: if order == 'oldest': - article_order = "explicit_published_at" + article_order = "first_published_at" else: - article_order = "-explicit_published_at" + article_order = "-first_published_at" return objects.order_by(article_order) else: @@ -211,7 +211,7 @@ def get_order_objects(self, order, objects, videos_section): def get_year_objects(self, objects, videos_section): if videos_section == False: - return objects.filter(explicit_published_at__year=str(self.year)) + return objects.filter(first_published_at__year=str(self.year)) else: return objects.filter(created_at__gte=str(self.year) + "-01-01", created_at__lte = str(self.year + 1) + "-12-31") diff --git a/archive/templates/archive/objects/archive.html b/archive/templates/archive/objects/archive.html index 50f5c4efa..576674411 100644 --- a/archive/templates/archive/objects/archive.html +++ b/archive/templates/archive/objects/archive.html @@ -42,7 +42,11 @@

{% for object in page_obj %} {% if video_section == False or video_section == None %} - {% include 'archive/objects/article_list.html' with article=object %} + {% if is_orderable %} + {% include 'archive/objects/article_list.html' with article=object.article_page %} + {% else %} + {% include 'archive/objects/article_list.html' with article=object %} + {% endif %} {% else %} {% include 'videos/stream_blocks/video.html' with video=object %} {% endif %} diff --git a/archive/templates/archive/objects/archive_filter.html b/archive/templates/archive/objects/archive_filter.html index 795d59141..87d03237f 100644 --- a/archive/templates/archive/objects/archive_filter.html +++ b/archive/templates/archive/objects/archive_filter.html @@ -112,15 +112,15 @@

Spoofs {% if years %} -
+
- + {% if year %}{{ year }}{% else %}All years{% endif %} - -
-
+ + +

Filter by year:

@@ -141,21 +141,21 @@

Filter by year:

{% endfor %}
-
+
{% endif %} {% if sections %} -
+
- + {% if section_slug %}{{ section_slug }}{% else %}All sections{% endif %} -
-
+ +

Filter by section:

@@ -200,7 +200,7 @@

Filter by section:

{% endfor %}
-
+
{% endif %}
\ No newline at end of file diff --git a/archive/templates/archive/objects/article_list.html b/archive/templates/archive/objects/article_list.html index 4c1c804b8..194ee1b30 100644 --- a/archive/templates/archive/objects/article_list.html +++ b/archive/templates/archive/objects/article_list.html @@ -23,9 +23,9 @@

{{ article.title|safe }}

{% if not hide_snippet %} diff --git a/archive/templates/archive/objects/gallery.html b/archive/templates/archive/objects/gallery.html index 70d252e26..ce7ed41b7 100644 --- a/archive/templates/archive/objects/gallery.html +++ b/archive/templates/archive/objects/gallery.html @@ -133,12 +133,12 @@

{% if page_obj %} {{ page_obj.paginator.count }} RESULTS {% else %}NO RESULTS{% endif %} {% if q %} FOR "{{ q }}"{% endif %}

-
- +
+ {{ order|title }} -
    +
    • Newest
    • diff --git a/article/blocks.py b/article/blocks.py index 34b50738b..e41f235df 100644 --- a/article/blocks.py +++ b/article/blocks.py @@ -7,8 +7,20 @@ class AudioBlock(blocks.StructBlock): caption = blocks.CharBlock(required=False) - audio = DocumentChooserBlock(required=True, help_text="Must be mp3 format") - + audio = DocumentChooserBlock(required=True, help_text="File format: .m4a, .mp4, .mp, .wav, or .ogg") + + def get_context(self, value, parent_context=None): + context = super().get_context(value, parent_context) + if value['audio'].url[-4:] == '.wav': + context['self'].format = 'wav' + if value['audio'].url[-4:] == '.mp3': + context['self'].format = 'mpeg' + if value['audio'].url[-4:] == '.ogg': + context['self'].format = 'ogg' + else: + context['self'].format = 'mp4' + return context + class Meta: template = 'article/stream_blocks/audio.html', icon = "media" @@ -16,7 +28,20 @@ class Meta: class PullQuoteBlock(blocks.StructBlock): content = blocks.CharBlock(required=True) source = blocks.CharBlock(required=False) - audio = DocumentChooserBlock(required=False, help_text="optional, must be mp3 format") + audio = DocumentChooserBlock(required=False, help_text="Optional, file format: .m4a, .mp4, .mp, .wav, or .ogg") + + def get_context(self, value, parent_context=None): + context = super().get_context(value, parent_context) + if value['audio']: + if value['audio'].url[-4:] == '.wav': + context['self'].format = 'wav' + if value['audio'].url[-4:] == '.mp3': + context['self'].format = 'mpeg' + if value['audio'].url[-4:] == '.ogg': + context['self'].format = 'ogg' + else: + context['self'].format = 'mp4' + return context class Meta: template = 'article/stream_blocks/quote.html', diff --git a/article/models.py b/article/models.py index b07c19f8f..3a22966ae 100644 --- a/article/models.py +++ b/article/models.py @@ -7,7 +7,7 @@ from dbtemplates.models import Template as DBTemplate from django.db import models -from django.db.models import fields +from django.db.models import fields, Q from django.db.models.fields import CharField from django.shortcuts import render from django.db.models.query import QuerySet @@ -168,6 +168,7 @@ class ArticleAuthorsOrderable(Orderable): ('illustrator','Illustrator'), ('photographer','Photographer'), ('videographer','Videographer'), + ('designer','Designer'), ('org_role', 'Show organization role'), ], ), @@ -479,7 +480,7 @@ class ArticlePage(RoutablePageMixin, SectionablePage, UbysseyMenuMixin): ('dropcap', blocks.TextBlock( label = "Dropcap Block", template = 'article/stream_blocks/dropcap.html', - help_text = "DO NOT USE - Legacy block. Create a block where special dropcap styling with be applied to the first letter and the first letter only.\n\nThe contents of this block will be enclosed in a

      ...

      element, allowing its targetting for styling.\n\nNo RichText allowed." + help_text = "Create a block where special dropcap styling with be applied to the first letter and the first letter only.\n\nThe contents of this block will be enclosed in a

      ...

      element, allowing its targetting for styling.\n\nNo RichText allowed." )), ('video', video_blocks.OneOffVideoBlock( label = "Credited/Captioned One-Off Video", @@ -545,7 +546,7 @@ class ArticlePage(RoutablePageMixin, SectionablePage, UbysseyMenuMixin): blank=True, default='', max_length=255, - help_text="Enter the slug of the tag to be used for linking at the end of the article. For example, the slug of the tag 'blue chip' is 'blue-chip'.", + help_text="IF USED, SHOULD ALSO BE IN TAGS FIELD. Enter the slug of the tag to be used for linking at the end of the article. For example, the slug of the tag 'blue chip' is 'blue-chip'.", ) tag_page_link = models.BooleanField( null=False, @@ -743,6 +744,8 @@ def get_template(self, request): return "article/article_page_spoof_2024.html" elif self.layout == 'guide-2024': return "article/article_page_guide_2024.html" + elif self.layout == 'science-2024': + return "article/article_page_supplement_2024_science.html" return "article/article_page.html" @@ -873,6 +876,7 @@ def get_template(self, request): ('magazine-2024', 'Magazine (2024 style)'), ('spoof-2024', 'Spoof (2024 style)'), ('guide-2024', 'Guide (2024 style)'), + ('science-2024', 'Science Supplement (2024)'), ], ), ), @@ -1131,51 +1135,89 @@ def get_authors_with_roles(self) -> str: """Returns list of authors as a comma-separated string sorted by author type (with 'and' before last author).""" - string_written = '' - string_photos = '' - string_illustrations = '' - string_videos = '' - string_org = '' - - authors = dict((k, list(v)) for k, v in groupby(self.article_authors.all(), lambda a: a.author_role)) - for author in authors: - if author == 'author': - string_written += 'Words by ' + self.get_authors_string(links=True, authors_list=authors['author']) - if author == 'photographer': - string_photos += 'Photos by ' + self.get_authors_string(links=True, authors_list=authors['photographer']) - if author == 'illustrator': - string_illustrations += 'Illustrations by ' + self.get_authors_string(links=True, authors_list=authors['illustrator']) - if author == 'videographer': - string_videos += 'Videos by ' + self.get_authors_string(links=True, authors_list=authors['videographer']) - if author == 'org_role': - string_org = ",".join( map(lambda a: ' ' + a.author.ubyssey_role + ": " + self.get_authors_string(links=True, authors_list=[a]) , authors['org_role'])) - - - authors_with_roles = filter(lambda a: a != '', [string_written, string_photos, string_illustrations, string_videos, string_org]) - return ', '.join(authors_with_roles) + role_types_words = { + 'author': 'Words by ', + 'photographer': 'Photos by ', + 'illustrator': 'Illustrations by ', + 'videographer': 'Videos by ', + 'designer': 'Design by ', + } + role_types = ['author', 'photographer', 'illustrator', 'videographer', 'designer', 'org_role'] + authors_with_roles = [] + for k, v in groupby(self.article_authors.all(), lambda a: a.author_role): + if k=='org_role': + authors_with_roles.append([k, ",".join( map(lambda a: ' ' + a.author.ubyssey_role + ": " + self.get_authors_string(links=True, authors_list=[a]) , list(v)))]) + else: + authors_with_roles.append([k, role_types_words[k] + self.get_authors_string(links=True, authors_list=list(v))]) + authors_with_roles.sort(key=lambda s: role_types.index(s[0])) + return ', '.join(map(lambda a: a[1], authors_with_roles)) authors_with_roles = property(fget=get_authors_with_roles) - def get_category_articles(self, order='-first_published_at') -> QuerySet: + def get_authors_split_out_visual_bylines(self) -> str: + """Returns list of authors as a comma-separated string + sorted by author type (with 'and' before last author).""" + + role_types_words = { + 'author': 'Words by ', + 'photographer': 'Photos by ', + 'illustrator': 'Illustrations by ', + 'videographer': 'Videos by ', + 'designer': 'Design by ', + } + role_types = ['author', 'photographer', 'illustrator', 'videographer', 'designer', 'org_role'] + writers = [] + visuals = [] + word_authors = [] + visual_authors = [] + for k, v in groupby(self.article_authors.all(), lambda a: a.author_role): + v = list(v) + if k=='org_role' or k=='author': + for author in v: + word_authors.append(author.author) + writers = writers + v + else: + for author in v: + visual_authors.append(author.author) + visuals.append([k, role_types_words[k] + self.get_authors_string(links=True, authors_list=v)]) + visuals.sort(key=lambda s: role_types.index(s[0])) + + visual_only_author = False + for visual_author in visual_authors: + if not visual_author in word_authors: + visual_only_author = True + break + + writers = self.get_authors_string(links=True, authors_list=list(writers)) + + if len(visuals) > 0 and visual_only_author: + visuals = ', ' + ', '.join(map(lambda a: a[1], visuals)) + else: + visuals = '' + + return writers + visuals + authors_split_out_visual_bylines = property(fget=get_authors_split_out_visual_bylines) + + def get_category_articles(self, order='-first_published_at', max=10) -> QuerySet: """ Returns a list of articles within the Article's category """ - category_articles = ArticlePage.objects.live().filter(category=self.category).not_page(self).order_by(order) + category_articles = ArticlePage.objects.live().filter(category=self.category).not_page(self).order_by(order)[:max] return category_articles - def get_section_articles(self, order='-first_published_at') -> QuerySet: + def get_section_articles(self, order='-first_published_at', max=10) -> QuerySet: """ Returns a list of articles within the Article's section """ - section_articles = ArticlePage.objects.live().child_of(self.get_parent()).not_page(self).order_by(order) + section_articles = ArticlePage.objects.live().child_of(self.get_parent()).not_page(self).order_by(order)[:max] return section_articles - def get_articles_by_tag(self, order='-first_published_at') -> QuerySet: + def get_articles_by_tag(self, order='-first_published_at', max=10) -> QuerySet: """ Returns a list of articles with the same tags as the current article """ - articles_by_tag = ArticlePage.objects.live().filter(tags__slug=self.primary_tag_slug).not_page(self).order_by(order) + articles_by_tag = ArticlePage.objects.live().filter(tags__slug=self.primary_tag_slug).not_page(self).order_by(order)[:max] return articles_by_tag def get_suggested(self, number_suggested=3): @@ -1185,7 +1227,7 @@ def get_suggested(self, number_suggested=3): from taggit.models import Tag suggested = {} if self.filter_by_tags: - articles_by_tag = self.get_articles_by_tag() + articles_by_tag = self.get_articles_by_tag(max=number_suggested) if len(articles_by_tag) > 0: tag = Tag.objects.get(slug=self.primary_tag_slug) suggested = {} @@ -1194,7 +1236,7 @@ def get_suggested(self, number_suggested=3): suggested['link'] = "/tag/" + tag.slug if not suggested: if self.category != None: - category_articles = self.get_category_articles() + category_articles = self.get_category_articles(max=number_suggested) if len(category_articles) > 0: suggested = {} suggested['title'] = self.category.title @@ -1202,7 +1244,7 @@ def get_suggested(self, number_suggested=3): suggested['link'] = self.category.section_page.url + "category/" + self.category.slug if not suggested: - section_articles = self.get_section_articles() + section_articles = self.get_section_articles(max=number_suggested) if len(section_articles) > 0: suggested = {} suggested['title'] = self.get_parent().title diff --git a/article/templates/article/article_page_supplement_2024_science.html b/article/templates/article/article_page_supplement_2024_science.html new file mode 100644 index 000000000..b60936850 --- /dev/null +++ b/article/templates/article/article_page_supplement_2024_science.html @@ -0,0 +1,159 @@ +{% extends 'ubyssey/base.html' %} + +{% load static %} +{% load wagtailcore_tags %} +{% load wagtailimages_tags %} +{% load wagtailuserbar %} +{% load ubyssey_ad_filters %} +{% load ubyssey_ad_tags %} +{% load menu_tags %} + +{% block root %}{%endblock%} +{% block darkmode %}{%endblock%} + +{% block stylesheet %} + + + + +{% endblock %} + +{% block head_scripts %} + + +{% endblock %} + +{% block header %} + +{% endblock %} + +{% block pre_main_content %} +{% endblock %} + +{% block content %} +
      + {% block banner_ad %} + + {% for orderable in settings.ads.AdTagSettings.article_header_placements.all %} + {% gpt_define_tag orderable.ad_slot is_mobile %} + {% endfor %} + {% endblock %} + + +
      +
      + {% block banner %} + +
      +

      + {% if self.fw_alternate_title %} + {{self.fw_alternate_title|safe}} + {% else %} + {{ self.title|safe }} + {% endif %} +

      + +
      + {% endblock %} + +
      + {% comment %} {% endcomment %} + {% for block in self.content %} + {% include_block block with id=block.id %} + {% endfor %} + +
      +
      +
      +
      + +
      +
      + +{% endblock %} + +{% block post_main_content %} +{% wagtailuserbar %} +{% endblock %} + + +{% block footer %} +
      +
      + +
      + + +
      +
      +

      + The Ubyssey acknowledges we operate on the traditional, ancestral and stolen territories of the Coast Salish peoples including the xʷməθkʷəy̓əm (Musqueam), Sḵwx̱wú7mesh Úxwumixw (Squamish) and səlilwətaɬ (Tsleil-Waututh) nations. Read More. +

      +
      + +
      +
      + +{% endblock %} \ No newline at end of file diff --git a/article/templates/article/objects/author_card.html b/article/templates/article/objects/author_card.html index 5eb47f5d1..79a7f11e6 100644 --- a/article/templates/article/objects/author_card.html +++ b/article/templates/article/objects/author_card.html @@ -5,7 +5,7 @@
      {% if author.author.image and author.author.display_image %} - {% image author.author.image width-200 format-webp class="author_image" alt=author.author.full_name %} + {% image author.author.image width-200 format-webp loading="lazy" class="author_image" alt=author.author.full_name %} {% endif %}

      @@ -27,6 +27,8 @@ {% if author.author.short_bio_description != "" %}

      {{ author.author.short_bio_description }}

      {% endif %} -
    • See more from {{ author.author.full_name|safe }}
    • +
      \ No newline at end of file diff --git a/article/templates/article/objects/blog_column-latest.html b/article/templates/article/objects/blog_column-latest.html index 62690266d..aa3629d07 100644 --- a/article/templates/article/objects/blog_column-latest.html +++ b/article/templates/article/objects/blog_column-latest.html @@ -23,7 +23,7 @@ {% endif %}