forked from Ugric/Komodo-Hub
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:
commit
8c1ad1dd2f
9 files changed
+268
-37
No files matched your search
+31
-1
@@ -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,
|
||||
))
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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 %}
|
||||
Reference in new issue
Block a user