Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

news page should be ready to go #29

Merged
merged 2 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ node_modules
**/dist/
/staticfiles/
.coverage
/hot_osm/locale
/media
/locale
63 changes: 63 additions & 0 deletions app/news/migrations/0020_newsownerpage_category_select_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generated by Django 4.2.7 on 2024-06-25 22:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('news', '0019_newsownerpage_and_more'),
]

operations = [
migrations.AddField(
model_name='newsownerpage',
name='category_select',
field=models.CharField(default='Select Categories'),
),
migrations.AddField(
model_name='newsownerpage',
name='enter_tag_hint',
field=models.CharField(default='Enter tag'),
),
migrations.AddField(
model_name='newsownerpage',
name='keyword_search_hint',
field=models.CharField(default='Search by keyword'),
),
migrations.AddField(
model_name='newsownerpage',
name='remove_filters_text',
field=models.CharField(default='Remove All Filters'),
),
migrations.AddField(
model_name='newsownerpage',
name='results_text',
field=models.CharField(default='Results'),
),
migrations.AddField(
model_name='newsownerpage',
name='search_button_text',
field=models.CharField(default='Search'),
),
migrations.AddField(
model_name='newsownerpage',
name='sort_by_new',
field=models.CharField(default='Sort by New'),
),
migrations.AddField(
model_name='newsownerpage',
name='sort_by_old',
field=models.CharField(default='Sort by Old'),
),
migrations.AddField(
model_name='newsownerpage',
name='sort_by_titlea',
field=models.CharField(default='Sort by Title Alphabetical'),
),
migrations.AddField(
model_name='newsownerpage',
name='sort_by_titlez',
field=models.CharField(default='Sort by Title Reverse Alphabetical'),
),
]
103 changes: 92 additions & 11 deletions app/news/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django import forms
from django.db import models
from django.db.models import Q
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager
Expand All @@ -10,9 +12,56 @@
from wagtail.admin.panels import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.blocks import CharBlock, StreamBlock, StructBlock, URLBlock, RichTextBlock, PageChooserBlock
from wagtail.snippets.models import register_snippet
from wagtail.search import index


class NewsOwnerPage(Page):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)

keyword = request.GET.get('keyword', '')
news_list = IndividualNewsPage.objects.live().filter(locale=context['page'].locale)

categories = NewsCategory.objects.all()
tags = [x[4:] for x in request.GET.keys() if x.startswith("tag.")]
query = Q()
for category in categories:
if request.GET.get(str(category), ''):
query = query | Q(categories=category)
for tag in tags:
query = query | Q(tags__name=tag)
news_list = news_list.filter(query).distinct()

match request.GET.get('sort', ''):
case 'sort.new':
news_list = news_list.order_by('date')
case 'sort.old':
news_list = news_list.order_by('-date')
case 'sort.titlea':
news_list = news_list.order_by('title')
case 'sort.titlez':
news_list = news_list.order_by('-title')
case _:
news_list = news_list.order_by('date')

if keyword:
news_list = news_list.search(keyword)

page = request.GET.get('page', 1)
paginator = Paginator(news_list, 6) # if you want more/less items per page (i.e., per load), change the number here to something else
try:
news = paginator.page(page)
except PageNotAnInteger:
news = paginator.page(1)
except EmptyPage:
news = paginator.page(paginator.num_pages)

context['news'] = news
context['news_paginator'] = paginator
context['current_page'] = int(page)
context['categories'] = categories
return context

max_count = 1

authors_posted_by_text = models.CharField(default="Posted by", help_text="The text which appears prior to the authors names; with 'posted by', the text displays as 'posted by [author]'.")
Expand All @@ -25,20 +74,45 @@ class NewsOwnerPage(Page):
categories_title = models.CharField(default="Categories")
tags_title = models.CharField(default="Tags")

keyword_search_hint = models.CharField(default="Search by keyword")
category_select = models.CharField(default="Select Categories")
sort_by_new = models.CharField(default="Sort by New")
sort_by_old = models.CharField(default="Sort by Old")
sort_by_titlea = models.CharField(default="Sort by Title Alphabetical")
sort_by_titlez = models.CharField(default="Sort by Title Reverse Alphabetical")
enter_tag_hint = models.CharField(default="Enter tag")
search_button_text = models.CharField(default="Search")
remove_filters_text = models.CharField(default="Remove All Filters")
results_text = models.CharField(default="Results")

content_panels = Page.content_panels + [
MultiFieldPanel([
FieldPanel("authors_posted_by_text"),
FieldPanel("authors_posted_on_text"),
], heading="Info"),
FieldPanel('keyword_search_hint'),
FieldPanel('category_select'),
FieldPanel('sort_by_new'),
FieldPanel('sort_by_old'),
FieldPanel('sort_by_titlea'),
FieldPanel('sort_by_titlez'),
FieldPanel('enter_tag_hint'),
FieldPanel('search_button_text'),
FieldPanel('remove_filters_text'),
FieldPanel('results_text'),
], heading="News Search Page"),
MultiFieldPanel([
FieldPanel('related_projects_title'),
FieldPanel('related_news_title'),
FieldPanel('view_all_news_text'),
FieldPanel('view_all_news_url'),
FieldPanel('news_read_more_text'),
FieldPanel('categories_title'),
FieldPanel('tags_title'),
], heading="Sidebar"),
MultiFieldPanel([
FieldPanel("authors_posted_by_text"),
FieldPanel("authors_posted_on_text"),
], heading="Info"),
MultiFieldPanel([
FieldPanel('related_projects_title'),
FieldPanel('related_news_title'),
FieldPanel('view_all_news_text'),
FieldPanel('view_all_news_url'),
FieldPanel('news_read_more_text'),
FieldPanel('categories_title'),
FieldPanel('tags_title'),
], heading="Sidebar"),
], heading="Individual Project Page"),
]


Expand Down Expand Up @@ -103,6 +177,13 @@ class IndividualNewsPage(Page):

tags = ClusterTaggableManager(through=NewsTag, blank=True)

search_fields = Page.search_fields + [
index.SearchField('title'),
index.SearchField('intro'),
index.FilterField('newscategory_id'), # the console warns you about this but if you don't have this then category search doesn't work
index.FilterField('name'),
]

content_panels = Page.content_panels + [
MultiFieldPanel([
FieldPanel("authors"),
Expand Down
4 changes: 4 additions & 0 deletions app/news/templates/news/components/NewsSortOption.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="mt-4 flex items-center w-fit h-fit" @click="hit()" x-data="{ sort: '{{sort_id}}', hit() {setSortType('{{sort_by}}')} }" x-init="if(sort == params.get('sort')) hit()">
<input class="checked:bg-hot-red checked:hover:bg-hot-red checked:focus:bg-hot-red" type="radio" name="sort" :id="sort" :value="sort" :checked="(params.get('sort') == sort || !params.get('sort')) ? 'on' : ''">
<label class="pl-2" :for="sort">{{sort_by}}</label>
</div>
146 changes: 146 additions & 0 deletions app/news/templates/news/news_owner_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{% extends "base.html" %}
{% load static %}
{% load wagtailcore_tags %}
{% load wagtailimages_tags %}
{% load compress %}
{% block body_class %}template-individualnewspage{% endblock %}
{% block extra_css %}
{% compress css %}
{% endcompress css %}
{% endblock extra_css %}

{% block content %}
<div class="max-w-7xl mx-auto my-10" x-data="{ params: new URLSearchParams((new URL(window.location.href)).search) }">
<div class="px-6 md:px-10">
<h1 class="text-h1 font-semibold my-10">{{page.title}}</h1>
<form>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{% comment %} KEYWORD SEARCH {% endcomment %}
<div class="bg-hot-off-white flex items-center">
<input class="w-full bg-transparent border-none" placeholder="{{page.keyword_search_hint}}" type="text" name="keyword" :value="params.get('keyword')">
{% include "ui/components/icon_svgs/SearchIcon.html" with class="text-hot-red mx-3" %}
</div>

{% comment %} CATEGORY {% endcomment %}
<div class="relative" x-data="{ show: false }" @click.away="show = false">
<div class="w-full h-full flex justify-between py-2 px-3 bg-hot-off-white items-center cursor-pointer" @click="show = !show">
<p class="pointer-events-none">
{{page.category_select}}
</p>
{% include "ui/components/icon_svgs/LinkCaret.html" with class="rotate-90 text-hot-red" %}
</div>
<div class="absolute z-20 bg-hot-off-white p-4 w-full" x-show="show">
<hr class="border-b-2">
{% for category in categories %}
<div class="mt-4 flex">
<label for="cat-{{forloop.counter}}">{{category.category_name}}</label>
<input class="ml-auto checked:bg-hot-red checked:hover:bg-hot-red checked:focus:bg-hot-red" id="cat-{{forloop.counter}}" type="checkbox" name="{{category.category_name}}" :checked="params.get('{{category.category_name}}')">
</div>
{% endfor %}
</div>
</div>

{% comment %} SORT {% endcomment %}
<div class="relative" x-data="{ show: false, setSortType(text) {$refs.sorttype.innerText = text} }" @click.away="show = false">
<div class="w-full h-full flex justify-between py-2 px-3 bg-hot-off-white items-center cursor-pointer" @click="show = !show">
<p class="pointer-events-none" x-ref="sorttype">
{{page.sort_by_new}}
</p>
{% include "ui/components/icon_svgs/LinkCaret.html" with class="rotate-90 text-hot-red" %}
</div>
<div class="absolute z-20 bg-hot-off-white p-4 w-full" x-show="show">
<hr class="border-b-2">
{% include "./components/NewsSortOption.html" with sort_by=page.sort_by_new sort_id="sort.new" %}
{% include "./components/NewsSortOption.html" with sort_by=page.sort_by_old sort_id="sort.old" %}
{% include "./components/NewsSortOption.html" with sort_by=page.sort_by_titlea sort_id="sort.titlea" %}
{% include "./components/NewsSortOption.html" with sort_by=page.sort_by_titlez sort_id="sort.titlez" %}
</div>
</div>

{% comment %} TAG {% endcomment %}
<div class="bg-hot-off-white flex items-center" x-data="{ submitTagInput() {
if(!$refs.taginput.value) return;
$refs.taglist.innerHTML += `<div @click='' class='bg-hot-red text-white p-2 cursor-default'><input class='hidden' value='on' name='tag.${escape($refs.taginput.value)}'><p>${escape($refs.taginput.value)} <span class='ml-2 cursor-pointer' @click='$el.parentElement.parentElement.remove()'>✖</span></p></div>`; $refs.taginput.value = '';
} }">
<input class="w-full bg-transparent border-none" placeholder="{{page.enter_tag_hint}}" type="text" name="tagfield" x-ref="taginput"
x-on:keydown.enter="submitTagInput(); $event.preventDefault(); return false;"
>
<p class="text-hot-red px-3 py-2 cursor-pointer" @click="submitTagInput()">+</p>
</div>

<div class="bg-hot-red text-white font-semibold">
<input type="submit" value="{{page.search_button_text}}" class="bg-transparent w-full h-full cursor-pointer py-2">
</div>
</div>

<hr class="border-black border-t-2 mt-10 mb-4">

{% comment %} TAGS GO HERE {% endcomment %}
<a href="{{page.url}}">
<p class="cursor-pointer inline-block">
{{page.remove_filters_text}}
{% include "ui/components/icon_svgs/RefreshIcon.html" with class="ml-4" %}
</p>
</a>
{% comment %} for some reason having the 'let nada = undefined' is necessary because it doesn't like when you start the x-init with a for loop {% endcomment %}
<div id="taglist" x-ref="taglist" class="flex gap-4 mt-4"
x-init="let nada = undefined; for (let x of params) {
if (x[0].startsWith('tag.')) {
let tag = x[0].slice(4);
$el.innerHTML += `<div @click='' class='bg-hot-red text-white p-2 cursor-default'><input class='hidden' value='on' name='tag.${tag}'><p>${tag} <span class='ml-2 cursor-pointer' @click='$el.parentElement.parentElement.remove()'>✖</span></p></div>`;
}
}"
>
</div>
</form>

<h2 class="text-h2 font-bold my-8">{{news_paginator.count}} {{page.results_text}}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{% for article in news %}
{% include "ui/components/news/NewsPreviewBlockProjects.html" with news=article showimage=True class="[&_.image-hide-small]:hidden md:[&_.image-hide-small]:block" %}
{% endfor %}
</div>

<div class="w-full flex justify-center items-center"
x-data="{
page: params.get('page') ? params.get('page') : 1,
paramsX: new URLSearchParams((new URL(window.location.href)).search),
getPageLink(x) { this.paramsX.set('page', x); return '?' + this.paramsX.toString() }
}">
<a :href="page > 1 ? getPageLink(parseInt(page) - 1) : ''" aria-label="Previous Page">
<p class="cursor-pointer" :class="page == 1 ? 'text-hot-slate-grey' : 'text-hot-red'">
{% include "ui/components/icon_svgs/LinkCaret.html" with class="rotate-180" %}
</p>
</a>
<div class="flex gap-2 mx-4">
{% with ''|center:news_paginator.num_pages as range %}
{% for _ in range %}
{% if forloop.counter == 1 or forloop.counter == news_paginator.num_pages or forloop.counter == current_page or forloop.counter|add:1 == current_page or forloop.counter|add:'-1' == current_page %}
<a :href="getPageLink(parseInt({{forloop.counter}}))">
<p class="px-2 font-bold" :class="page == {{forloop.counter}} ? 'bg-hot-red text-white' : 'bg-hot-off-white'">
{{forloop.counter}}
</p>
</a>
{% endif %}
{% if forloop.counter|add:2 == current_page or forloop.counter|add:'-2' == current_page and not forloop.last %}
{% comment %}
for reasons that i can't begin to comprehend, the if statement
below CANNOT be part of the if statement above. it just does
not work
{% endcomment %}
{% if not forloop.first %}
<p class="mx-1">...</p>
{% endif %}
{% endif %}
{% endfor %}
{% endwith %}
</div>
<a :href="page < {{news_paginator.num_pages}} ? getPageLink(parseInt(page) + 1) : ''" aria-label="Next Page">
<p class="cursor-pointer" :class="page == {{news_paginator.num_pages}} ? 'text-hot-slate-grey' : 'text-hot-red'">
{% include "ui/components/icon_svgs/LinkCaret.html" %}
</p>
</a>
</div>
</div>
</div>
{% endblock %}
3 changes: 3 additions & 0 deletions app/ui/templates/ui/components/icon_svgs/RefreshIcon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<svg class="inline {{class}}" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20.75 20.75">
<path id="Icon_ionic-md-refresh" data-name="Icon ionic-md-refresh" d="M16,23.781A7.781,7.781,0,0,1,16,8.219a7.538,7.538,0,0,1,5.447,2.334L17.3,14.7h9.078V5.625L23.327,8.673A10.362,10.362,0,1,0,25.986,18.83H23.242A7.727,7.727,0,0,1,16,23.781Z" transform="translate(-5.625 -5.625)" fill="#d43f3f"/>
</svg>
3 changes: 3 additions & 0 deletions app/ui/templates/ui/components/icon_svgs/SearchIcon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<svg class="inline {{class}}" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 13.99 13.993">
<path id="Icon_ionic-ios-search" data-name="Icon ionic-ios-search" d="M18.326,17.474l-3.891-3.927a5.545,5.545,0,1,0-.842.853l3.865,3.9a.6.6,0,0,0,.845.022A.6.6,0,0,0,18.326,17.474Zm-8.248-3.027a4.378,4.378,0,1,1,3.1-1.282A4.351,4.351,0,0,1,10.078,14.446Z" transform="translate(-4.5 -4.493)" fill="#d43f3f"/>
</svg>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% load wagtailimages_tags %}

{% image news.image original as image_p %}
<div class="my-4">
<div class="my-4 {{class}}">
<a href="{{ news.url }}">
{% if showimage %}
<div class="image-hide-small w-full min-h-[9rem] mb-3 content-end bg-no-repeat bg-cover image-overshadow-gradient" style="background-image: url('{{ image_p.url }}')">
Expand Down
Loading
Loading