Merge pull request 'Added species editing page, added delete button to species info page' (#54) from TussockyJoker/Komodo-Hub:main into main

Reviewed-on: Ugric/Komodo-Hub#54
This commit is contained in:
Ugric committed 2026-03-09 05:58:56 +00:00
commit 8c1ad1dd2f
9 files changed
+268 -37

No files matched your search

+31 -1
View File
@@ -35,6 +35,17 @@ def get_species_by_name(species_english):
(species_english,)
).fetchone()
return dict(row) if row else None
def get_species_by_id(species_id):
with get_db() as conn:
result = conn.execute(
"SELECT * FROM species WHERE species_id = ?",
(species_id,)
).fetchone()
if result:
return dict(result)
else:
return None
def search_species(query):
with get_db() as conn:
@@ -56,4 +67,23 @@ def delete_species(species_id):
conn.execute(
"DELETE FROM species WHERE species_id = ?",
(species_id,)
)
)
def update_species(species_id, english, latin, body, category, extinction_risk):
with get_db() as conn:
conn.execute("""
UPDATE species
SET species_english = ?,
species_latin = ?,
body_text = ?,
category = ?,
extinction_risk = ?
WHERE species_id = ?
""", (
english.title(),
latin.lower(),
body,
category,
extinction_risk,
species_id,
))
+1
View File
@@ -31,6 +31,7 @@ def import_pages(route, app):
app = Flask(__name__, static_folder='static', static_url_path='')
app.secret_key = "59474221-13e4-43dd-bc07-46a8d051d456"
init_database()
import_pages("routes", app)
+6 -7
View File
@@ -4,15 +4,13 @@ import sqlite3
page = Blueprint("species_information", __name__)
# this page works by taking the "species_id" 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
# 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/{{species_id}}.jpg but this will be generated automatically
# so long as you use the 'species_portal' page I created - CS
# /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("/species/<species_english>")
@@ -28,7 +26,8 @@ def data(species_english):
main_text =database_entry["body_text"],
category =database_entry["category"].capitalize(),
extinction_risk =database_entry["extinction_risk"],
photoid =database_entry["photoid"]
photoid =database_entry["photoid"],
species_id =str(database_entry["species_id"])
)
except TypeError as error_information:
return render_template(
+63
View File
@@ -0,0 +1,63 @@
from flask import render_template, Blueprint, request, redirect, url_for
from database.db_commands import delete_species, get_species_by_id, update_species, get_species_by_name
import uuid
page = Blueprint("species_editor", __name__, url_prefix="/species/editor")
@page.route("/<species_id>")
def edit_species(species_id):
species_to_edit = get_species_by_id(species_id)
print(species_id)
return render_template(
'species_portal/species_editor.jinja',
sp = species_to_edit,
species_id = species_id
)
@page.route("/<species_id>", methods=["POST"])
def species_update(species_id):
try:
eng_name = request.form["eng_name"].title()
latin_name = request.form["latin_name"].capitalize()
main_text = request.form["main_text"]
category = request.form["category"].capitalize()
extinction_risk = request.form["extinction_risk"]
species_image = request.files.get("species_image")
image_id = ""
if species_image and species_image.filename:
image_id = str(uuid.uuid4())
species_image.save(f"static/images/species_database/{image_id}.jpg")
update_species(species_id, eng_name, latin_name, main_text, category, extinction_risk)
return redirect(url_for("species_information.data", species_english=eng_name))
except Exception as error_information:
return render_template(
"species_portal/species_portal_failed.jinja",
error_information=error_information
)
@page.route("/success/<eng_name>")
def edit_success(eng_name):
return render_template(
"species_portal/species_portal_success.jinja",
eng_name = eng_name,
)
@page.route("/delete/<species_english>", methods=["POST"])
def delete_species_from_database(species_english):
try:
entry = get_species_by_name(species_english)
if entry is None:
raise ValueError(f"Species '{species_english}' not found.")
delete_species(entry["species_id"])
return redirect(url_for("species.all_species"))
except Exception as error_information:
return render_template(
"species_portal/species_portal_failed.jinja",
error_information=error_information
)
+13 -18
View File
@@ -1,5 +1,5 @@
from flask import render_template, Blueprint, request, redirect, url_for
from database.db_commands import add_species, delete_species, get_species_by_name
from flask import render_template, Blueprint, request, redirect, url_for, flash
from database.db_commands import add_species, delete_species, get_species_by_name, get_species_by_id
import os
import uuid
@@ -31,25 +31,20 @@ def add_species_to_database():
"species_portal/species_portal_success.jinja",
eng_name = eng_name,
)
except ValueError as error_information:
species_to_edit = get_species_by_name(request.form["eng_name"].title())
species_id = species_to_edit['species_id']
flash(f"{species_to_edit['species_english']} already exists. You have been redirected to the editor to update it.", "info")
return redirect(url_for("species_editor.edit_species", species_id=species_id))
except Exception as error_information:
return render_template(
"species_portal/species_portal_failed.jinja",
error_information=error_information
)
# now add editing functionality
@page.route("/delete/<species_english>", methods=["POST"])
def delete_species_from_database(species_english):
try:
entry = get_species_by_name(species_english)
if entry is None:
raise ValueError(f"Species '{species_english}' not found.")
delete_species(entry["species_id"])
return redirect(url_for("species.all_species"))
except Exception as error_information:
return render_template(
"species_portal/species_portal_failed.jinja",
error_information=error_information
)
@page.route("/<eng_name>")
def portal_success(eng_name):
return render_template(
"species_portal/species_portal_success.jinja",
eng_name = eng_name,
)
+65 -8
View File
@@ -130,7 +130,8 @@ h6 {
}
.extinction-icon {
height: 10%
height: 60px;
width: auto;
}
.navbar li {
@@ -592,6 +593,8 @@ input:focus {
.form{
width: 100%;
flex-direction:column;
display: flex;
}
.form-section {
@@ -605,11 +608,13 @@ input:focus {
width: 100%;
max-width: 600px;
background: var(--green3);
padding: 40px;
padding: 20px;
padding-top: 10px;
padding-bottom: 10px;
border-radius: 20px;
border: 2px solid var(--primary);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
margin: 1rem auto;
margin: auto;
box-sizing: border-box;
}
@@ -685,12 +690,11 @@ input:focus {
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 25px;
margin-bottom: 10px;
}
/* Labels */
.form-group label {
margin-bottom: 8px;
margin-bottom: 4px;
font-weight: 700;
font-size: 1rem;
}
@@ -698,7 +702,7 @@ input:focus {
/* Inputs + textarea */
.form-group input,
.form-group textarea {
padding: 12px 15px;
padding: 9px 12px;
border-radius: 12px;
border: 2px solid var(--secondary);
background-color: var(--background);
@@ -719,7 +723,6 @@ input:focus {
padding: 8px;
}
/* Submit button */
.submit-button {
width: 100%;
padding: 14px;
@@ -733,6 +736,19 @@ input:focus {
transition: 0.2s ease;
}
.delete-button {
width: 100%;
padding: 14px;
border-radius: 14px;
border: none;
font-size: 1.2rem;
font-weight: 700;
cursor: pointer;
background: linear-gradient(120deg, rgb(255, 0, 0), rgb(255, 0, 0), rgb(255, 0, 0));
color: rgb(255, 255, 255);
transition: 0.2s ease;
}
.submit-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.25);
@@ -789,4 +805,45 @@ input:focus {
align-items: center;
justify-content: center;
cursor: pointer;
}
.button-row {
display: flex;
gap: 2px;
margin-top: 10px;
flex-direction: column;
}
.species-layout{
display:flex;
gap:30px;
align-items:flex-start;
margin-bottom: 20px;
}
.species-layout .container{
flex:1;
}
/* flash message css please review */
.flash-container {
margin: 20px;
}
.flash {
padding: 12px 16px;
margin: 10px;
border-radius: 6px;
animation: fadeOut 3.5s forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; display: none; }
}
.flash-info {
background: var(--background);
border: 1px solid var(--primary);
}
+11
View File
@@ -15,6 +15,17 @@
<body>
{% include 'navbar.jinja' %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-container">
{% for category, message in messages %}
<div class="flash flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="content">
{% block content %}
{% endblock %}
@@ -4,13 +4,12 @@
{% endblock %}
{% block content %}
{% from 'button.jinja' import button %}
<!-- Banner section -->
<section id="hero" style="padding: 10px 20px; text-align: center;" class="container">
<div class="species-layout">
<section id="summary" style="padding: 10px 20px; text-align: center; margin-top:1rem" class="container">
<h1>{{ english_name }}</h1>
<h2><em>{{ latin_name }}</em></h2>
<p>Category: {{category}}</p>
<img src="/images/species_database/{{ photoid }}.jpg" class="species-photo" style="max-width: 100%; height: auto; border-radius: inherit;">
<p>{{ main_text }}</p>
<p>This species is currently classed as: {{extinction_risk}}</p>
{% if extinction_risk == "Not Evaluated" %}
<img src="/images/extinction_risk/notevaluated.png" class="extinction-icon">
@@ -32,4 +31,12 @@
<p></p>
{% endif %}
</section>
<section id="info" style="padding: 10px 20px; text-align: center;" class="container">
<p>{{ main_text }}</p>
</section>
</div>
{# this button should only be visible to certain account types #}
<div style="text-align: right;">
{{ button(url='/species/editor/'+species_id, button_text='Edit Species') }}
</div>
{% endblock %}
@@ -0,0 +1,68 @@
{% extends "base.jinja" %}
{% block title %} Species Upload Portal {% endblock %}
{% block head %}
{% endblock %}
{% block content %}
{% from 'button.jinja' import button %}
<h1 style="text-align:left;">SPECIES EDITOR</h1>
<div style="display:flex;flex-direction: row;">
<div class="container" style="height: fit-content; width:fit-content; text-align:center; max-width: 40%; padding:20px">
<p> Current photo: </p>
<img src="/images/species_database/{{ sp['photoid'] }}.jpg" style="width: 100%; height: auto; margin: auto">
</div>
<section id="hero" class="container">
<form action="{{ url_for('species_editor.species_update', species_id=species_id) }}" class="form" method="POST"
enctype="multipart/form-data">
<div class="form-group">
<label for="eng_name">English Name:</label>
<input type="text" id="eng_name" name="eng_name" value="{{ sp['species_english'] }}" required>
</div>
<div class="form-group">
<label for="latin_name">Latin Name:</label>
<input type="text" id="latin_name" name="latin_name" value="{{ sp['species_latin'] }}" required>
</div>
<div class="form-group">
<label for="main_text">Species Information:</label>
<textarea id="main_text" name="main_text" rows="4.5" required>{{ sp['body_text'] }}</textarea>
</div>
<div class="form-group">
<label for="species_image">Species Image:</label>
<input type="file" id="species_image" name="species_image" accept="image/*">
</div>
<div class="form-group">
<label for="category">Category (e.g. Reptile):</label>
<textarea id="category" name="category" required>{{ sp['category'] }}</textarea>
</div>
<div class="form-group">
<label for="extinction_risk"> Extinction risk: </label>
<select id="extinction_risk" name="extinction_risk">
{%for option in [
"Not Evaluated",
"Least Concern",
"Near Threatened",
"Vulnerable",
"Endangered",
"Critically Endangered",
"Extinct in Wild",
"Extinct"]%}
<option value='{{ option }}'{% if option == sp['extinction_risk'] %}
selected='selected'
{%endif%}
>{{option}}</option>
{% endfor %}
</select>
</div>
<div class="button-row">
<button type="submit" class="submit-button">
Update Species Information
</button>
</div>
</form>
<form action="{{ url_for('species_editor.delete_species_from_database', species_english=sp['species_english']) }}" method="POST" style="text-align: center; margin-bottom: 0px;">
<div class="button-row">
<button type="submit" class="delete-button">Delete from Database</button>
</div>
</form>
</section>
</div>
{% endblock %}