+{% for article in articles %}
+ {% include 'article/objects/author_pinned.html' with article=article %}
+{% endfor %}
+
diff --git a/article/templates/article/objects/top_article.html b/article/templates/article/objects/top_article.html
new file mode 100644
index 000000000..f0a2c75b2
--- /dev/null
+++ b/article/templates/article/objects/top_article.html
@@ -0,0 +1,44 @@
+{% load cache %}
+{% load humanize %}
+{% load wagtailcore_tags %}
+{% load wagtailimages_tags %}
+{% load video_filters %}
+{% load articletags %}
+{% if article %}
+
+
+
+ {% for block in self.content %}
+ {% if block.value.side == "left" %}
+
+ {% if block.block_type == 'image' %}
+ {% if block.value.block.caption or block.value.block.credit %}
+
+ {% if block.value.block.caption %}{{ block.value.block.caption|safe }}{% endif %}
+ {% if block.value.block.credit %}{{ block.value.block.credit }}{% endif %}
+
+ {% endif %}
+ {% image block.value.block.image original as bg %}
+
+ {% else %}
+ {% include_block block.value.block with id=block.id %}
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+
+ {% for block in self.content %}
+ {% if block.block_type == 'gap' or block.block_type == 'switch_view' %}
+ {% include_block block with id=block.id %}
+ {% elif block.value.side == "right" %}
+ {% if block.block_type == 'raw_html' %}
+ {% include_block block.value.block with id=block.id %}
+ {% else %}
+
+ {% include_block block.value.block with id=block.id %}
+
+ {% endif %}
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
\ No newline at end of file
diff --git a/article/templatetags/article_display.py b/article/templatetags/article_display.py
new file mode 100644
index 000000000..ca2e0b931
--- /dev/null
+++ b/article/templatetags/article_display.py
@@ -0,0 +1,22 @@
+from django import template
+
+register = template.Library()
+
+@register.filter
+def modulo(num, val):
+ return num % val
+
+@register.filter
+def indexing_from(lst, start):
+ return lst[start:]
+
+@register.filter
+def split(value, num):
+ result = []
+ i = 0
+ while i < len(value):
+ result.append(value[i:i+num])
+ i = i + num
+ return result
+
+
\ No newline at end of file
diff --git a/article/templatetags/articletags.py b/article/templatetags/articletags.py
new file mode 100644
index 000000000..b08e76464
--- /dev/null
+++ b/article/templatetags/articletags.py
@@ -0,0 +1,32 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.template.loader import render_to_string
+from section.models import SectionPage
+
+register = template.Library()
+
+@register.filter(name='get_label')
+def get_label(value):
+ if value.get_parent().get_specific().label_svg == None:
+ return False
+ else:
+ return value.get_parent().get_specific().label_svg.url
+
+@register.filter(name='get_colour')
+def get_colour(value):
+ pageColour = value.colour
+ if value.use_parent_colour:
+ if value.get_parent() is not None:
+ parent_page = value.get_parent().specific
+ if hasattr(parent_page,'colour'):
+ pageColour = value.colour = parent_page.colour
+
+ return pageColour
+
+@register.filter(name='get_section_link')
+def get_section_link(value):
+ return value.get_parent().url
+
+@register.filter(name='get_section_title')
+def get_section_title(value):
+ return value.get_parent().title
\ No newline at end of file
diff --git a/article/templatetags/visual_essay.py b/article/templatetags/visual_essay.py
new file mode 100644
index 000000000..9b79cf873
--- /dev/null
+++ b/article/templatetags/visual_essay.py
@@ -0,0 +1,10 @@
+from django import template
+
+register = template.Library()
+
+@register.filter(name="get_first_image")
+def get_first_image(blocks):
+ for i in range(len(blocks)):
+ if blocks[i].block_type == "image":
+ return i
+ return None
\ No newline at end of file
diff --git a/authors/migrations/0006_authorpage_links.py b/authors/migrations/0006_authorpage_links.py
new file mode 100644
index 000000000..ff825cf57
--- /dev/null
+++ b/authors/migrations/0006_authorpage_links.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.11 on 2023-05-17 03:58
+
+from django.db import migrations
+import wagtail.core.blocks
+import wagtail.core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0005_alter_authorpage_ubyssey_role'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='authorpage',
+ name='links',
+ field=wagtail.core.fields.StreamField([('url', wagtail.core.blocks.URLBlock(label='Url'))], blank=True),
+ ),
+ ]
diff --git a/authors/migrations/0007_authorpage_linkicons.py b/authors/migrations/0007_authorpage_linkicons.py
new file mode 100644
index 000000000..12bf54c5e
--- /dev/null
+++ b/authors/migrations/0007_authorpage_linkicons.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.11 on 2023-05-30 23:47
+
+from django.db import migrations
+import wagtail.core.blocks
+import wagtail.core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0006_authorpage_links'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='authorpage',
+ name='linkIcons',
+ field=wagtail.core.fields.StreamField([('raw_html', wagtail.core.blocks.RawHTMLBlock())], blank=True),
+ ),
+ ]
diff --git a/authors/migrations/0008_alter_authorpage_linkicons.py b/authors/migrations/0008_alter_authorpage_linkicons.py
new file mode 100644
index 000000000..d15921447
--- /dev/null
+++ b/authors/migrations/0008_alter_authorpage_linkicons.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.11 on 2023-05-31 00:17
+
+from django.db import migrations
+import wagtail.core.blocks
+import wagtail.core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0007_authorpage_linkicons'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='authorpage',
+ name='linkIcons',
+ field=wagtail.core.fields.StreamField([('richtext', wagtail.core.blocks.RichTextBlock())], blank=True),
+ ),
+ ]
diff --git a/authors/migrations/0009_alter_authorpage_linkicons.py b/authors/migrations/0009_alter_authorpage_linkicons.py
new file mode 100644
index 000000000..65c3da7e6
--- /dev/null
+++ b/authors/migrations/0009_alter_authorpage_linkicons.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.11 on 2023-05-31 00:24
+
+from django.db import migrations
+import wagtail.core.blocks
+import wagtail.core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0008_alter_authorpage_linkicons'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='authorpage',
+ name='linkIcons',
+ field=wagtail.core.fields.StreamField([('raw_html', wagtail.core.blocks.RawHTMLBlock())], blank=True),
+ ),
+ ]
diff --git a/authors/migrations/0010_pinnedarticlesorderable.py b/authors/migrations/0010_pinnedarticlesorderable.py
new file mode 100644
index 000000000..80ec0df33
--- /dev/null
+++ b/authors/migrations/0010_pinnedarticlesorderable.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.11 on 2023-05-31 20:34
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('article', '0018_articlepage_noindex'),
+ ('authors', '0009_alter_authorpage_linkicons'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PinnedArticlesOrderable',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
+ ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pinned_articles', to='article.articlepage')),
+ ('author_page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='pinned_articles', to='authors.authorpage')),
+ ],
+ options={
+ 'ordering': ['sort_order'],
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/authors/migrations/0011_authorpage_main_media_type.py b/authors/migrations/0011_authorpage_main_media_type.py
new file mode 100644
index 000000000..f68ca3f9c
--- /dev/null
+++ b/authors/migrations/0011_authorpage_main_media_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.11 on 2023-06-02 02:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0010_pinnedarticlesorderable'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='authorpage',
+ name='main_media_type',
+ field=models.CharField(blank=True, choices=[('stories', 'Stories'), ('photos', 'Photos'), ('videos', 'Videos')], default='stories', max_length=20),
+ ),
+ ]
diff --git a/authors/migrations/0012_alter_authorpage_main_media_type.py b/authors/migrations/0012_alter_authorpage_main_media_type.py
new file mode 100644
index 000000000..f98f7750b
--- /dev/null
+++ b/authors/migrations/0012_alter_authorpage_main_media_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.11 on 2023-06-02 02:35
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0011_authorpage_main_media_type'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='authorpage',
+ name='main_media_type',
+ field=models.CharField(choices=[('stories', 'Stories'), ('photos', 'Photos'), ('videos', 'Videos')], default='stories', max_length=20),
+ ),
+ ]
diff --git a/authors/migrations/0014_merge_20230718_1044.py b/authors/migrations/0014_merge_20230718_1044.py
new file mode 100644
index 000000000..fc3f42692
--- /dev/null
+++ b/authors/migrations/0014_merge_20230718_1044.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.2.11 on 2023-07-18 17:44
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0012_alter_authorpage_main_media_type'),
+ ('authors', '0013_auto_20230704_0459'),
+ ]
+
+ operations = [
+ ]
diff --git a/authors/migrations/0015_alter_authorpage_main_media_type.py b/authors/migrations/0015_alter_authorpage_main_media_type.py
new file mode 100644
index 000000000..d9d8f66c5
--- /dev/null
+++ b/authors/migrations/0015_alter_authorpage_main_media_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.11 on 2023-07-29 06:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0014_merge_20230718_1044'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='authorpage',
+ name='main_media_type',
+ field=models.CharField(choices=[('articles', 'Articles'), ('photos', 'Photos'), ('videos', 'Videos')], default='stories', max_length=20),
+ ),
+ ]
diff --git a/authors/migrations/0016_alter_authorpage_main_media_type.py b/authors/migrations/0016_alter_authorpage_main_media_type.py
new file mode 100644
index 000000000..d92a469f8
--- /dev/null
+++ b/authors/migrations/0016_alter_authorpage_main_media_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.11 on 2023-08-03 07:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authors', '0015_alter_authorpage_main_media_type'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='authorpage',
+ name='main_media_type',
+ field=models.CharField(choices=[('articles', 'Articles'), ('photos', 'Photos'), ('videos', 'Videos')], default='articles', max_length=20),
+ ),
+ ]
diff --git a/authors/models.py b/authors/models.py
index 7eee77b3d..68de1c90b 100644
--- a/authors/models.py
+++ b/authors/models.py
@@ -21,11 +21,15 @@
from wagtail.core import blocks
from wagtail.core.fields import StreamField
-from wagtail.core.models import Page
+from wagtail.core.models import Page, Orderable
from wagtail.search import index
from wagtail.images.edit_handlers import ImageChooserPanel
-
-
+from modelcluster.fields import ParentalKey
+from wagtail.contrib.routable_page.models import RoutablePageMixin, route
+from django.shortcuts import render
+from images.models import UbysseyImage
+from django import forms
+from videos.models import VideoSnippet
class AllAuthorsPage(Page):
subpage_types = [
@@ -39,7 +43,27 @@ class Meta:
verbose_name = "Author Management"
verbose_name_plural = "Author Management Pages"
-class AuthorPage(Page):
+class PinnedArticlesOrderable(Orderable):
+ author_page = ParentalKey(
+ "authors.AuthorPage",
+ related_name="pinned_articles",
+ )
+ article = models.ForeignKey(
+ 'article.ArticlePage',
+ on_delete=models.CASCADE,
+ related_name="pinned_articles",
+ )
+
+ panels = [
+ MultiFieldPanel(
+ [
+ PageChooserPanel('article'),
+ ],
+ heading="Article"
+ ),
+ ]
+
+class AuthorPage(RoutablePageMixin, Page):
template = "authors/author_page.html"
@@ -103,6 +127,18 @@ class AuthorPage(Page):
)
+
+ CHOICES = [("articles", "Articles"), ("photos", "Photos"), ("videos", "Videos")]
+ main_media_type = models.CharField(
+ choices=CHOICES,
+ default='articles',
+ max_length=20,
+ blank=False,
+ null=False,)
+
+ linkIcons = StreamField([('raw_html', blocks.RawHTMLBlock()),], blank=True)
+ links = StreamField([('url', blocks.URLBlock(label="Url")),], blank=True)
+
# For editting in wagtail:
content_panels = [
# title not present, title should NOT be directly editable
@@ -119,8 +155,9 @@ class AuthorPage(Page):
FieldPanel("ubyssey_role"),
StreamFieldPanel("bio_description"),
StreamFieldPanel("short_bio_description"),
- FieldPanel("facebook_url"),
- FieldPanel("twitter_url"),
+ FieldPanel("main_media_type"),
+ StreamFieldPanel("links"),
+ InlinePanel("pinned_articles", label="Pinned articles")
],
heading="Optional Stuff",
),
@@ -132,27 +169,41 @@ class AuthorPage(Page):
index.SearchField('description'),
]
- def get_context(self, request, *args, **kwargs):
- context = super().get_context(request, *args, **kwargs)
-
+ def organize_media(self, media_type, request, context):
search_query = request.GET.get("q")
page = request.GET.get("page")
order = request.GET.get("order")
- if order == 'oldest':
- article_order = "explicit_published_at"
- else:
- article_order = "-explicit_published_at"
- context["order"] = order
+ if media_type == "articles":
+ if order == 'oldest':
+ article_order = "explicit_published_at"
+ else:
+ article_order = "-explicit_published_at"
+ authors_media = ArticlePage.objects.live().public().filter(article_authors__author=self).order_by(article_order)
+ elif media_type == "photos":
+ if order == 'oldest':
+ article_order = "updated_at"
+ else:
+ article_order = "-updated_at"
+ authors_media = UbysseyImage.objects.filter(author=self).order_by(article_order)
+ elif media_type == "videos":
+ if order == 'oldest':
+ article_order = "updated_at"
+ else:
+ article_order = "-updated_at"
+ authors_media = VideoSnippet.objects.filter(video_authors__author=self).order_by(article_order)
- # Hit the db
- authors_articles = ArticlePage.objects.live().public().filter(article_authors__author=self).order_by(article_order)
if search_query:
- context["search_query"] = search_query
- authors_articles = authors_articles.search(search_query)
+ if media_type == "videos":
+ #from wagtail.search.backends import get_search_backend
+ #s = get_search_backend()
+ #authors_media = s.search(search_query, authors_media)
+ authors_media = authors_media.filter(title=search_query)
+ else:
+ authors_media = authors_media.search(search_query)
# Paginate all posts by 15 per page
- paginator = Paginator(authors_articles, per_page=15)
+ paginator = Paginator(authors_media, per_page=15)
try:
# If the page exists and the ?page=x is an int
paginated_articles = paginator.page(page)
@@ -160,16 +211,83 @@ def get_context(self, request, *args, **kwargs):
except PageNotAnInteger:
# If the ?page=x is not an int; show the first page
paginated_articles = paginator.page(1)
+ context["current_page"] = 1
except EmptyPage:
# If the ?page=x is out of range (too high most likely)
# Then return the last page
paginated_articles = paginator.page(paginator.num_pages)
+ context["current_page"] = paginator.num_pages
+
+ context["paginated_articles"] = paginated_articles
+
+ return context
+
+ def get_context(self, request, *args, **kwargs):
+ context = super().get_context(request, *args, **kwargs)
+
+ media_types = []
+ if VideoSnippet.objects.all().count() > 0:
+ media_types.append("videos")
+ if UbysseyImage.objects.all().count() > 0:
+ media_types.append("photos")
+ if ArticlePage.objects.live().public().all().count() > 0:
+ media_types.append("articles")
+
+ context["media_types"] = media_types
+ context["media_type"] = self.main_media_type
- context["paginated_articles"] = paginated_articles #this object is often called page_obj in Django docs, but Page means something else in Wagtail
+ context = self.organize_media(self.main_media_type, request, context)
return context
-
+ def save(self, *args, **kwargs):
+ import requests
+ from urllib.parse import urlparse
+ from django.utils.safestring import mark_safe
+
+ domainToIcon = {'www.tumblr.com': 'fa-tumblr',
+ 'www.instagram.com': 'fa-instagram',
+ 'twitter.com': 'fa-twitter',
+ 'www.facebook.com': 'fa-facebook',
+ 'www.youtube.com': 'fa-youtube-play',
+ 'www.tiktok.com': 'fa-tiktok',
+ 'www.linkedin.com': 'fa-linkedin',
+ 'www.reddit.com': 'fa-reddit'}
+
+ for i in range(len(self.linkIcons)):
+ del self.linkIcons[-1]
+
+ for link in self.links:
+ url = link.value
+ domain = urlparse(url).netloc
+ extra = ""
+ if domain in domainToIcon:
+ if domain == "www.linkedin.com":
+ username = self.full_name
+ else:
+ icon = domainToIcon[domain]
+ if url[-1] == "/":
+ url = url[0:-1]
+ username = url.split("/")[-1]
+ username = username.replace("@","")
+ else:
+ icon = "fa-globe"
+ try:
+ json = requests.get(urlparse(url).scheme + "://" + domain + "/api/v2/instance").json()
+ if 'source_url' in json:
+ if json['source_url']=='https://github.com/mastodon/mastodon':
+ icon = "fa-brands fa-mastodon"
+ extra = "rel='me'"
+ username = url.split("/")[-1]
+ username = username.replace("@","")
+ except:
+ icon = "fa-globe"
+ username = domain
+
+ self.linkIcons.append(('raw_html', '