Browse Source

Quality of life improvements and bug squashing

* Add registration_code as a field in the DB for registered users
* Display the code for the list of registered users on the admin page
* Add a utility method to check if a registration code has uses remaining
* Update models and sql for the new field
* Add form validators for registration code and username
* Registration code must exist, not expired, and have usages remaining to be accepted
* Username must not exist as a registered user already
master
Drew Short 5 years ago
parent
commit
cdfa83ec0a
  1. 24
      app.py
  2. 24
      db.py
  3. 31
      forms.py
  4. 12
      models.py
  5. 1
      schema.sql
  6. 6
      templates/admin.html
  7. 2
      templates/register.html

24
app.py

@ -4,13 +4,14 @@ import uuid
from urllib.parse import urlparse, urljoin
import flask
from flask import Flask, redirect, render_template, request, g
from flask import Flask, redirect, render_template, request, g, flash
from flask_login import LoginManager, login_required, login_user, logout_user, UserMixin, \
current_user
from flask_wtf import CSRFProtect
from db import get_db, get_registration_codes, add_registration_code, \
expire_registration_code, delete_registration_code, get_registered_users
expire_registration_code, delete_registration_code, get_registered_users, \
add_registered_user
from forms import RegistrationForm, LoginForm, RegistrationCodeForm, \
ExpireRegistrationCodeForm
@ -53,6 +54,12 @@ def create_app():
app = create_app()
def flash_form_errors(form):
if hasattr(form, 'errors') and len(form.errors) > 0:
for error in form.errors.items():
flash("%s: %s" % (form[error[0]].label.text, error[1]), 'error')
class User(UserMixin):
username: str
token: str
@ -92,9 +99,14 @@ def index():
def registration():
form = RegistrationForm()
if form.validate_on_submit():
add_registered_user(form.registration_code.data, form.username.data)
return redirect('/success')
flash_form_errors(form)
if 'registrationCode' in request.values:
form.registration_code.data = request.values['registrationCode']
return render_template('register.html', form=form)
@ -118,6 +130,9 @@ def admin_add_registration_code():
max_usages = form.max_usages.data
add_registration_code(expiration_time, max_usages)
redirect('/admin')
flash_form_errors(form)
return redirect('/admin')
@ -131,6 +146,9 @@ def admin_expire_registration_code():
elif form.delete.data:
delete_registration_code(form.registration_code.data)
redirect('/admin')
flash_form_errors(form)
return redirect('/admin')
@ -154,6 +172,8 @@ def admin_login():
else:
return redirect('/admin')
flash_form_errors(form)
return render_template('login.html', form=form)

24
db.py

@ -2,7 +2,7 @@ import os
import sqlite3
import uuid
from datetime import datetime
from typing import Optional
from typing import Optional, Tuple
from flask import g
@ -20,8 +20,8 @@ SET expirationTime = ?, usages = ?, maxUsages = ?
WHERE code = ?"""
REGISTERED_USER_INSERT_SQL = """INSERT INTO
registered_users(username, registeredTime)
VALUES(?, ?)
registered_users(registrationCode, username, registeredTime)
VALUES(?, ?, ?)
"""
@ -47,9 +47,11 @@ def get_registration_codes() -> [RegistrationCode]:
def get_registration_code(code: str) -> Optional[RegistrationCode]:
return RegistrationCode.from_db(
_query_db("SELECT * FROM registration_codes WHERE code = ?", [code], one=True)
)
registration_code: Optional[Tuple] = _query_db(
"SELECT * FROM registration_codes WHERE code = ?", [code], one=True)
if registration_code is not None:
return RegistrationCode.from_db(registration_code)
return None
def add_registration_code(
@ -105,9 +107,11 @@ def get_registered_users() -> [RegisteredUser]:
def get_registered_user(username: str) -> Optional[RegisteredUser]:
return RegisteredUser.from_db(
_query_db("SELECT * FROM registered_users WHERE username = ?", username, one=True)
)
registered_user: Optional[Tuple] = _query_db(
"SELECT * FROM registered_users WHERE username = ?", [username], one=True)
if registered_user is not None:
return RegisteredUser.from_db(registered_user)
return None
def add_registered_user(code: str, username: str) -> RegisteredUser:
@ -115,7 +119,7 @@ def add_registered_user(code: str, username: str) -> RegisteredUser:
registration_code.usages += 1
update_registration_code(registration_code)
db = get_db()
db.execute(REGISTERED_USER_INSERT_SQL, [username, datetime.now()])
db.execute(REGISTERED_USER_INSERT_SQL, [code, username, datetime.now()])
db.commit()
return get_registered_user(username)

31
forms.py

@ -1,8 +1,10 @@
import safe
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, DateField, IntegerField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo, InputRequired, \
ValidationError, NumberRange, Optional
import safe
from db import get_registration_code, get_registered_user
def safe_password_validator(form: FlaskForm, field):
@ -11,8 +13,30 @@ def safe_password_validator(form: FlaskForm, field):
raise ValidationError("Password is not secure enough: %s" % strength.message)
def registration_code_validator(form: FlaskForm, field):
registration_code = get_registration_code(field.data)
if registration_code is None:
raise ValidationError("Registration code invalid!")
if registration_code.is_expired():
raise ValidationError("Registration code expired!")
if not registration_code.has_available_uses():
raise ValidationError("Registration code out of uses!")
def username_availability_validator(form: FlaskForm, field):
registered_user = get_registered_user(field.data)
if registered_user is not None:
raise ValidationError("Username already registered!")
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=3, max=30)])
username = StringField(
'Username',
validators=[
DataRequired(),
Length(min=3, max=30),
username_availability_validator
])
password = PasswordField(
'Password',
validators=[
@ -21,7 +45,8 @@ class RegistrationForm(FlaskForm):
safe_password_validator
])
confirm = PasswordField('Repeat Password')
registration_code = StringField('Registration Code', validators=[DataRequired()])
registration_code = StringField(
'Registration Code', validators=[DataRequired(), registration_code_validator])
class LoginForm(FlaskForm):

12
models.py

@ -29,9 +29,16 @@ class RegistrationCode:
def is_expired(self):
return self.expiration_time is not None and self.expiration_time < datetime.now()
def has_available_uses(self):
return self.usages < self.max_usages
class RegisteredUser:
def __init__(self, username: str, registered_time: datetime = datetime.now()):
def __init__(self,
registration_code: str,
username: str,
registered_time: datetime = datetime.now()):
self.registration_code = registration_code
self.username = username
self.registered_time = registered_time
@ -39,5 +46,6 @@ class RegisteredUser:
def from_db(db_registered_user: Tuple) -> 'RegisteredUser':
return RegisteredUser(
db_registered_user[0],
datetime.fromisoformat(db_registered_user[1])
db_registered_user[1],
datetime.fromisoformat(db_registered_user[2])
)

1
schema.sql

@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS registration_codes (
);
CREATE TABLE IF NOT EXISTS registered_users (
registrationCode VARCHAR(60) NOT NULL UNIQUE,
username VARCHAR(30) NOT NULL UNIQUE ,
registeredTime TIMESTAMP NOT NULL
);

6
templates/admin.html

@ -25,7 +25,7 @@
<td>{{ registration_code.usages|tojson|safe }}</td>
<td>{{ registration_code.max_usages|tojson|safe }}</td>
<td>
{% if not registration_code.is_expired() %}
{% if not registration_code.is_expired() and registration_code.has_available_uses() %}
<a href="/register?registrationCode={{ registration_code.code }}">link</a>
{% endif %}
<td>
@ -50,12 +50,14 @@
<thead>
<th>User</th>
<th>Registration Time</th>
<th>Registration Code</th>
</thead>
<tbody>
{% for registered_user in registered_users %}
<tr>
<td>{{ registered_user.username|tojson|safe }}</td>
<td>{{ registered_user.registration_time|tojson|safe }}</td>
<td>{{ registered_user.registered_time|tojson|safe }}</td>
<td>{{ registered_user.registration_code|tojson|safe }}</td>
</tr>
{% endfor %}
</tbody>

2
templates/register.html

@ -11,6 +11,6 @@
{{ form.password.label }} {{ form.password() }}
{{ form.confirm.label }} {{ form.confirm() }}
{{ form.registration_code.label }} {{ form.registration_code() }}
<input type="submit" value="Go">
<input type="submit" value="Register">
</form>
{% endblock %}
Loading…
Cancel
Save