8 Commits
v0.3 ... dev

Author SHA1 Message Date
186f257704 added 8 to ambiguous characters (looks like B) 2019-07-10 19:40:14 +01:00
6c14e13d08 fix conditional if for testing 2019-07-10 19:36:22 +01:00
8db931a4c7 added spam removal 2019-07-10 19:31:11 +01:00
1067b4fc3b tidying up after dev 2019-07-06 12:52:47 +01:00
ae825d0342 Merge branch 'dev' into 'master'
Created database class.

Closes #2

See merge request acid/wiganhbc-competition!2
2019-07-06 11:46:52 +00:00
4da511a7d5 Created database class.
Tidied app.py so that it only contains controller logic.
Added error handling for if number of entrants exceeds number of possible identifiers
Updated test coverage to test for all identifiers, as well as error case
2019-07-06 12:43:49 +01:00
198cfb4bdb Merge branch 'dev' into 'master'
Closes #3

Closes #3

See merge request acid/wiganhbc-competition!1
2019-07-06 08:49:21 +00:00
9c42cb7890 Closes #3
Fixes issue with numeric identifiers not being handled as strings
2019-07-06 09:47:00 +01:00
7 changed files with 175 additions and 56 deletions

View File

@@ -33,7 +33,7 @@
![Screen Shot](screenshot.PNG) ![Screen Shot](screenshot.PNG)
This project is to allow homebrew club members to be given an identifying number/letter to put on their bottles for competitions. The goal is to allow all members to take part with no invigilator/organiser required. This project is to allow homebrew club members to be given an identifying number/letter to put on their bottles for competitions. The goal is to allow all members to take part with no invigilator/organiser required. It support around 100 entries (alphabetical identifiers are given first, then numerical)
### Built With ### Built With
This project is built in python and deployed in docker. This project is built in python and deployed in docker.

View File

@@ -1,6 +1,15 @@
import pytest import pytest
import sys, os
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.append(myPath + '/../web/')
try:
os.remove('./tests/hbc.db')
except:
pass
from web import app from web import app
@pytest.fixture @pytest.fixture
def client(): def client():
app.app.config['TESTING'] = True app.app.config['TESTING'] = True
@@ -8,18 +17,24 @@ def client():
yield client yield client
def test_root_page(client):
def test_root_page(client):
rv = client.get('/') rv = client.get('/')
assert rv.status_code == 200 assert rv.status_code == 200
assert b'Please enter your first name and initial' in rv.data assert b'Please enter your first name and initial' in rv.data
def test_generate_first(client):
rv2 = client.post('/generate', data=dict(name='tester'), follow_redirects=True)
assert rv2.status_code == 200
assert b'Please mark all of your bottlecaps with the following identifier: <strong>A' in rv2.data
def test_generate_second(client): def test_generate(client):
for identifier in app.my_db.get_identifiers_list():
rv = client.post('/generate', data=dict(name='tester'), follow_redirects=True) rv = client.post('/generate', data=dict(name='tester'), follow_redirects=True)
assert rv.status_code == 200 assert rv.status_code == 200
assert b'Please mark all of your bottlecaps with the following identifier: <strong>B' in rv.data assert b'Please mark all of your bottlecaps with the following identifier: <strong>' \
+ str(identifier).encode('UTF-8') in rv.data
def test_generate_over_limit(client):
rv = client.post('/generate', data=dict(name='tester'), follow_redirects=True)
assert rv.status_code == 200
assert b'Maximum entry limit reached - please contact Sean or Joe' in rv.data

View File

@@ -1,52 +1,16 @@
from flask import Flask, render_template, request from flask import Flask, render_template, request
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
import sqlite3 import db
import os import config
import string import utils
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config.BaseConfig)
Bootstrap(app) Bootstrap(app)
my_db = db.Database(app.config['DB_PATH'])
def get_db_connection():
db_path = os.environ.get('HBC_DB_PATH')
if not db_path:
raise Exception("DB Path not defined")
return sqlite3.connect(db_path + '/hbc.db')
def db_setup():
conn = get_db_connection()
sql_create_table = """ CREATE TABLE IF NOT EXISTS brewers (
id integer PRIMARY KEY,
name text NOT NULL,
identifier text NOT NULL );"""
c = conn.cursor()
c.execute(sql_create_table)
conn.close()
db_setup()
brew_name = "Fruit Beer" brew_name = "Fruit Beer"
brew_month = "October" brew_month = "October"
identifiers = list(string.ascii_uppercase) + list(range(1, 100))
def get_identifier(name):
conn = get_db_connection()
c = conn.cursor()
for identifier in identifiers:
c.execute('''SELECT identifier FROM brewers WHERE identifier=?''', (str(identifier),))
data = c.fetchone()
if data is None:
print("Assigning " + identifier + " to " + name)
c.execute("INSERT INTO brewers (name,identifier) VALUES(?, ?)",(name, identifier) )
conn.commit()
conn.close()
return identifier
@app.route('/') @app.route('/')
@@ -56,8 +20,32 @@ def hello_world():
@app.route('/generate', methods=["POST"]) @app.route('/generate', methods=["POST"])
def generate(): def generate():
identifier = get_identifier(request.form['name']) try:
return render_template('generate.html', brew_name=brew_name, brew_month=brew_month, identifier=identifier) ip = utils.get_ip(request)
pass
except:
ip = ''
pass
if ip != '5.135.188.148' and ip != '178.32.58.160':
try:
error = None
identifier = my_db.get_identifier(request.form['name'])
pass
except StopIteration:
identifier = ''
error = 'Maximum entry limit reached - please contact Sean or Joe'
pass
else:
error = None
identifier = 'F'
return render_template('generate.html', brew_name=brew_name, brew_month=brew_month, identifier=identifier, error=error)
@app.route('/getip')
def getip():
return utils.get_ip(request)
if __name__ == '__main__': if __name__ == '__main__':

6
web/config.py Normal file
View File

@@ -0,0 +1,6 @@
import os
class BaseConfig(object):
TESTING = True
DB_PATH = os.environ.get('HBC_DB_PATH', 'tests/')

92
web/db.py Normal file
View File

@@ -0,0 +1,92 @@
import sqlite3
import string
import utils
class Database:
def __init__(self, db_path):
if not db_path:
raise Exception("DB Path not defined")
self.db_path = db_path
self.identifiers = self.get_identifiers_list()
self.setup_database_tables()
@staticmethod
def get_identifiers_list():
"""Returns a list of non ambiguous identifiers"""
numbers = list(range(1, 100))
identifiers = list(string.ascii_uppercase) + [str(item) for item in
numbers] # All identifiers are treated as strings
ambiguous = ['I', 'O', 'V', '1', '8', '5', '9', '99']
utils.remove_common_elements(identifiers, ambiguous)
return identifiers
def get_connection(self):
"""Returns sqlite db connection when provided with base directory"""
return sqlite3.connect(self.db_path + '/hbc.db')
def setup_database_tables(self):
"""Creates sqlite database and set up the sqlite table if it doesnt already exist"""
conn = self.get_connection()
sql_create_table = """ CREATE TABLE IF NOT EXISTS brewers (
id integer PRIMARY KEY,
name text NOT NULL,
identifier text NOT NULL );"""
c = conn.cursor()
c.execute(sql_create_table)
conn.close()
def get_identifier(self, name):
"""Returns the next availible identifier, passing the result through record_entry to make sure it is not reused"""
conn = self.get_connection()
c = conn.cursor()
c.execute('''select identifier from brewers ORDER BY id DESC LIMIT 1;''')
identifier_search_result = c.fetchone()
conn.close()
if identifier_search_result is None:
return self.record_entry(self.identifiers[0], name)
else:
if identifier_search_result[0] == self.identifiers[-1]:
raise StopIteration
else:
i = self.identifiers.index(identifier_search_result[0])
return self.record_entry(self.identifiers[i + 1], name)
def record_entry(self, identifier, name):
"""Returns identifier after recording entry in sqlite database"""
conn = self.get_connection()
c = conn.cursor()
c.execute("INSERT INTO brewers (name,identifier) VALUES(?, ?)", (name, identifier))
conn.commit()
conn.close()
return identifier
# def get_identifier(name, db_path):
# conn = get_connection(db_path)
# c = conn.cursor()
# c.execute('''select identifier from brewers ORDER BY id DESC LIMIT 1;''')
# identifier_search_result = c.fetchone()
# conn.close()
#
# if identifier_search_result is None:
# return record_entry(DataStore.identifiers[0], name, db_path)
# else:
# if identifier_search_result[0] == DataStore.identifiers[-1]:
# raise StopIteration
# else:
# i = DataStore.identifiers.index(identifier_search_result[0])
# return record_entry(DataStore.identifiers[i+1], name, db_path)
# def record_entry(identifier, name, db_path):
# conn = get_connection(db_path)
# c = conn.cursor()
# c.execute("INSERT INTO brewers (name,identifier) VALUES(?, ?)", (name, identifier))
# conn.commit()
# conn.close()
# return identifier

View File

@@ -16,10 +16,15 @@
<div class="row"> <div class="row">
<div class="col-md-2"></div> <div class="col-md-2"></div>
<div class="col-md-8 content-div"> <div class="col-md-8 content-div">
{% if error == None %}
<p>You have been entered into the competition to brew a <strong>{{ brew_name }}</strong>, which will be judged at the meeting in <strong>{{ brew_month }}</strong>.</p> <p>You have been entered into the competition to brew a <strong>{{ brew_name }}</strong>, which will be judged at the meeting in <strong>{{ brew_month }}</strong>.</p>
<p>To make sure your beer isn't easily identifiable, most people tend to use brown bottles of either 330ml or 500ml size. You probably don't want to put your own label on the bottle!</p> <p>To make sure your beer isn't easily identifiable, most people tend to use brown bottles of either 330ml or 500ml size. You probably don't want to put your own label on the bottle!</p>
<br/> <br/>
<div class="alert alert-success lead" role="alert"> <span class="glyphicon glyphicon-tags" aria-hidden="true"></span> &nbsp; &nbsp;Please mark all of your bottlecaps with the following identifier: <strong>{{ identifier }}</strong></div> <div class="alert alert-success lead" role="alert"> <span class="glyphicon glyphicon-tags" aria-hidden="true"></span> &nbsp; &nbsp;Please mark all of your bottlecaps with the following identifier: <strong>{{ identifier }}</strong></div>
{% else %}
<p>Unfortunately it has not been possible to enter you into the competition at this time</p>
<div class="alert alert-danger lead" role="alert"> {{ error }} </div>
{% endif %}
</div> </div>
</div> </div>

13
web/utils.py Normal file
View File

@@ -0,0 +1,13 @@
def remove_common_elements(a, b):
"""Removes the common elements from both supplied lists (in place), doesnt not return a new list"""
for e in a[:]:
if e in b:
a.remove(e)
b.remove(e)
def get_ip(request):
if request.headers.getlist("X-Forwarded-For"):
return request.headers.getlist("X-Forwarded-For")[0]
else:
return request.remote_addr