From 4da511a7d53aa02cb60b695c36d7dee428007070 Mon Sep 17 00:00:00 2001 From: AcidUK Date: Sat, 6 Jul 2019 12:43:49 +0100 Subject: [PATCH] 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 --- README.md | 2 +- tests/app_test.py | 29 +++++++++--- web/app.py | 60 +++++++----------------- web/config.py | 6 +++ web/db.py | 92 +++++++++++++++++++++++++++++++++++++ web/templates/generate.html | 13 ++++-- web/utils.py | 6 +++ 7 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 web/config.py create mode 100644 web/db.py create mode 100644 web/utils.py diff --git a/README.md b/README.md index 8144c54..4649288 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ ![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 This project is built in python and deployed in docker. diff --git a/tests/app_test.py b/tests/app_test.py index 10b30a2..28722da 100644 --- a/tests/app_test.py +++ b/tests/app_test.py @@ -1,6 +1,15 @@ 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 + @pytest.fixture def client(): app.app.config['TESTING'] = True @@ -8,18 +17,24 @@ def client(): yield client -def test_root_page(client): +def test_root_page(client): rv = client.get('/') assert rv.status_code == 200 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: 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) + assert rv.status_code == 200 + assert b'Please mark all of your bottlecaps with the following identifier: ' \ + + 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'Please mark all of your bottlecaps with the following identifier: B' in rv.data \ No newline at end of file + assert b'Maximum entry limit reached - please contact Sean or Joe' in rv.data + + diff --git a/web/app.py b/web/app.py index 4feaf4c..ffcb6d2 100644 --- a/web/app.py +++ b/web/app.py @@ -1,52 +1,16 @@ from flask import Flask, render_template, request from flask_bootstrap import Bootstrap -import sqlite3 -import os -import string - +import db +import config +import pprint app = Flask(__name__) +app.config.from_object(config.BaseConfig) 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_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: - identifier = str(identifier) - c.execute('''SELECT identifier FROM brewers WHERE identifier=?''', (identifier,)) - data = c.fetchone() - if data is None: - c.execute("INSERT INTO brewers (name,identifier) VALUES(?, ?)", (name, identifier)) - conn.commit() - conn.close() - return identifier @app.route('/') @@ -56,9 +20,19 @@ def hello_world(): @app.route('/generate', methods=["POST"]) def generate(): - identifier = get_identifier(request.form['name']) - return render_template('generate.html', brew_name=brew_name, brew_month=brew_month, identifier=identifier) + 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 + return render_template('generate.html', brew_name=brew_name, brew_month=brew_month, identifier=identifier, error=error) if __name__ == '__main__': app.run(debug=True, host='0.0.0.0') + + + diff --git a/web/config.py b/web/config.py new file mode 100644 index 0000000..d90b8c8 --- /dev/null +++ b/web/config.py @@ -0,0 +1,6 @@ +import os + + +class BaseConfig(object): + TESTING = True + DB_PATH = os.environ.get('HBC_DB_PATH', 'tests/') diff --git a/web/db.py b/web/db.py new file mode 100644 index 0000000..8804fd1 --- /dev/null +++ b/web/db.py @@ -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', '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 \ No newline at end of file diff --git a/web/templates/generate.html b/web/templates/generate.html index 808740c..ef746fc 100644 --- a/web/templates/generate.html +++ b/web/templates/generate.html @@ -16,10 +16,15 @@
-

You have been entered into the competition to brew a {{ brew_name }}, which will be judged at the meeting in {{ brew_month }}.

-

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!

-
- + {% if error == None %} +

You have been entered into the competition to brew a {{ brew_name }}, which will be judged at the meeting in {{ brew_month }}.

+

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!

+
+ + {% else %} +

Unfortunately it has not been possible to enter you into the competition at this time

+ + {% endif %}
diff --git a/web/utils.py b/web/utils.py new file mode 100644 index 0000000..09f1749 --- /dev/null +++ b/web/utils.py @@ -0,0 +1,6 @@ +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)