Skip to content

Commit

Permalink
Merge pull request #29 from hotosm/feat/news-page
Browse files Browse the repository at this point in the history
news page should be ready to go
  • Loading branch information
luminaryFlowers authored Jun 27, 2024
2 parents 5473ab0 + f19db53 commit c3506e0
Show file tree
Hide file tree
Showing 92 changed files with 1,356 additions and 876 deletions.
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

0 comments on commit c3506e0

Please sign in to comment.