From 64d00c8293234486e72366fd107454d58cb809bf Mon Sep 17 00:00:00 2001 From: vadik likholetov Date: Sun, 12 Nov 2023 22:06:15 +0200 Subject: [PATCH] First working prototype with map search and some dynamic UI --- .idea/SARBase.iml | 3 +- .idea/misc.xml | 2 +- .idea/vcs.xml | 6 ++ admin.py | 4 +- app.py | 17 ++++- create_db.py | 11 --- models.py | 18 ++++- requirements.txt | 5 +- sar_calls.py | 72 ++++++++++++++----- templates/create_sar.html | 139 +++++++++++++++++++++++++------------ templates/edit_sar.html | 59 ++++++++++++---- templates/list_sar.html | 33 ++++++--- templates/sar_details.html | 44 ++++++++++++ 13 files changed, 307 insertions(+), 106 deletions(-) create mode 100644 .idea/vcs.xml delete mode 100644 create_db.py create mode 100644 templates/sar_details.html diff --git a/.idea/SARBase.iml b/.idea/SARBase.iml index 1a680f9..5d798a1 100644 --- a/.idea/SARBase.iml +++ b/.idea/SARBase.iml @@ -7,10 +7,11 @@ - + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 97320c3..1006bee 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/admin.py b/admin.py index 02442d0..b8a1073 100644 --- a/admin.py +++ b/admin.py @@ -1,10 +1,12 @@ from flask_admin import Admin from flask_admin.contrib.sqla import ModelView from app import app, db -from models import User, SARCall, SARCategory, GPSTrack +from models import User, SARCall, SARCategory, GPSTrack, SARStatus, SARResult admin = Admin(app, name='SAR Admin', template_mode='bootstrap3') admin.add_view(ModelView(User, db.session)) admin.add_view(ModelView(SARCall, db.session)) admin.add_view(ModelView(SARCategory, db.session)) +admin.add_view(ModelView(SARStatus, db.session)) +admin.add_view(ModelView(SARResult, db.session)) admin.add_view(ModelView(GPSTrack, db.session)) diff --git a/app.py b/app.py index d25c2d9..c9f7627 100644 --- a/app.py +++ b/app.py @@ -2,11 +2,24 @@ from flask import Flask, redirect, url_for from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager +from sqlalchemy import MetaData + +convention = { + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +metadata = MetaData(naming_convention=convention) + app = Flask(__name__) app.config['SECRET_KEY'] = 'secret_key' -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' -db = SQLAlchemy(app) +#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' +app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://sarbaseuser:password@localhost/sarbaseapp' +db = SQLAlchemy(app, metadata=metadata) migrate = Migrate(app, db) login_manager = LoginManager(app) diff --git a/create_db.py b/create_db.py deleted file mode 100644 index ce675d5..0000000 --- a/create_db.py +++ /dev/null @@ -1,11 +0,0 @@ -from flask import Flask, render_template, redirect, url_for, request, flash -from flask_sqlalchemy import SQLAlchemy - -app = Flask(__name__) -app.config['SECRET_KEY'] = 'secret_key' -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' -db = SQLAlchemy(app) -from models import User, SARCall, SARCategory - -if __name__ == '__main__': - db.create_all() diff --git a/models.py b/models.py index 823f842..9df011e 100644 --- a/models.py +++ b/models.py @@ -14,18 +14,30 @@ class User(UserMixin, db.Model): class SARCall(db.Model): id = db.Column(db.Integer, primary_key=True) start_date = db.Column(db.DateTime, nullable=False) - finish_date = db.Column(db.DateTime, nullable=False) - category = db.Column(db.String(150), nullable=False) + finish_date = db.Column(db.DateTime, nullable=True) + category = db.Column(db.Integer, db.ForeignKey('sar_category.id'), nullable=False) + status = db.Column(db.Integer, db.ForeignKey('sar_status.id'), nullable=False) + result = db.Column(db.Integer, db.ForeignKey('sar_result.id'), nullable=True) latitude = db.Column(db.Float, nullable=False) longitude = db.Column(db.Float, nullable=False) + title = db.Column(db.String(150), nullable=False) + description = db.Column(db.Text, nullable=True) + description_hidden = db.Column(db.Text, nullable=True) search_manager_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) gpx_data = db.Column(db.Text, nullable=True) # This will store GPX data as a text class SARCategory(db.Model): - id = db.Column(db.Integer, primary_key=True) + id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(150), unique=True, nullable=False) +class SARResult(db.Model): + id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) + name = db.Column(db.String(150), unique=True, nullable=False) + +class SARStatus(db.Model): + id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) + name = db.Column(db.String(150), unique=True, nullable=False) class GPSTrack(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/requirements.txt b/requirements.txt index 7a926c7..5b142ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ alembic==1.12.1 Flask==3.0.0 Flask_Admin==1.6.1 -Flask_Login==0.6.2 +Flask_Login==0.6.3 Flask_Migrate==4.0.5 flask_sqlalchemy==3.1.1 python_dateutil==2.8.2 SQLAlchemy==2.0.22 -Werkzeug==2.3.7 +Werkzeug==3.0.1 +mysql_connector_python==8.2.0 diff --git a/sar_calls.py b/sar_calls.py index 03df702..7624751 100644 --- a/sar_calls.py +++ b/sar_calls.py @@ -2,71 +2,105 @@ from app import app, db from flask import request, redirect, flash, render_template, url_for from flask_login import login_required, current_user from dateutil import parser -from models import SARCall, GPSTrack +from models import SARCall, GPSTrack, SARCategory, SARStatus, User @app.route('/create_sar', methods=['GET', 'POST']) @login_required def create_sar(): + categories = SARCategory.query.all() + statuses = SARStatus.query.order_by('id').all() + if request.method == 'POST': start_date = parser.parse(request.form.get('start_date')) - finish_date = parser.parse(request.form.get('finish_date')) category = request.form.get('category') latitude = request.form.get('latitude') longitude = request.form.get('longitude') - gpx_data_list = request.form.getlist('gpx_data[]') - gpx_color_list = request.form.getlist('gpx_color[]') + status = request.form.get('status') + title = request.form.get('title') + # gpx_data_list = request.form.getlist('gpx_data[]') + # gpx_color_list = request.form.getlist('gpx_color[]') - for data, color in zip(gpx_data_list, gpx_color_list): - track = GPSTrack(data=data, color=color, sar_call=new_sar_call) - db.session.add(track) + # for data, color in zip(gpx_data_list, gpx_color_list): + # track = GPSTrack(data=data, color=color, sar_call=new_sar_call) + # db.session.add(track) new_sar_call = SARCall( start_date=start_date, - finish_date=finish_date, category=category, latitude=latitude, longitude=longitude, search_manager_id=current_user.id, - gpx_data=gpx_data_list, - gpx_color_list=gpx_color_list, + status=status, + title=title, + description=request.form.get('description'), + description_hidden=request.form.get('description_hidden'), + # gpx_data=gpx_data_list, + # gpx_color_list=gpx_color_list, ) db.session.add(new_sar_call) db.session.commit() flash('SAR call created successfully!', 'success') return redirect(url_for('dashboard')) - return render_template('create_sar.html') + + return render_template('create_sar.html', categories=categories, statuses=statuses) @app.route('/list_sar') @login_required def list_sar(): - sar_calls = SARCall.query.all() + sar_calls = SARCall.query.join(User, SARCall.search_manager_id == User.id).join(SARCategory, SARCall.category == SARCategory.id).add_columns(SARCategory, User, SARCall).all() return render_template('list_sar.html', sar_calls=sar_calls) @app.route('/edit_sar/', methods=['GET', 'POST']) @login_required def edit_sar(id): - sar_call = SARCall.query.get(id) + sar_call = SARCall.query.get_or_404(id) + categories = SARCategory.query.all() + statuses = SARStatus.query.order_by('id').all() + if request.method == 'POST': - sar_call.start_date = request.form.get('start_date') - sar_call.finish_date = request.form.get('finish_date') + sar_call.start_date = parser.parse(request.form.get('start_date')) + if request.form.get('finish_date'): + sar_call.finish_date = parser.parse(request.form.get('finish_date')) sar_call.category = request.form.get('category') sar_call.latitude = request.form.get('latitude') sar_call.longitude = request.form.get('longitude') - sar_call.gpx_data = request.form.get('gpx_data') + sar_call.status = request.form.get('status') + sar_call.result = request.form.get('result') + sar_call.title = request.form.get('title') + sar_call.description = request.form.get('description') + sar_call.description_hidden = request.form.get('description_hidden') + # sar_call.gpx_data = request.form.get('gpx_data') + + db.session.commit() flash('SAR call updated successfully!', 'success') return redirect(url_for('list_sar')) - return render_template('edit_sar.html', sar_call=sar_call) + + if sar_call.start_date: + sar_call.start_date = sar_call.start_date.strftime('%Y-%m-%d') + if sar_call.finish_date: + sar_call.finish_date = sar_call.finish_date.strftime('%Y-%m-%d') + + return render_template('edit_sar.html', sar_call=sar_call,categories=categories, statuses=statuses) + + + +@app.route('/sar_details/') +def sar_details(id): + sar = SARCall.query.get_or_404(id) # Fetch the SARCall record or return 404 + return render_template('sar_details.html', sar=sar) + @app.route('/delete_sar/') @login_required def delete_sar(id): - sar_call = SARCall.query.get(id) + sar_call = SARCall.query.get_or_404(id) db.session.delete(sar_call) db.session.commit() - flash('SAR call deleted successfully!', 'success') + flash('SAR call record deleted successfully!', 'success') return redirect(url_for('list_sar')) + diff --git a/templates/create_sar.html b/templates/create_sar.html index 4c4a7e8..535f646 100644 --- a/templates/create_sar.html +++ b/templates/create_sar.html @@ -8,50 +8,73 @@ + + +

Create SAR Call

+ +
-
- - -
- - + + +
+ + +
+ +
+ +
+
- + {% for category in categories %} + {% endfor %}
- - Initial Planning Point (IPP) Coordinates: + +
+
+ + +
- - + +
- -
- - -
+ {# #} + {#
#} + {# #} + {# #} + {#
#}
@@ -66,6 +89,24 @@ var marker; + // Add the geocoder + L.Control.geocoder({ + defaultMarkGeocode: true + }) + .on('markgeocode', function (e) { + if (marker) { + map.removeLayer(marker); + } + var latlng = e.geocode.center; + L.marker(latlng).addTo(map); + map.setView(latlng, map.getZoom()); + // Update your form fields with the selected location + document.querySelector('input[name="latitude"]').value = latlng.lat; + document.querySelector('input[name="longitude"]').value = latlng.lng; + }) + .addTo(map); + + map.on('click', function (e) { if (marker) { map.removeLayer(marker); @@ -82,36 +123,48 @@ {# map.fitBounds(e.target.getBounds());#} {# });#} {# map.addLayer(gpxLayer);#} - {#});#} + {#}) + ; + #} - // If editing, set the marker to the existing coordinates - var latInput = document.querySelector('input[name="latitude"]'); - var lngInput = document.querySelector('input[name="longitude"]'); - if (latInput.value && lngInput.value) { - marker = L.marker([latInput.value, lngInput.value]).addTo(map); - } + // If editing, set the marker to the existing coordinates + var latInput = document.querySelector('input[name="latitude"]'); + var lngInput = document.querySelector('input[name="longitude"]'); + if (latInput.value && lngInput.value) { + marker = L.marker([latInput.value, lngInput.value]).addTo(map); + } + {##} + {# #} - {% endblock %} \ No newline at end of file diff --git a/templates/edit_sar.html b/templates/edit_sar.html index 605f86d..2095cb2 100644 --- a/templates/edit_sar.html +++ b/templates/edit_sar.html @@ -14,46 +14,66 @@

Edit SAR Call

+
+ + +
- +
+ +
+
- + {% for category in categories %} + {% endfor %}
- - + +
+
+ + +
+ +
- - + +
- -
- - -
+{# #} +{#
#} +{# #} +{# #} +{#
#}
@@ -83,6 +103,17 @@ marker = L.marker([latInput.value, lngInput.value]).addTo(map); } + +
<{% endblock %} diff --git a/templates/list_sar.html b/templates/list_sar.html index aec2045..0c47241 100644 --- a/templates/list_sar.html +++ b/templates/list_sar.html @@ -11,28 +11,32 @@ Id + Title Start Date Finish Date Category Latitude Longitude + Created by Actions {% for sar in sar_calls %} - - {{ sar.id }} - {{ sar.start_date }} - {{ sar.finish_date }} - {{ sar.category }} - {{ sar.latitude }} - {{ sar.longitude }} + + {{ sar.SARCall.id }} + {{ sar.SARCall.title }} + {{ sar.SARCall.start_date }} + {{ sar.SARCall.finish_date }} + {{ sar.SARCategory.name }} + {{ sar.SARCall.latitude }} + {{ sar.SARCall.longitude }} + {{ sar.User.full_name }} - + | - + @@ -42,4 +46,15 @@ Back to Dashboard + + + + + {% endblock %} \ No newline at end of file diff --git a/templates/sar_details.html b/templates/sar_details.html new file mode 100644 index 0000000..490e969 --- /dev/null +++ b/templates/sar_details.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} + +{% block title %} + SAR job details +{% endblock %} + +{% block content %} + + + + + +
+

SAR Job Details

+
+
+

ID: {{ sar.id }}

+

Start Date: {{ sar.start_date }}

+

Manager: {{ sar.manager }}

+ +
+
+ + +
+
+ Edit + Delete +
+
+ + + +{% endblock %}