diff --git a/configurer.py b/configurer.py new file mode 100644 index 0000000..9da8547 --- /dev/null +++ b/configurer.py @@ -0,0 +1,36 @@ +# coding=utf-8 +import os +from xml.dom.minidom import parse + + +def parse_xml_config(): + print "XML Configuration..." + dom = parse('settings.xml') + database = dom.getElementsByTagName('database')[0].firstChild.nodeValue + reader = dom.getElementsByTagName('reader')[0].firstChild.nodeValue + return database.strip(), reader.strip() + +if os.path.isfile('settings.xml'): + DATABASE, READER = parse_xml_config() +else: + from settings import DATABASE, READER + +if DATABASE == "Pickle": + from contacts.pickle_contacts import PickleContacts as Contacts +elif DATABASE == "Redis": + from contacts.redis_contacts import RedisContacts as Contacts +elif DATABASE == "MySQL": + from contacts.mysql_contacts import MysqlContacts as Contacts +elif DATABASE == "SQLAlchemy": + from contacts.sqlalchemy_contacts import SQLAlchemyContacts as Contacts +else: + print "Invalid database" + exit() + +if READER == 'Console': + from reader.console_reader import ConsoleReader as DefaultReader +else: + from reader.file_reader import FileReader as DefaultReader + +contacts = Contacts() +reader = DefaultReader() diff --git a/console_controller.py b/console_controller.py index f6dddb6..28cc575 100644 --- a/console_controller.py +++ b/console_controller.py @@ -1,28 +1,29 @@ import functools from console_wrappers import ask_create_contacts, ask_find_contact, ask_delete_contact, ask_update_contact -from pickle_model import Contacts - -my_contacts = Contacts() +from configurer import contacts, reader controller = { - 'c': functools.partial(ask_create_contacts, my_contacts), - 'f': functools.partial(ask_find_contact, my_contacts), - 'd': functools.partial(ask_delete_contact, my_contacts), - 'u': functools.partial(ask_update_contact, my_contacts), + 'c': ask_create_contacts, + 'f': ask_find_contact, + 'd': ask_delete_contact, + 'u': ask_update_contact, } +controller = {key: functools.partial(value, contacts, reader) for key, value in controller.items()} def default(): print "Invalid action" - + + if __name__ == '__main__': try: while True: - action = raw_input("Action?") + action = reader.ask_action() if action in "Qq": break controller.get(action.lower(), default)() finally: - my_contacts.save_contacts() \ No newline at end of file + contacts.save_contacts() + reader.close() \ No newline at end of file diff --git a/console_wrappers.py b/console_wrappers.py index d73a21c..afbcb54 100644 --- a/console_wrappers.py +++ b/console_wrappers.py @@ -1,38 +1,34 @@ - -def ask_name(): - return raw_input('name?') - -def ask_phone(): - return raw_input('phone?') - -def ask_create_contacts(f_contacts): - name = ask_name() - phone = ask_phone() +def ask_create_contacts(contacts, reader): + name = reader.ask_name() + phone = reader.ask_phone() try: - f_contacts.create_contact(name, phone) + contacts.create_contact(name, phone) except ValueError as e: - print e + print e + -def ask_find_contact(f_contacts): - name = ask_name() +def ask_find_contact(contacts, reader): + name = reader.ask_name() try: - print name, f_contacts.find_contact(name) + print name, contacts.find_contact(name) except ValueError as e: print e -def ask_delete_contact(f_contacts): - name = ask_name() + +def ask_delete_contact(contacts, reader): + name = reader.ask_name() try: - f_contacts.delete_contact(name) + contacts.delete_contact(name) except ValueError as e: print e - -def ask_update_contact(f_contacts): - name = ask_name() - phone = ask_phone() + + +def ask_update_contact(contacts, reader): + name = reader.ask_name() + phone = reader.ask_phone() try: - f_contacts.update_contact(name, phone) + contacts.update_contact(name, phone) except ValueError as e: print e diff --git a/contacts.dat b/contacts.dat index f67ed38..5acebab 100644 --- a/contacts.dat +++ b/contacts.dat @@ -1,6 +1,14 @@ (dp0 S'Bob' p1 -S'111' +S'123' p2 +sS'Bill' +p3 +S'223' +p4 +sS'John' +p5 +S'111' +p6 s. \ No newline at end of file diff --git a/contacts/__init__.py b/contacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/contacts/abstract_contacts.py b/contacts/abstract_contacts.py new file mode 100644 index 0000000..f069df2 --- /dev/null +++ b/contacts/abstract_contacts.py @@ -0,0 +1,35 @@ +# coding=utf-8 +import abc + + +class AbstractModel(object): + __metaclass__ = abc.ABCMeta + NOT_EXIST_MESSAGE = "Name doesn't exist" + + @abc.abstractmethod + def load_contacts(self): + pass + + @abc.abstractmethod + def save_contacts(self): + pass + + @abc.abstractmethod + def create_contact(self, name, phone): + pass + + @abc.abstractmethod + def find_contact(self, name): + pass + + @abc.abstractmethod + def delete_contact(self, name): + pass + + @abc.abstractmethod + def update_contact(self, name, phone): + pass + + @abc.abstractmethod + def list_contacts(self): + pass diff --git a/contacts/mysql_contacts.py b/contacts/mysql_contacts.py new file mode 100644 index 0000000..2154668 --- /dev/null +++ b/contacts/mysql_contacts.py @@ -0,0 +1,48 @@ +# coding=utf-8 +import MySQLdb +from abstract_contacts import AbstractModel + + +class MysqlContacts(AbstractModel): + db = MySQLdb.connect(host='localhost', user='root', passwd='1', db='phones') + + def __init__(self): + self.cursor = self.db.cursor() + + def load_contacts(self): + pass + + def find_contact(self, name): + if not self.cursor.execute("select phone from contacts where name='{}'".format(name)): + raise ValueError(self.NOT_EXIST_MESSAGE) + return self.cursor.fetchone()[0] + + def delete_contact(self, name): + if not self.cursor.execute("delete from contacts where name='{}'".format(name)): + raise ValueError(self.NOT_EXIST_MESSAGE) + self.cursor.execute("commit") + + def save_contacts(self): + self.cursor.close() + self.db.close() + + def update_contact(self, name, phone): + if not self.cursor.execute("update contacts set phone='{}' where name='{}'".format(phone, name)): + raise ValueError(self.NOT_EXIST_MESSAGE) + self.cursor.execute("commit") + + def create_contact(self, name, phone): + try: + self.find_contact(name) + except ValueError: + pass + else: + raise ValueError("Name exists") + self.cursor.execute("insert into contacts (name, phone) values ('{}', '{}')".format(name, phone)) + self.cursor.execute("commit") + + def list_contacts(self): + self.cursor.execute("select name, phone from contacts") + return tuple( + [(row[0], row[1]) for row in self.cursor.fetchall()] + ) diff --git a/pickle_model.py b/contacts/pickle_contacts.py similarity index 82% rename from pickle_model.py rename to contacts/pickle_contacts.py index fa3ab99..1c41465 100644 --- a/pickle_model.py +++ b/contacts/pickle_contacts.py @@ -1,5 +1,8 @@ import pickle +from abstract_contacts import AbstractModel + + def key_exists(f): def wrapper(self, name, *args): if name not in self.contacts: @@ -8,7 +11,7 @@ def wrapper(self, name, *args): return wrapper -class Contacts(object): +class PickleContacts(AbstractModel): FILENAME = 'contacts.dat' def __init__(self): @@ -18,33 +21,30 @@ def load_contacts(self): try: with open(self.FILENAME, 'r') as f: return pickle.load(f) - except IOError, EOFError: + except (IOError, EOFError): return {} - def save_contacts(self): with open(self.FILENAME, 'w') as f: pickle.dump(self.contacts, f) - + print "Contacts saved" def create_contact(self, name, phone): if name in self.contacts: raise ValueError("Name exists") self.contacts[name] = phone - @key_exists def find_contact(self, name): return self.contacts[name] - @key_exists def delete_contact(self, name): del self.contacts[name] - @key_exists def update_contact(self, name, phone): self.contacts[name] = phone - + def list_contacts(self): + return tuple(self.contacts.items()) diff --git a/contacts/redis_contacts.py b/contacts/redis_contacts.py new file mode 100644 index 0000000..e508234 --- /dev/null +++ b/contacts/redis_contacts.py @@ -0,0 +1,40 @@ +# coding=utf-8 +import redis + +from abstract_contacts import AbstractModel + + +class RedisContacts(AbstractModel): + + rc = redis.StrictRedis() + + def load_contacts(self): + pass + + def find_contact(self, name): + phone = self.rc.get(name) + if phone is None: + raise ValueError(self.NOT_EXIST_MESSAGE) + return phone + + def delete_contact(self, name): + if not self.rc.delete(name): + raise ValueError(self.NOT_EXIST_MESSAGE) + + def save_contacts(self): + pass + + def update_contact(self, name, phone): + if not self.rc.exists(name): + raise ValueError(self.NOT_EXIST_MESSAGE) + self.rc.set(name, phone) + + def create_contact(self, name, phone): + if self.rc.exists(name): + raise ValueError("Name exists") + self.rc.set(name, phone) + + def list_contacts(self): + names = self.rc.keys("*") + contacts = [(name, self.rc.get(name)) for name in names] + return tuple(contacts) diff --git a/contacts/sqlalchemy_contacts.py b/contacts/sqlalchemy_contacts.py new file mode 100644 index 0000000..bee3cb8 --- /dev/null +++ b/contacts/sqlalchemy_contacts.py @@ -0,0 +1,47 @@ +# coding=utf-8 +from abstract_contacts import AbstractModel + +from sqlalchemy import create_engine +from sqlalchemy.ext.automap import automap_base +from sqlalchemy.orm import Session + + +class SQLAlchemyContacts(AbstractModel): + engine = create_engine('mysql://root:1@localhost/phones') + + def __init__(self): + self.session = Session(self.engine) + Base = automap_base() + Base.prepare(self.engine, reflect=True) + self.Contacts = Base.classes.contacts + + def create_contact(self, name, phone): + self.session.add(self.Contacts(name=name, phone=phone)) + self.session.commit() + + def delete_contact(self, name): + if not self.session.query(self.Contacts).filter_by(name=name).delete(): + raise ValueError(self.NOT_EXIST_MESSAGE) + self.session.commit() + + def find_contact(self, name): + contacts = self.session.query(self.Contacts).filter_by(name=name).all() + if contacts: + return contacts[0].phone + raise ValueError(self.NOT_EXIST_MESSAGE) + + def save_contacts(self): + pass + + def load_contacts(self): + pass + + def update_contact(self, name, phone): + if not self.session.query(self.Contacts).filter_by(name=name).update({'phone': phone}): + raise ValueError(self.NOT_EXIST_MESSAGE) + self.session.commit() + + def list_contacts(self): + return tuple( + [(c.name, c.phone) for c in self.session.query(self.Contacts).all()] + ) diff --git a/desktop_controller.py b/desktop_controller.py new file mode 100644 index 0000000..656a1a9 --- /dev/null +++ b/desktop_controller.py @@ -0,0 +1,94 @@ +# coding=utf-8 +from Tkinter import * +import tkMessageBox + +from configurer import contacts + + +root = Tk() +tl = None + +l_name = Label(root, text="Name") +l_name.grid(row=0, column=0) +e_name = Entry(root) +e_name.grid(row=0, column=1) +l_phone = Label(root, text="Phone") +l_phone.grid(row=1, column=0) +e_phone = Entry(root) +e_phone.grid(row=1, column=1) + + +def clear_entries(): + e_name.delete(0, END) + e_phone.delete(0, END) + + +def ask_create_contact(): + try: + contacts.create_contact(e_name.get(), e_phone.get()) + clear_entries() + tkMessageBox.showinfo("Success", "Contact added") + except ValueError as e: + tkMessageBox.showerror("Error", e) + + +def ask_find_contact(): + try: + phone = contacts.find_contact(e_name.get()) + e_phone.delete(0, END) + e_phone.insert(0, phone) + except ValueError as e: + tkMessageBox.showerror("Error", e) + + +def ask_update_contact(): + try: + contacts.update_contact(e_name.get(), e_phone.get()) + clear_entries() + tkMessageBox.showinfo("Success", "Contact updated") + except ValueError as e: + tkMessageBox.showerror("Error", e) + + +def ask_delete_contact(): + try: + contacts.delete_contact(e_name.get()) + clear_entries() + tkMessageBox.showinfo("Success", "Contact deleted") + except ValueError as e: + tkMessageBox.showerror("Error", e) + + +def ask_list_contacts(): + global tl + b_l.config(text="Close List", command=on_tl_exit) + tl = Toplevel(root) + tl.bind('', on_tl_exit) + scrollbar = Scrollbar(tl) + scrollbar.pack(side=RIGHT, fill=Y) + lb = Listbox(tl) + lb.pack() + lb.config(yscrollcommand=scrollbar.set) + scrollbar.config(command=lb.yview) + for name, phone in contacts.list_contacts(): + lb.insert(0, "{}:{}".format(name, phone)) + + +def on_tl_exit(_=None): + b_l.config(text="Show All", command=ask_list_contacts) + tl.destroy() + + +def on_exit(): + contacts.save_contacts() + root.quit() + +Button(root, text="Add", width=10, command=ask_create_contact).grid(row=0, column=2) +Button(root, text="Find", width=10, command=ask_find_contact).grid(row=1, column=2) +Button(root, text="Update", width=10, command=ask_update_contact).grid(row=2, column=2) +Button(root, text="Delete", width=10, command=ask_delete_contact).grid(row=3, column=2) +b_l = Button(root, text="Show All", width=10, command=ask_list_contacts) +b_l.grid(row=3, column=0) + +root.protocol("WM_DELETE_WINDOW", on_exit) +root.mainloop() diff --git a/reader/__init__.py b/reader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reader/console_reader.py b/reader/console_reader.py new file mode 100644 index 0000000..b28ff44 --- /dev/null +++ b/reader/console_reader.py @@ -0,0 +1,22 @@ +# coding=utf-8 +class ConsoleReader(object): + @staticmethod + def validate(message): + if "'" in message or " " in message: + raise ValueError("SQL Injection") + return message + + @staticmethod + def ask_name(): + return ConsoleReader.validate(raw_input('name?')) + + @staticmethod + def ask_phone(): + return ConsoleReader.validate(raw_input('phone?')) + + @staticmethod + def ask_action(): + return raw_input("Action?") + + def close(self): + pass diff --git a/reader/file_reader.py b/reader/file_reader.py new file mode 100644 index 0000000..442e71d --- /dev/null +++ b/reader/file_reader.py @@ -0,0 +1,21 @@ +# coding=utf-8 +class FileReader(object): + def __init__(self, filename='session.txt'): + try: + self.file = open(filename, 'r') + except IOError: + raise + + def read_action(self): + command = self.file.readline()[:-1] + if not command: + command = 'q' + print command + return command + + def __getattr__(self, item): + assert item in ('ask_action', 'ask_name', 'ask_phone') + return self.read_action + + def close(self): + self.file.close() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f99b88f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +Flask==0.10.1 +itsdangerous==0.24 +Jinja2==2.8 +MarkupSafe==0.23 +MySQL-python==1.2.5 +py==1.4.30 +pytest==2.8.3 +redis==2.10.5 +selenium==2.48.0 +SQLAlchemy==1.0.9 +Werkzeug==0.11.2 +wheel==0.24.0 diff --git a/session.txt b/session.txt new file mode 100644 index 0000000..99c13ea --- /dev/null +++ b/session.txt @@ -0,0 +1,9 @@ +f +Bob +d +John +c +John +111 +f +John diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..a1a31bf --- /dev/null +++ b/settings.py @@ -0,0 +1,5 @@ +# coding=utf-8 + +READER = "File" # "Console" + +DATABASE = "SQLAlchemy" #"MySQL" #"Redis" # "Pickle" diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..1e3f032 --- /dev/null +++ b/settings.xml @@ -0,0 +1,8 @@ + + + Redis + + + File + + \ No newline at end of file diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..8f74e10 --- /dev/null +++ b/static/main.css @@ -0,0 +1,9 @@ +h1, .error { + color:red +} + +table, td { + border-style: solid; + border-color: #424242; + border-width: 1px +} \ No newline at end of file diff --git a/templates/add.html b/templates/add.html new file mode 100644 index 0000000..a791d0e --- /dev/null +++ b/templates/add.html @@ -0,0 +1,9 @@ +{% extends 'index.html' %} +{% block content %} +

Add contact

+
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/templates/delete.html b/templates/delete.html new file mode 100644 index 0000000..df6b123 --- /dev/null +++ b/templates/delete.html @@ -0,0 +1,8 @@ +{% extends 'index.html' %} +{% block content %} +

Delete contact

+
+ + +
+{% endblock %} \ No newline at end of file diff --git a/templates/find.html b/templates/find.html new file mode 100644 index 0000000..7162253 --- /dev/null +++ b/templates/find.html @@ -0,0 +1,14 @@ +{% extends 'index.html' %} +{% block content %} +

Find contact

+ {% if name %} +

Found: {{name}} - {{phone}}

+ {% endif %} +
+ + +
+{% endblock %} +{%block footer%} +© 2015 +{%endblock%} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f699842 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,36 @@ + + + + + Phone Book + + + + {%block errors%} + {% if message %}

{{message}}

{%endif%} + {%endblock%} + {% block content %} +

Phone Book

+ + + {% for contact in contacts %} + + + + + + {% endfor %} + +
{{contact.0}}{{contact.1}}
+ {% endblock %} + +{%block footer%} +{%endblock%} + + \ No newline at end of file diff --git a/templates/update.html b/templates/update.html new file mode 100644 index 0000000..1956c84 --- /dev/null +++ b/templates/update.html @@ -0,0 +1,9 @@ +{% extends 'index.html' %} +{% block content %} +

Update contact

+
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_web.py b/test/test_web.py new file mode 100644 index 0000000..89977b1 --- /dev/null +++ b/test/test_web.py @@ -0,0 +1,16 @@ +# coding=utf-8 +from selenium import webdriver + +class TestWeb(object): + def setup_class(self): + self.driver = webdriver.Firefox() + self.driver.get('http://localhost:5000') + + def teardown_class(self): + self.driver.close() + + def test_A(self): + assert 1 == 1 + + def test_B(self): + assert 1 == 2 \ No newline at end of file diff --git a/web_controller.py b/web_controller.py new file mode 100644 index 0000000..2ab8d5d --- /dev/null +++ b/web_controller.py @@ -0,0 +1,87 @@ +# coding=utf-8 +from flask import Flask, render_template, request, redirect, session +from configurer import contacts + +app = Flask(__name__) + + +@app.route('/') +def index(): + message = session.pop('message', '') + return render_template( + 'index.html', + contacts=contacts.list_contacts(), + message=message + ) + + +@app.route('/find') +def found(): + if 'search' in request.args: + try: + name = request.args['search'] + phone = contacts.find_contact(name) + return render_template('find.html', name=name, phone=phone) + except ValueError as e: + return render_template('find.html', message=e) + return render_template('find.html') + + +@app.route('/add', methods=['GET', 'POST']) +def add(): + if request.method == 'POST': + try: + contacts.create_contact(request.form['name'], request.form['phone']) + session['message'] = "Contact added" + return redirect('/') + except ValueError as e: + return render_template( + 'add.html', + message=e, + name=request.form['name'], + phone=request.form['phone'] + ) + return render_template('add.html') + + +@app.route('/update', methods=['GET', 'POST']) +def update(): + if request.method == 'POST': + try: + contacts.update_contact(request.form['name'], request.form['phone']) + session['message'] = "Contact updated: {}".format(request.form['name']) + return redirect('/') + except ValueError as e: + return render_template( + 'update.html', + message=e, + name=request.form['name'], + phone=request.form['phone'] + ) + return render_template('update.html') + + +@app.route('/delete', methods=['GET', 'POST']) +def delete(): + if request.method == 'POST': + try: + contacts.delete_contact(request.form['name']) + session['message'] = "Contact deleted: {}".format(request.form['name']) + return redirect('/') + except ValueError as e: + return render_template( + 'delete.html', + message=e, + name=request.form['name'], + ) + return render_template('delete.html') + + +@app.route('/do') +def do(): + return "a" + +app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' +if __name__ == '__main__': + app.run(debug=True) +