From 9ddc6f3771c194ca7f990708cb6b07865017219d Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Sun, 5 Apr 2026 20:09:35 +0100
Subject: [PATCH 1/6] fixed 'register now' button going to wrong url on about
page
---
templates/about/about.jinja | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/about/about.jinja b/templates/about/about.jinja
index 5f756b5..28ecf97 100644
--- a/templates/about/about.jinja
+++ b/templates/about/about.jinja
@@ -73,7 +73,7 @@
Create a free account to track species, contribute to our database, and connect with the conservation community.
- {{ button(url='/accounts/user_registration', button_text='Create an Account', extra_classes='homepage-btn') }}
+ {{ button(url='/register', button_text='Create an Account', extra_classes='homepage-btn') }}
From 5bd1ec7fa4802e6c008d46c0bb4ec4e8dd0837fb Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Sun, 5 Apr 2026 23:33:52 +0100
Subject: [PATCH 2/6] changed display_all_items macro to display_all_species
and added display_all_articles macro
---
templates/display_items.jinja | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/templates/display_items.jinja b/templates/display_items.jinja
index 6f4e6da..ff9be00 100644
--- a/templates/display_items.jinja
+++ b/templates/display_items.jinja
@@ -1,6 +1,6 @@
-{% macro display_all_items(items)%}
+{% macro display_all_species(items)%}
{# Displays all species in the provided 'items' array #}
-{# Used in the species database and species results pages #}
+{# Used in the species database and search species results pages #}
+{% endmacro %}
+{% macro display_all_articles(items)%}
+{# Displays all articles in the provided 'items' array #}
+{# Used in the article library and article search results pages #}
+
{% endmacro %}
\ No newline at end of file
From 8dc5dacd09172ff6b78dfd8f0bda8015ffe876e1 Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Mon, 6 Apr 2026 04:54:22 +0100
Subject: [PATCH 3/6] added articles table to db
---
database/db_init.py | 63 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/database/db_init.py b/database/db_init.py
index acda5d0..9aeee3d 100644
--- a/database/db_init.py
+++ b/database/db_init.py
@@ -90,7 +90,27 @@ SEED_SPECIES = [
"photoid": "celebes crested macaque"
},
]
-
+test_article = [
+ {
+ "article_id":"a-starrier-night",
+ "title" :"A Starrier Night",
+ "subtitle" :("In the past decade, our planet has rapidly lost its night sky. In response,"
+ "West Texas created the largest international dark-sky reserve on Earth."),
+ "main_text" :("Light pollution has been growing rapidly, with the sky brightening nearly 10% each year "
+ "since 2011. In West Texas, the McDonald Observatory—the darkest observatory in the "
+ "continental US—faced an operational threat as development crept into the region. "
+ "Superintendent Teznie Pugh and colleagues partnered with local landowners, parks, and "
+ "The Nature Conservancy to protect the night sky. In 2022, DarkSky International certified "
+ "the Greater Big Bend International Dark Sky Reserve, covering 9.6 million acres and "
+ "spanning the US-Mexico border—the largest International Dark Sky Reserve in the world. "
+ "The Davis Mountains Preserve (33,000 acres) forms the core of the reserve. The greatest "
+ "ongoing threat comes from oil and gas development in the Permian Basin. The reserve also "
+ "supports ecological research into the effects of light pollution on nocturnal wildlife, "
+ "plants, and ecosystems."),
+ "author" :"Jenny Rogers",
+ "publish_date":"2026-02-13"
+ }
+]
def init_database():
with get_db() as conn:
@@ -109,7 +129,48 @@ def init_database():
print("Database initialised.")
seed_species()
init_programs_database()
+ init_articles()
+def init_articles():
+ with get_db() as conn:
+ # table for articles in library
+ conn.execute("""
+ CREATE TABLE IF NOT EXISTS articles (
+ article_id TEXT PRIMARY KEY UNIQUE,
+ title TEXT,
+ subtitle TEXT,
+ main_text TEXT,
+ author TEXT,
+ publish_date);""")
+ print("Successfully created/found articles table.")
+ # table to store links relevant to articles (e.g. sources or original article)
+ conn.execute("""
+ CREATE TABLE IF NOT EXISTS article_links (
+ link_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ article_id TEXT NOT NULL,
+ url TEXT NOT NULL,
+ link_text TEXT,
+ FOREIGN KEY (article_id) REFERENCES articles(article_id)
+ ON DELETE CASCADE);""")
+ print("Succesfully found/created article_links table.")
+ for article in test_article:
+ table_exists = conn.execute(
+ "SELECT article_id FROM articles WHERE article_id = ?",
+ (article["article_id"],)).fetchone()
+ if table_exists:
+ print(f" SKIP {article['article_id']} (already exists)")
+ continue
+ conn.execute("""
+ INSERT INTO articles
+ (article_id, title, subtitle, main_text, author, publish_date)
+ VALUES (?, ?, ?, ?, ?, ?)""",
+ (article["article_id"],
+ article["title"],
+ article["subtitle"],
+ article["main_text"],
+ article["author"],
+ article["publish_date"],))
+ print(f" ADD {article['article_id']}")
def seed_species():
with get_db() as conn:
From 23884c2bad88c2bb4fe9845a950cd3ccac69fdee Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Mon, 6 Apr 2026 05:29:34 +0100
Subject: [PATCH 4/6] article library page and individual articles working
---
database/db_commands.py | 10 ++++++++
routes/library/article.py | 32 ++++++++++++++++++++++++++
routes/library/article_library.py | 30 ++++++++++++++++++++++++
templates/display_items.jinja | 5 ++--
templates/library/article.jinja | 23 ++++++++++++++++++
templates/library/library.jinja | 16 +++++++++++++
templates/species/species.jinja | 4 ++--
templates/species/species_search.jinja | 4 ++--
8 files changed, 118 insertions(+), 6 deletions(-)
create mode 100644 routes/library/article.py
create mode 100644 routes/library/article_library.py
create mode 100644 templates/library/article.jinja
create mode 100644 templates/library/library.jinja
diff --git a/database/db_commands.py b/database/db_commands.py
index d7871c1..57066e2 100644
--- a/database/db_commands.py
+++ b/database/db_commands.py
@@ -92,6 +92,16 @@ def get_all_species():
with get_db() as conn:
rows = conn.execute("SELECT * FROM species").fetchall()
return [dict(row) for row in rows]
+
+def get_all_articles():
+ with get_db() as conn:
+ rows = conn.execute("SELECT * FROM articles").fetchall()
+ return [dict(row) for row in rows]
+
+def get_article_by_id(id):
+ with get_db() as conn:
+ row = conn.execute("SELECT * FROM articles WHERE article_id = ? LIMIT 1", (id,)).fetchone()
+ return dict(row) if row else None
def get_species_by_name(species_english):
diff --git a/routes/library/article.py b/routes/library/article.py
new file mode 100644
index 0000000..f11d7bc
--- /dev/null
+++ b/routes/library/article.py
@@ -0,0 +1,32 @@
+from flask import render_template, Blueprint, send_from_directory
+from database.db_commands import get_article_by_id, get_species_by_id
+import sqlite3
+
+page = Blueprint("article", __name__)
+
+# this page works by taking the species name (in english) from the url and using that to read the database.
+# Because of this we are able to create one page and simply generate links based on the contents of the database
+# i.e. we can create a button with the hyperlink /species/iguana and this will
+# allow the user to view the information about the species
+# please be aware the page expects a .jpg at:
+# /static/images/species_database/{{photoid}}.jpg but this will be generated automatically
+# so long as you use the species_portal or species editor pages I created - CS
+
+
+@page.route("/articles/")
+def data(article_id):
+ database_entry = get_article_by_id(article_id)
+ if database_entry is None:
+ raise TypeError(f"No species named '{species_english}' found in the database.")
+ return render_template(
+ "library/article.jinja",
+ article_id =database_entry["article_id" ].capitalize(),
+ title =database_entry["title" ].capitalize(),
+ subtitle =database_entry["subtitle" ],
+ main_text =database_entry["main_text" ].capitalize(),
+ author =database_entry["author" ],
+ publish_date=database_entry["publish_date" ])
+
+@page.route("/species/image/")
+def image(photoid):
+ return send_from_directory("database/images", photoid)
\ No newline at end of file
diff --git a/routes/library/article_library.py b/routes/library/article_library.py
new file mode 100644
index 0000000..a55b9bc
--- /dev/null
+++ b/routes/library/article_library.py
@@ -0,0 +1,30 @@
+from flask import render_template, Blueprint, request
+import configparser
+
+from database.db_commands import get_all_articles, search_species, get_species_by_name
+from utilities.featured_species import featured_species_function
+
+config = configparser.ConfigParser()
+
+page = Blueprint("articles", __name__, url_prefix="/articles")
+
+# article library page
+@page.route("")
+def all_species():
+ article_list = get_all_articles()
+
+ article_list = sorted(article_list, key=lambda sp: sp["title"])
+
+ for sp in article_list:
+ print(sp['title'])
+
+ return render_template("library/library.jinja", articles=article_list)
+
+# search page
+@page.route("/search")
+def search():
+ query = request.args.get("search-query")
+
+ results = search_species(query)
+ print(results)
+ return render_template("/species/species_search.jinja", results=results, query=query)
\ No newline at end of file
diff --git a/templates/display_items.jinja b/templates/display_items.jinja
index ff9be00..df9be6f 100644
--- a/templates/display_items.jinja
+++ b/templates/display_items.jinja
@@ -22,9 +22,10 @@
{% for item in items %}
- {{ item['article_title'] }}
+ {{ item['title'] }}
- Main category: {{ item['category'] }}
+ Main category: {{ item['author'] }}
+ Date published: {{ item["publish_date"] }}
diff --git a/templates/library/article.jinja b/templates/library/article.jinja
new file mode 100644
index 0000000..2b29863
--- /dev/null
+++ b/templates/library/article.jinja
@@ -0,0 +1,23 @@
+{% extends "base.jinja" %}
+{% block title %}{{ english_name }} {% endblock %}
+{% block head %}
+{% endblock %}
+{% block content %}
+{% from 'button.jinja' import button %}
+
+
+ {{ title }}
+ {{ subtitle }}
+ Category: {{author}}
+ Category: {{publish_date}}
+
+
+
+
+{# this button should only be visible to certain account types #}
+{#
+ {{ button(url='/species/editor/'+species_id, button_text='Edit Species') }}
+
#}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/library/library.jinja b/templates/library/library.jinja
new file mode 100644
index 0000000..dc20e3d
--- /dev/null
+++ b/templates/library/library.jinja
@@ -0,0 +1,16 @@
+{% extends "base.jinja" %}
+{% block title %}Species Database{% endblock %}
+{% block head %}
+{% endblock %}
+{% block content %}
+{% from 'button.jinja' import button %}
+{% from 'display_items.jinja' import display_all_articles %}
+{% from 'search_bar.jinja' import search_bar%}
+
+{# All Articles #}
+{{search_bar("/articles/search", "")}}
+All Articles
+{{display_all_articles(articles)}}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/species/species.jinja b/templates/species/species.jinja
index 046cb27..08f4245 100644
--- a/templates/species/species.jinja
+++ b/templates/species/species.jinja
@@ -5,7 +5,7 @@
{% block content %}
{% from 'button.jinja' import button %}
{% from 'featured_species.jinja' import featured_species_section %}
-{% from 'display_items.jinja' import display_all_items %}
+{% from 'display_items.jinja' import display_all_species %}
{% from 'search_bar.jinja' import search_bar%}
Species Database
@@ -16,5 +16,5 @@ Species Database
{# All Species #}
{{search_bar("/species/search", "")}}
All Species
-{{display_all_items(species)}}
+{{display_all_species(species)}}
{% endblock %}
\ No newline at end of file
diff --git a/templates/species/species_search.jinja b/templates/species/species_search.jinja
index a7dd344..cbd06a5 100644
--- a/templates/species/species_search.jinja
+++ b/templates/species/species_search.jinja
@@ -4,12 +4,12 @@
{% endblock %}
{% block content %}
{% from 'button.jinja' import button %}
-{% from 'display_items.jinja' import display_all_items %}
+{% from 'display_items.jinja' import display_all_species %}
{% from 'search_bar.jinja' import search_bar%}
{# All Species #}
{{search_bar("/species/search", query)}}
-{{display_all_items(results)}}
+{{display_all_species(results)}}
{% endblock %}
\ No newline at end of file
From 1e8f3b67f3927bad57dfd5a1b79fd08a6ab9e7a6 Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Mon, 6 Apr 2026 15:13:58 +0100
Subject: [PATCH 5/6] article searching working
---
database/db_commands.py | 12 ++++++++++++
routes/library/article_library.py | 6 +++---
templates/library/article_search.jinja | 15 +++++++++++++++
3 files changed, 30 insertions(+), 3 deletions(-)
create mode 100644 templates/library/article_search.jinja
diff --git a/database/db_commands.py b/database/db_commands.py
index 57066e2..ca43acd 100644
--- a/database/db_commands.py
+++ b/database/db_commands.py
@@ -137,6 +137,18 @@ WHERE
(query.lower(),)
).fetchall()
return [dict(row) for row in rows]
+
+def search_articles(query):
+ with get_db() as conn:
+ rows = conn.execute("""SELECT * FROM articles WHERE
+ instr(LOWER(COALESCE(CAST(article_id AS TEXT), '')), ?1) > 0
+ OR instr(LOWER(COALESCE(CAST(title AS TEXT), '')), ?1) > 0
+ OR instr(LOWER(COALESCE(CAST(subtitle AS TEXT), '')), ?1) > 0
+ OR instr(LOWER(COALESCE(CAST(author AS TEXT), '')), ?1) > 0
+ OR instr(LOWER(COALESCE(CAST(publish_date AS TEXT), '')), ?1) > 0;""",
+ (query.lower(),)
+ ).fetchall()
+ return [dict(row) for row in rows]
def delete_species(species_id):
with get_db() as conn:
diff --git a/routes/library/article_library.py b/routes/library/article_library.py
index a55b9bc..f4e3e3f 100644
--- a/routes/library/article_library.py
+++ b/routes/library/article_library.py
@@ -1,7 +1,7 @@
from flask import render_template, Blueprint, request
import configparser
-from database.db_commands import get_all_articles, search_species, get_species_by_name
+from database.db_commands import get_all_articles, search_articles, get_species_by_name
from utilities.featured_species import featured_species_function
config = configparser.ConfigParser()
@@ -25,6 +25,6 @@ def all_species():
def search():
query = request.args.get("search-query")
- results = search_species(query)
+ results = search_articles(query)
print(results)
- return render_template("/species/species_search.jinja", results=results, query=query)
\ No newline at end of file
+ return render_template("/library/article_search.jinja", results=results, query=query)
\ No newline at end of file
diff --git a/templates/library/article_search.jinja b/templates/library/article_search.jinja
new file mode 100644
index 0000000..8830d1b
--- /dev/null
+++ b/templates/library/article_search.jinja
@@ -0,0 +1,15 @@
+{% extends "base.jinja" %}
+{% block title %}Species Database{% endblock %}
+{% block head %}
+{% endblock %}
+{% block content %}
+{% from 'button.jinja' import button %}
+{% from 'display_items.jinja' import display_all_articles %}
+{% from 'search_bar.jinja' import search_bar%}
+
+{# All Species #}
+{{search_bar("/articles/search", query)}}
+{{display_all_articles(results)}}
+{% endblock %}
\ No newline at end of file
From f5e2349acab7a7d9c4ee96ab5149e4f516e96ee5 Mon Sep 17 00:00:00 2001
From: TussockyJoker
Date: Mon, 6 Apr 2026 15:16:43 +0100
Subject: [PATCH 6/6] updated example db with new articles table
---
database/example_db.db | Bin 57344 -> 69632 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/database/example_db.db b/database/example_db.db
index 3b0ac705aa1cb91708c4be285b5bf546ba05ad6e..804ba99c72c0d947aea2fcb951d8c1143375f3e8 100644
GIT binary patch
delta 2858
zcmc&$OKclO7fdpp+|7>hE3AE(`Sc;t8`TuXe$3N@6
zcDwi5M}6-;IeImh%Z=h+_jCAWSs%Zen|S+Zq40gKIFQff8^t@NH%GQgwbFR;`QpdJ
zm7!wcx53X2-5tn(b@N95Yd4M;2ggpG>Ul$?)C+;eX~05Q34>@g*>`bxda*iLtCHH}
z)P*Y9w?s}4lZSmI&`Q8@dHeQA`L9)7AS|g>U#@L`Gd%XxzN%IncCWhy?e2~2JKG_J
z%PGka$`9vq_Tpl7_S`%+7niG#
z(3}Qo2#+%+X?g#sZjA=fN(!%_*D{MoY)e~dL&T4(YPP&ENE$1grZ9e{FfcZKV*5<#
z_>qp}WJT@%H@Z_Cy4%+~P#oC#xo~Xj*ie2fSK2K8T7<%nh4Uk~M%4T#`LUf*^80Z6
z`tVTSR-xbXd{t$JZ!lZq+^}uta@X*54XEksj4{hoZPTY7IErCVMWIXums+0d>b_zy
z*EBstbD8F91~(jRcbV=3bv&Jdt}xBFJkzlZ1%PRw8H%CnIybzwq2gV~p}C$9zJBhz
z>%)g68;U)kc+};-&UMH4HQY(nZ0b9@=i44r3fXXs&a$tmg$-Xb(o_$ma3W5Q7KncB(ExlYb%NZzU`Z~p(@y`Q%&(L
z*LS&YKSQZud0;8J?&&JWKFcyK6I?Jo+fpnS81r}8?A-eBZ0{38`KjEk_K9tB{{UqJhNLN?1j;V~
zuGipl8+%v`_$c8Sc1Yv8TneHT;)te!h-gSSjaMs(A^Ra-2O=U;#ivYT;)3Kwnj#Ea
zvNq%30Ys!8i;Wh8e|i%NfJa1h^DA&
zM!d_HCgludj?GABoLuxIBbthM^WL@hr9G0Ylpr3gpq#9Wi6{*sh*H$FyoB|Bn=({R
zBO;m*cjcxH=?FnNXVRMVRAu~kcI9Xp4Rm;K2>*#j1ynPYe^?*G*0-Pq_Uyig{o)D<#Oh9Lf79d^=aAc+^4(Tpxmk$jSL6TOpDPlZ(fZ>aJ
zfH7wWhsHSEI4QgH1HO+bqAX+?9ymp4Z6p|mFAv{F5b`ka@vO^po_6K~v6Iatg|%ej
z1&AU{O4MwBf6M|vwm
I2Om3s0@g8Fr2qf`
delta 135
zcmZozz|wGld4jZH2?GNI9}vR;*F+s-r4k0cE)ibd(+u34d<@(feAoHgd7Ah`_%-<)
z_!e>La`N$R7A)xH*v!Xymt|rA$L7zxf=vQ!Jo^~T<5RijpNJVeaZ*ae1vE7^);Ht00vYe%>V!Z