Added user roles, updated admin interface

This commit is contained in:
Vadim Likholetov 2023-11-13 19:51:57 +02:00
parent 64d00c8293
commit e124209da0
4 changed files with 122 additions and 7 deletions

View File

@ -1,12 +1,32 @@
from flask_admin import Admin from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView from flask_admin.contrib.sqla import ModelView
from flask_login import current_user
from app import app, db from app import app, db
from models import User, SARCall, SARCategory, GPSTrack, SARStatus, SARResult from models import User, Role, SARCall, Comment, SARCategory, GPSTrack, SARStatus, SARResult
class AdminModelView(ModelView):
def is_accessible(self):
# return current_user.is_authenticated and current_user.role.name == "admin"
return True
class UserModelView(AdminModelView):
# Display human-readable names for foreign keys
column_list = ('id', 'username', 'full_name', "email", "phone_number", "role.name", "password")
column_labels = {'role.name': 'user role'}
# Allow searching and filtering by related fields
column_searchable_list = ('role.name', 'username', 'full_name', "email", "phone_number")
column_filters = ('role.name', 'username', 'full_name', "email", "phone_number")
admin = Admin(app, name='SAR Admin', template_mode='bootstrap3') admin = Admin(app, name='SAR Admin', template_mode='bootstrap3')
admin.add_view(ModelView(User, db.session)) admin.add_view(UserModelView(User, db.session))
admin.add_view(ModelView(SARCall, db.session)) admin.add_view(AdminModelView(Role, db.session))
admin.add_view(ModelView(SARCategory, db.session)) admin.add_view(AdminModelView(SARCall, db.session))
admin.add_view(ModelView(SARStatus, db.session)) admin.add_view(AdminModelView(Comment, db.session))
admin.add_view(ModelView(SARResult, db.session)) admin.add_view(AdminModelView(GPSTrack, db.session))
admin.add_view(ModelView(GPSTrack, db.session)) admin.add_view(AdminModelView(SARCategory, db.session, category="Dictionaries"))
admin.add_view(AdminModelView(SARStatus, db.session, category="Dictionaries"))
admin.add_view(AdminModelView(SARResult, db.session, category="Dictionaries"))

View File

@ -9,6 +9,14 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(150), unique=True, nullable=False) email = db.Column(db.String(150), unique=True, nullable=False)
phone_number = db.Column(db.String(50), nullable=True) phone_number = db.Column(db.String(50), nullable=True)
password = db.Column(db.String(50)) 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'))
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):
@ -30,14 +38,20 @@ class SARCall(db.Model):
class SARCategory(db.Model): class SARCategory(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)
name = db.Column(db.String(150), unique=True, nullable=False) name = db.Column(db.String(150), unique=True, nullable=False)
def __repr__(self):
return self.name # Assuming 'name' is the field you want to display
class SARResult(db.Model): class SARResult(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)
name = db.Column(db.String(150), unique=True, nullable=False) name = db.Column(db.String(150), unique=True, nullable=False)
def __repr__(self):
return self.name # Assuming 'name' is the field you want to display
class SARStatus(db.Model): class SARStatus(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)
name = db.Column(db.String(150), unique=True, nullable=False) name = db.Column(db.String(150), unique=True, nullable=False)
def __repr__(self):
return self.name # Assuming 'name' is the field you want to display
class GPSTrack(db.Model): class GPSTrack(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -45,3 +59,12 @@ class GPSTrack(db.Model):
color = db.Column(db.String(7)) # Stores the color as a HEX code like #FF5733 color = db.Column(db.String(7)) # Stores the color as a HEX code like #FF5733
sar_call_id = db.Column(db.Integer, db.ForeignKey('sar_call.id'), nullable=False) sar_call_id = db.Column(db.Integer, db.ForeignKey('sar_call.id'), nullable=False)
sar_call = db.relationship('SARCall', backref=db.backref('gps_tracks', lazy=True)) sar_call = db.relationship('SARCall', backref=db.backref('gps_tracks', lazy=True))
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.Text, nullable=True)
gpx_data = db.Column(db.Text, nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('comments', lazy=True))
sar_call_id = db.Column(db.Integer, db.ForeignKey('sar_call.id'), nullable=False)
sar_call = db.relationship('SARCall', backref=db.backref('comments', lazy=True))

View File

@ -104,3 +104,40 @@ def delete_sar(id):
flash('SAR call record deleted successfully!', 'success') flash('SAR call record deleted successfully!', 'success')
return redirect(url_for('list_sar')) return redirect(url_for('list_sar'))
@app.route('/add_comment/<int:sar_call_id>', methods=['POST'])
@login_required
def add_comment(sar_call_id):
text = request.form.get('text')
gpx_file = request.files.get('gpx_file')
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.commit()
return redirect(url_for('view_sar', sar_call_id=sar_call_id))
@app.route('/edit_comment/<int:comment_id>', methods=['GET', 'POST'])
@login_required
def edit_comment(comment_id):
comment = Comment.query.get_or_404(comment_id)
if current_user.id != comment.user_id and current_user.id != 1 and current_user.id != comment.sar_call.user_id:
abort(403)
# Handle the form submission and save changes
if request.method == 'POST':
comment.text = request.form.get('text')
gpx_file = request.files.get('gpx_file')
if gpx_file:
comment.gpx_data = gpx_file.read().decode("utf-8")
db.session.commit()
return redirect(url_for('view_sar', sar_call_id=comment.sar_call_id))
@app.route('/delete_comment/<int:comment_id>', methods=['POST'])
@login_required
def delete_comment(comment_id):
comment = Comment.query.get_or_404(comment_id)
if current_user.id != comment.user_id and current_user.id != 1 and current_user.id != comment.sar_call.user_id:
abort(403)
db.session.delete(comment)
db.session.commit()
return redirect(url_for('view_sar', sar_call_id=comment.sar_call_id))

View File

@ -25,12 +25,47 @@
</div> </div>
<div id="map" class="mb-4"></div> <div id="map" class="mb-4"></div>
<div> <div>
<a href="{{ url_for('edit_sar', id=sar.id) }}" class="btn btn-primary">Edit</a> <a href="{{ url_for('edit_sar', id=sar.id) }}" class="btn btn-primary">Edit</a>
<a href="{{ url_for('delete_sar', id=sar.id) }}" class="btn btn-danger">Delete</a> <a href="{{ url_for('delete_sar', id=sar.id) }}" class="btn btn-danger">Delete</a>
</div> </div>
</div> </div>
<!-- Display Comments -->
{% for comment in sar_call.comments %}
<div class="comment">
<strong>{{ comment.user.username }}</strong>:
<p>{{ comment.text }}</p>
{% if comment.gpx_data %}
<!-- Display the GPX data on the map -->
{% endif %}
{% if current_user.id == comment.user_id or current_user.id == 1 or current_user.id == sar_call.user_id %}
<a href="{{ url_for('edit_comment', comment_id=comment.id) }}">Edit</a>
<a href="{{ url_for('delete_comment', comment_id=comment.id) }}">Delete</a>
{% endif %}
</div>
{% endfor %}
<!-- Add Comment Form -->
<form action="{{ url_for('add_comment', sar_call_id=sar_call.id) }}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="text">Comment:</label>
<textarea name="text" class="form-control"></textarea>
</div>
<div class="form-group">
<label for="gpx_file">GPX File:</label>
<input type="file" name="gpx_file">
</div>
<button type="submit" class="btn btn-primary">Add Comment</button>
</form>
<script> <script>
var map = L.map('map').setView([{{ sar.latitude }}, {{ sar.longitude }}], 13); var map = L.map('map').setView([{{ sar.latitude }}, {{ sar.longitude }}], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {