Browse Source

added non-admin users

master
Juni Kim 1 year ago
parent
commit
cce2ee1bc1
  1. 107
      app.py
  2. 51
      migrations/versions/282564545160_changed_user_schema.py
  3. 51
      templates/account_edit.html
  4. 34
      templates/account_view.html
  5. 4
      templates/base.html
  6. 23
      templates/register.html

107
app.py

@ -26,6 +26,16 @@ migrate = Migrate()
db.init_app(app)
migrate.init_app(app, db)
# Helper Methods
def object_as_dict(obj):
return {c.key: getattr(obj, c.key)
for c in inspect(obj).mapper.column_attrs}
# Model Forms
BaseModelForm = model_form_factory(FlaskForm)
@ -35,10 +45,19 @@ class ModelForm(BaseModelForm):
return db.session
class Admin(db.Model):
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
name = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
institution = db.Column(db.String, unique=True, nullable=False)
position = db.Column(db.String, unique=True, nullable=False)
admin = db.Column(db.Boolean)
# for type annotations
query: db.Query
@classmethod
@ -47,31 +66,28 @@ class Admin(db.Model):
@classmethod
def authenticate(cls, username: str, pw: str):
user = Admin.query.filter_by(username=username).one_or_none()
user = User.query.filter_by(username=username).one_or_none()
if user and bcrypt.checkpw(pw, user.password):
session['admin'] = user.username
session['user'] = user.username
if user.admin:
session['admin'] = user.username
return user
else:
return None
@classmethod
def exists(cls):
user = Admin.query.first()
def admin_exists(cls):
user = User.query.filter_by(admin=True).first()
return True if user else False
@classmethod
def authorize(cls):
if "admin" not in session:
return redirect(url_for("admin_login"))
def authorize_or_redirect(cls, admin=True):
if (admin and "admin" not in session) or "user" not in session:
return redirect(url_for("accounts_create"))
else:
return None
def object_as_dict(obj):
return {c.key: getattr(obj, c.key)
for c in inspect(obj).mapper.column_attrs}
class Chemical(db.Model):
query: db.Query
id = db.Column(db.Integer, primary_key=True)
@ -126,15 +142,15 @@ def handler_403(msg):
# Admin routes
@app.route('/admin')
def admin_root():
if login := Admin.authorize():
if login := User.authorize_or_redirect():
return login
return render_template("admin.html", user=session.get("admin"))
@app.route('/admin/create', methods=['GET', 'POST'])
def admin_create():
if Admin.exists():
if login := Admin.authorize():
@app.route('/accounts/create', methods=['GET', 'POST'])
def accounts_create():
if User.admin_exists():
if login := User.authorize_or_redirect():
return login
if request.method == "GET":
return render_template("register.html")
@ -143,21 +159,51 @@ def admin_create():
'username'), request.form.get('password')
if username is None or pw is None:
return render_template("register.html", fail="Invalid Input.")
elif db.session.execute(db.select(Admin).filter_by(username=username)).fetchone():
elif db.session.execute(db.select(User).filter_by(username=username)).fetchone():
return render_template("register.html", fail="Username already exists.")
else:
db.session.add(
Admin(username=username, password=Admin.generate_password(pw)))
# because the IDE complains about type mismatches
form = {} | request.form
form['password'] = User.generate_password(pw)
form['admin'] = (True if form['admin'] == 'y' else False)
form.pop('reconfirm')
user = User(**form)
db.session.add(user)
db.session.commit()
return render_template("register.html", success=True)
@app.route('/admin/login', methods=['GET', 'POST'])
def admin_login():
@app.route('/accounts/edit', methods=['GET', 'POST'])
def accounts_edit():
if login := User.authorize_or_redirect(admin=False):
return login
user = User.query.filter_by(username=session.get('user')).one_or_404()
if request.method == "GET":
return render_template("account_edit.html", user=object_as_dict(user))
else:
dct = object_as_dict(user)
# update all of the changes
for key in request.form:
if key in dct:
setattr(user, key, request.form[key])
db.session.commit()
return render_template("account_edit.html", user=object_as_dict(user), success=True)
@app.route('/accounts/view/<int:id>')
def accounts_edit_admin(id):
if login := User.authorize_or_redirect(admin=True):
return login
user = User.query.filter_by(id=id).one_or_404()
return render_template("account_view.html", user=object_as_dict(user))
@app.route('/accounts/login', methods=['GET', 'POST'])
def login():
if request.method == "POST":
username, pw = request.form.get(
'username', ''), request.form.get('password', '')
if Admin.authenticate(username, pw):
if User.authenticate(username, pw):
return render_template("login.html", success=True)
else:
return render_template("login.html", fail="Could not authenticate.")
@ -165,19 +211,21 @@ def admin_login():
return render_template("login.html")
@app.route('/admin/logout', methods=['GET'])
def admin_logout():
@app.route('/accounts/logout', methods=['GET'])
def logout():
if "admin" in session:
session.pop('admin')
if "user" in session:
session.pop('user')
return redirect(url_for('home'))
@app.route("/")
def home():
if Admin.exists():
if User.admin_exists():
return render_template("index.html")
else:
return redirect(url_for("admin_create"))
return redirect(url_for("accounts_create"))
# Routes for CRUD operations on chemicals
@ -318,9 +366,10 @@ def batch_add_request():
return render_template("batchadd.html")
# regular users can batch search.
@app.route("/chemical/batch", methods=["GET", "POST"])
def batch_query_request():
if not session.get('admin'):
if not session.get('user'):
abort(403)
if request.method == "POST":
if "input" not in request.files or request.files["input"].filename == '':

51
migrations/versions/282564545160_changed_user_schema.py

@ -0,0 +1,51 @@
"""changed user schema
Revision ID: 282564545160
Revises: 1a0a82192181
Create Date: 2023-05-20 16:07:52.967576
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '282564545160'
down_revision = '1a0a82192181'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('password', sa.String(), nullable=False),
sa.Column('institution', sa.String(), nullable=False),
sa.Column('position', sa.String(), nullable=False),
sa.Column('admin', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('institution'),
sa.UniqueConstraint('name'),
sa.UniqueConstraint('position'),
sa.UniqueConstraint('username')
)
op.drop_table('admin')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('admin',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('username', sa.VARCHAR(), nullable=False),
sa.Column('password', sa.VARCHAR(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('username')
)
op.drop_table('user')
# ### end Alembic commands ###

51
templates/account_edit.html

@ -0,0 +1,51 @@
{% extends "base.html" %}
{% block content %}
<h1>Edit Account Profile</h1>
<table>
<tr>
<td>Id</td>
<td>{{user.id}}</td>
</tr>
<tr>
<td>Username</td>
<td>{{user.username}}</td>
</tr>
<tr>
<td>Admin</td>
<td>{% if user.admin %} Yes {% else %} False {% endif %}</td>
</tr>
</table>
<form method="post">
<label for="name">Name:</label>
<input id="name" name="name" type="text" required="required"
value="{{user.name | safe}}">
<br>
<label for="email">Email:</label>
<input id="email" name="email" type="text" required="required"
value="{{user.email | safe}}">
<br>
<label for="institution">Institution:</label>
<input id="institution" name="institution" type="text"
required="required"
value="{{user.institution |
safe}}">
<br>
<label for="position">Position:</label>
<input id="position" name="position" type="text" required="required"
value="{{user.position
| safe}}">
<br>
<br>
<input type="submit" value="Submit" id="submit">
</form>
{% if success %}
<p style="color:darkgreen">Edits Successful!</p>
{% elif fail %}
<p style="color:darkred">Edits Failed. Please try again. {{fail}}</p>
{% endif %}
{% endblock %}

34
templates/account_view.html

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block content %}
<h1>View User</h1>
<table>
<tr>
<td>Id</td>
<td>{{user.id}}</td>
</tr>
<tr>
<td>Username</td>
<td>{{user.username | safe}}</td>
</tr>
<tr>
<td>Admin</td>
<td>{% if user.admin %} Yes {% else %} False {% endif %}</td>
</tr>
<tr>
<td>Name</td>
<td>{{user.name | safe}}</td>
</tr>
<tr>
<td>Email</td>
<td>{{user.email | safe}}</td>
</tr>
<tr>
<td>Institution</td>
<td>{{user.institution | safe}}</td>
</tr>
<tr>
<td>Position</td>
<td>{{user.position | safe}}</td>
</tr>
</table>
{% endblock %}

4
templates/base.html

@ -18,9 +18,9 @@
<a href="{{ url_for('search') }}">Search</a>
{% if session.admin %}
<a href="{{ url_for('admin_root') }}">Admin</a>
<a href="{{ url_for('admin_logout') }}">Logout</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('admin_login') }}">Login</a>
<a href="{{ url_for('login') }}">Login</a>
{% endif %}
</nav>
</header>

23
templates/register.html

@ -28,6 +28,27 @@
<label for="username">Username:</label>
<input id="username" name="username" type="text" required="required">
<br>
<label for="name">Name:</label>
<input id="name" name="name" type="text" required="required">
<br>
<label for="email">Email:</label>
<input id="email" name="email" type="text" required="required">
<br>
<label for="institution">Institution:</label>
<input id="institution" name="institution" type="text" required="required">
<br>
<label for="position">Position:</label>
<input id="position" name="position" type="text" required="required">
<br>
<label for="admin">Admin:</label>
<input id="admin" name="admin" type="checkbox" value="y">
<br>
<label for="password">Password:</label>
<input id="password" name="password" type="password" required="required">
<br>
@ -41,4 +62,4 @@
{% elif fail %}
<p style="color:darkred">Login Failed. Please try again. {{fail}}</p>
{% endif %}
{% endblock %}
{% endblock %}
Loading…
Cancel
Save