First step on adding permissions
This commit is contained in:
parent
a7a9f47756
commit
2143b261ac
@ -9,6 +9,7 @@
|
|||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.9 (SARBase)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.9 (SARBase)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="leaflet-control-geocoder" level="application" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PackageRequirementsSettings">
|
<component name="PackageRequirementsSettings">
|
||||||
<option name="removeUnused" value="true" />
|
<option name="removeUnused" value="true" />
|
||||||
|
@ -26,3 +26,8 @@ An attempt to create search and rescue database for logging SAR jobs and activit
|
|||||||
# How to run the application with docker-compose
|
# How to run the application with docker-compose
|
||||||
|
|
||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
|
|
||||||
|
# Database migration with docker environment
|
||||||
|
|
||||||
|
docker-compose exec web flask db migrate
|
||||||
|
docker-compose exec web flask db upgrade
|
3
app.py
3
app.py
@ -4,6 +4,7 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
|
#from flask_debugtoolbar import DebugToolbarExtension
|
||||||
from sqlalchemy import MetaData
|
from sqlalchemy import MetaData
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ app = Flask(__name__)
|
|||||||
app.config['SECRET_KEY'] = 'secret_key'
|
app.config['SECRET_KEY'] = 'secret_key'
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'mysql+mysqlconnector://sarbaseuser:password@localhost/sarbaseapp')
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'mysql+mysqlconnector://sarbaseuser:password@localhost/sarbaseapp')
|
||||||
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
|
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
|
||||||
|
#app.debug = True
|
||||||
|
#toolbar = DebugToolbarExtension(app)
|
||||||
babel= Babel(app)
|
babel= Babel(app)
|
||||||
babel.init_app(app, locale_selector=get_locale)
|
babel.init_app(app, locale_selector=get_locale)
|
||||||
db = SQLAlchemy(app, metadata=metadata)
|
db = SQLAlchemy(app, metadata=metadata)
|
||||||
|
6
init-db/01-initdb.sh
Executable file
6
init-db/01-initdb.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo Initialising the database...
|
||||||
|
|
||||||
|
mysql -u root --password=password sarbaseapp < /docker-entrypoint-initdb.d/mysql_dump.sql
|
||||||
|
|
303
init-db/mysql_dump.sql
Normal file
303
init-db/mysql_dump.sql
Normal file
File diff suppressed because one or more lines are too long
62
models.py
62
models.py
@ -5,30 +5,6 @@ from flask_login import UserMixin
|
|||||||
from app import db
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
created = db.Column(db.DateTime, nullable=False, default=datetime.now)
|
|
||||||
updated = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
|
||||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
|
||||||
full_name = db.Column(db.String(301), nullable=False)
|
|
||||||
email = db.Column(db.String(150), unique=True, nullable=False)
|
|
||||||
phone_number = db.Column(db.String(50), nullable=True)
|
|
||||||
password = db.Column(db.String(50))
|
|
||||||
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
|
||||||
role = db.relationship('Role', backref=db.backref('users', lazy='dynamic'))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.username
|
|
||||||
|
|
||||||
|
|
||||||
class Role(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
name = db.Column(db.String(80), unique=True)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.name # This is so that when we print the Role class, it will print the name instead of the object memory address
|
|
||||||
|
|
||||||
|
|
||||||
class SARCall(db.Model):
|
class SARCall(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
created = db.Column(db.DateTime, nullable=False, default=datetime.now)
|
created = db.Column(db.DateTime, nullable=False, default=datetime.now)
|
||||||
@ -45,8 +21,42 @@ class SARCall(db.Model):
|
|||||||
title = db.Column(db.String(150), nullable=False)
|
title = db.Column(db.String(150), nullable=False)
|
||||||
description = db.Column(db.Text, nullable=True)
|
description = db.Column(db.Text, nullable=True)
|
||||||
description_hidden = 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)
|
search_officer_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
search_manager = db.relationship('User', backref=db.backref('sar_calls', lazy=True))
|
coordination_officer_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
search_officer = db.relationship('User', back_populates='search_sar_calls', foreign_keys=[search_officer_id])
|
||||||
|
coordination_officer = db.relationship('User', back_populates='coordination_sar_calls',
|
||||||
|
foreign_keys=[coordination_officer_id])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
created = db.Column(db.DateTime, nullable=False, default=datetime.now)
|
||||||
|
updated = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
||||||
|
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
|
full_name = db.Column(db.String(301), nullable=False)
|
||||||
|
email = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
|
phone_number = db.Column(db.String(50), nullable=True)
|
||||||
|
password = db.Column(db.String(50))
|
||||||
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
||||||
|
role = db.relationship('Role', back_populates='users')
|
||||||
|
search_sar_calls = db.relationship('SARCall', back_populates='search_officer',
|
||||||
|
foreign_keys=[SARCall.search_officer_id])
|
||||||
|
coordination_sar_calls = db.relationship('SARCall', back_populates='coordination_officer',
|
||||||
|
foreign_keys=[SARCall.coordination_officer_id])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
|
||||||
|
class Role(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(80), unique=True)
|
||||||
|
users = db.relationship('User', back_populates='role')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.name # Assuming 'name' is the field you want to display
|
||||||
|
|
||||||
|
|
||||||
class SARCategory(db.Model):
|
class SARCategory(db.Model):
|
||||||
|
@ -11,3 +11,5 @@ mysql_connector_python==8.2.0
|
|||||||
gpxpy==1.6.1
|
gpxpy==1.6.1
|
||||||
geopy==2.4.0
|
geopy==2.4.0
|
||||||
flask_babel==4.0.0
|
flask_babel==4.0.0
|
||||||
|
#flask_debugtoolbar==0.13.1
|
||||||
|
|
||||||
|
33
sar_calls.py
33
sar_calls.py
@ -1,9 +1,10 @@
|
|||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
from flask import request, redirect, flash, render_template, url_for, jsonify, Response
|
from flask import request, redirect, flash, render_template, url_for, jsonify, Response
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
from sqlalchemy import or_
|
||||||
|
|
||||||
from app import app, db
|
from app import app, db
|
||||||
from models import SARCall, Comment, GPSTrack, SARCategory, SARStatus, User
|
from models import SARCall, Comment, GPSTrack, SARCategory, SARStatus, User, Role
|
||||||
|
|
||||||
|
|
||||||
@app.route('/create_sar', methods=['GET', 'POST'])
|
@app.route('/create_sar', methods=['GET', 'POST'])
|
||||||
@ -11,6 +12,8 @@ from models import SARCall, Comment, GPSTrack, SARCategory, SARStatus, User
|
|||||||
def create_sar():
|
def create_sar():
|
||||||
categories = SARCategory.query.all()
|
categories = SARCategory.query.all()
|
||||||
statuses = SARStatus.query.order_by('id').all()
|
statuses = SARStatus.query.order_by('id').all()
|
||||||
|
managers = User.query.join(Role).filter(or_(Role.name == 'search manager', Role.name =='admin')).all()
|
||||||
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
start_date = parser.parse(request.form.get('start_date'))
|
start_date = parser.parse(request.form.get('start_date'))
|
||||||
@ -31,26 +34,25 @@ def create_sar():
|
|||||||
category=category,
|
category=category,
|
||||||
latitude=latitude,
|
latitude=latitude,
|
||||||
longitude=longitude,
|
longitude=longitude,
|
||||||
search_manager_id=current_user.id,
|
coordination_officer_id=current_user.id,
|
||||||
status=status,
|
status=status,
|
||||||
title=title,
|
title=title,
|
||||||
description=request.form.get('description'),
|
description=request.form.get('description'),
|
||||||
description_hidden=request.form.get('description_hidden'),
|
description_hidden=request.form.get('description_hidden'),
|
||||||
# gpx_data=gpx_data_list,
|
search_officer_id=request.form.get('search_manager_id')
|
||||||
# gpx_color_list=gpx_color_list,
|
|
||||||
)
|
)
|
||||||
db.session.add(new_sar_call)
|
db.session.add(new_sar_call)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('SAR call created successfully!', 'success')
|
flash('SAR call created successfully!', 'success')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('list_sar'))
|
||||||
|
|
||||||
return render_template('create_sar.html', categories=categories, statuses=statuses)
|
return render_template('create_sar.html', categories=categories, statuses=statuses, managers=managers)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/list_sar')
|
@app.route('/list_sar')
|
||||||
@login_required
|
@login_required
|
||||||
def list_sar():
|
def list_sar():
|
||||||
sar_calls = SARCall.query.join(User, SARCall.search_manager_id == User.id).join(SARCategory,
|
sar_calls = SARCall.query.join(User, SARCall.search_officer_id == User.id).join(SARCategory,
|
||||||
SARCall.category == SARCategory.id).add_columns(
|
SARCall.category == SARCategory.id).add_columns(
|
||||||
SARCategory, User, SARCall).all()
|
SARCategory, User, SARCall).all()
|
||||||
return render_template('list_sar.html', sar_calls=sar_calls)
|
return render_template('list_sar.html', sar_calls=sar_calls)
|
||||||
@ -62,6 +64,7 @@ def edit_sar(id):
|
|||||||
sar_call = SARCall.query.get_or_404(id)
|
sar_call = SARCall.query.get_or_404(id)
|
||||||
categories = SARCategory.query.all()
|
categories = SARCategory.query.all()
|
||||||
statuses = SARStatus.query.order_by('id').all()
|
statuses = SARStatus.query.order_by('id').all()
|
||||||
|
managers = User.query.join(Role).filter(or_(Role.name == 'search manager', Role.name =='admin')).all()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
sar_call.start_date = parser.parse(request.form.get('start_date'))
|
sar_call.start_date = parser.parse(request.form.get('start_date'))
|
||||||
@ -75,7 +78,6 @@ def edit_sar(id):
|
|||||||
sar_call.title = request.form.get('title')
|
sar_call.title = request.form.get('title')
|
||||||
sar_call.description = request.form.get('description')
|
sar_call.description = request.form.get('description')
|
||||||
sar_call.description_hidden = request.form.get('description_hidden')
|
sar_call.description_hidden = request.form.get('description_hidden')
|
||||||
# sar_call.gpx_data = request.form.get('gpx_data')
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('SAR call updated successfully!', 'success')
|
flash('SAR call updated successfully!', 'success')
|
||||||
@ -86,7 +88,8 @@ def edit_sar(id):
|
|||||||
if sar_call.finish_date:
|
if sar_call.finish_date:
|
||||||
sar_call.finish_date = sar_call.finish_date.strftime('%Y-%m-%d')
|
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)
|
return render_template('edit_sar.html', sar_call=sar_call, categories=categories, statuses=statuses,
|
||||||
|
managers=managers)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sar_details/<int:id>')
|
@app.route('/sar_details/<int:id>')
|
||||||
@ -100,10 +103,10 @@ def sar_details(id):
|
|||||||
gpx_tracks = GPSTrack.query.filter_by(comment_id=comment.id).all()
|
gpx_tracks = GPSTrack.query.filter_by(comment_id=comment.id).all()
|
||||||
for track in gpx_tracks:
|
for track in gpx_tracks:
|
||||||
comments_with_gpx.append({
|
comments_with_gpx.append({
|
||||||
"id": track.id,
|
"id": track.id,
|
||||||
"comment_id": comment.id,
|
"comment_id": comment.id,
|
||||||
"name": track.file_name,
|
"name": track.file_name,
|
||||||
"comment": track.gpx_name
|
"comment": track.gpx_name
|
||||||
})
|
})
|
||||||
|
|
||||||
return render_template('sar_details.html', sar=sar, gpx_ids=gpx_files, comments_with_gpx=comments_with_gpx)
|
return render_template('sar_details.html', sar=sar, gpx_ids=gpx_files, comments_with_gpx=comments_with_gpx)
|
||||||
@ -123,9 +126,7 @@ def delete_sar(id):
|
|||||||
@login_required
|
@login_required
|
||||||
def add_comment(sar_call_id):
|
def add_comment(sar_call_id):
|
||||||
text = request.form.get('text')
|
text = request.form.get('text')
|
||||||
gpx_file = request.files.get('gpx_file')
|
comment = Comment(text=text, user_id=current_user.id, sar_call_id=sar_call_id)
|
||||||
gpx_data = gpx_file.read().decode("utf-8") if gpx_file else None
|
|
||||||
comment = Comment(text=text, gpx_data=gpx_data, user_id=current_user.id, sar_call_id=sar_call_id)
|
|
||||||
db.session.add(comment)
|
db.session.add(comment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('sar_details', id=sar_call_id))
|
return redirect(url_for('sar_details', id=sar_call_id))
|
||||||
|
@ -70,24 +70,50 @@
|
|||||||
<textarea name="description_hidden" class="form-control"></textarea>
|
<textarea name="description_hidden" class="form-control"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# <!-- GPX Track Fields (You can expand upon this based on the previous discussions about multiple tracks) -->#}
|
<div class="form-group">
|
||||||
{# <div class="form-group">#}
|
<label for="manager">Assigned search manager:</label>
|
||||||
{# <label for="gpx_file">Upload GPX Track:</label>#}
|
<select name="category" id="search_officer" class="form-control">
|
||||||
{# <input type="file" name="gpx_file">#}
|
{% for user in managers %}
|
||||||
{# </div>#}
|
<option value="{{ user.id }}">{{ user.full_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Create</button>
|
<button type="submit" class="btn btn-primary">Create</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="map" style="width: 600px; height: 400px;"></div>
|
<div id="map" style="width: 600px; height: 400px;"></div>
|
||||||
<script>
|
<script>
|
||||||
var map = L.map('map').setView([51.505, -0.09], 13); // Default to London, adjust as needed
|
|
||||||
|
var map = L.map('map').setView([60.19, 20.37], 13); // Default to London, adjust as needed#
|
||||||
|
var marker;
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
var marker;
|
|
||||||
|
// Function to initialize map with user's location
|
||||||
|
function initializeMap(lat, lon) {
|
||||||
|
map = L.map('map').setView([lat, lon], 13);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user's current location
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(function (position) {
|
||||||
|
initializeMap(position.coords.latitude, position.coords.longitude);
|
||||||
|
}, function () {
|
||||||
|
// If there's an error or permission denied, default to a location (e.g., London in this case)
|
||||||
|
initializeMap(60.19, 20.37);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Geolocation is not supported by this browser, default to a location (e.g., London)
|
||||||
|
initializeMap(60.19, 20.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add the geocoder
|
// Add the geocoder
|
||||||
L.Control.geocoder({
|
L.Control.geocoder({
|
||||||
@ -116,44 +142,15 @@
|
|||||||
document.querySelector('input[name="longitude"]').value = e.latlng.lng;
|
document.querySelector('input[name="longitude"]').value = e.latlng.lng;
|
||||||
});
|
});
|
||||||
|
|
||||||
{#var gpxTracks = {{ gpx_data | tojson }};#}
|
|
||||||
{#gpxTracks.forEach(function (track) {#}
|
|
||||||
{# var gpxLayer = new L.GPX(track.data, {async: true, polyline_options: {color: track.color}});#}
|
|
||||||
{# gpxLayer.on('loaded', function (e) {#}
|
|
||||||
{# map.fitBounds(e.target.getBounds());#}
|
|
||||||
{# });#}
|
|
||||||
{# map.addLayer(gpxLayer);#}
|
|
||||||
{#})
|
|
||||||
|
|
||||||
#}
|
// If editing, set the marker to the existing coordinates
|
||||||
|
var latInput = document.querySelector('input[name="latitude"]');
|
||||||
// If editing, set the marker to the existing coordinates
|
var lngInput = document.querySelector('input[name="longitude"]');
|
||||||
var latInput = document.querySelector('input[name="latitude"]');
|
if (latInput.value && lngInput.value) {
|
||||||
var lngInput = document.querySelector('input[name="longitude"]');
|
marker = L.marker([latInput.value, lngInput.value]).addTo(map);
|
||||||
if (latInput.value && lngInput.value) {
|
}
|
||||||
marker = L.marker([latInput.value, lngInput.value]).addTo(map);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{##}
|
|
||||||
{# <script>#}
|
|
||||||
{# function addTrack() {#}
|
|
||||||
{# let trackDiv = document.createElement('div');#}
|
|
||||||
{# trackDiv.className = 'gpx_track';#}
|
|
||||||
{# trackDiv.innerHTML = `#}
|
|
||||||
{# <label>GPX Data:</label>#}
|
|
||||||
{# <textarea name="gpx_data[]"></textarea>#}
|
|
||||||
{# <label>Color:</label>#}
|
|
||||||
{# <input type="color" name="gpx_color[]" value="#ff0000">#}
|
|
||||||
{# <button type="button" onclick="removeTrack(this)">Remove</button>#}
|
|
||||||
{# `;#}
|
|
||||||
{# document.getElementById('gpx_tracks').appendChild(trackDiv);#}
|
|
||||||
{# }#}
|
|
||||||
{##}
|
|
||||||
{# function removeTrack(button) {#}
|
|
||||||
{# let trackDiv = button.parentElement;#}
|
|
||||||
{# trackDiv.remove();#}
|
|
||||||
{# }#}
|
|
||||||
{# </script>#}
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('today_button').addEventListener('click', function () {
|
document.getElementById('today_button').addEventListener('click', function () {
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<div id="map" style="width: 600px; height: 400px;"></div>
|
<div id="map" style="width: 600px; height: 400px;"></div>
|
||||||
<script>
|
<script>
|
||||||
var map = L.map('map').setView([51.505, -0.09], 13); // Default to London, adjust as needed
|
var map = L.map('map').setView([60.19,20.37], 13); // Default to London, adjust as needed
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
|
<th>Status</th>
|
||||||
<th>Start Date</th>
|
<th>Start Date</th>
|
||||||
<th>Finish Date</th>
|
<th>Finish Date</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Latitude</th>
|
|
||||||
<th>Longitude</th>
|
|
||||||
<th>Created by</th>
|
<th>Created by</th>
|
||||||
|
<th>Manager</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -26,12 +26,12 @@
|
|||||||
<tr class="clickable-row" data-href="{{ url_for('sar_details', id=sar.SARCall.id) }}">
|
<tr class="clickable-row" data-href="{{ url_for('sar_details', id=sar.SARCall.id) }}">
|
||||||
<td>{{ sar.SARCall.id }}</td>
|
<td>{{ sar.SARCall.id }}</td>
|
||||||
<td>{{ sar.SARCall.title }}</td>
|
<td>{{ sar.SARCall.title }}</td>
|
||||||
|
<td>{{ sar.SARCall.status }}</td>
|
||||||
<td>{{ sar.SARCall.start_date }}</td>
|
<td>{{ sar.SARCall.start_date }}</td>
|
||||||
<td>{{ sar.SARCall.finish_date }}</td>
|
<td>{{ sar.SARCall.finish_date }}</td>
|
||||||
<td>{{ sar.SARCategory.name }}</td>
|
<td>{{ sar.SARCategory.name }}</td>
|
||||||
<td>{{ sar.SARCall.latitude }}</td>
|
|
||||||
<td>{{ sar.SARCall.longitude }}</td>
|
|
||||||
<td>{{ sar.User.full_name }}</td>
|
<td>{{ sar.User.full_name }}</td>
|
||||||
|
<td>{{ sar.SARCall.manager }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('edit_sar', id=sar.SARCall.id) }}">
|
<a href="{{ url_for('edit_sar', id=sar.SARCall.id) }}">
|
||||||
<button type="button" class="btn btn-info">Edit</button>
|
<button type="button" class="btn btn-info">Edit</button>
|
||||||
|
Loading…
Reference in New Issue
Block a user