From fc01a3eb12731e8e2512e77b426ab5b33503edd1 Mon Sep 17 00:00:00 2001
From: Michael Rasmussen <mir@datanom.net>
Date: Wed, 16 May 2018 08:28:46 +0200
Subject: [PATCH] Half way through migration away from sqlalchemy

Signed-off-by: Michael Rasmussen <mir@datanom.net>
---
 app/DB/__init__.py      |   2 +-
 app/DB/base.py          |  11 ++-
 app/DB/baseorm.py       |  48 +++++++++---
 app/DB/db.py            |  92 ++++++++++++++--------
 app/DB/db_mysql.sql     |   8 +-
 app/DB/db_postgres.sql  |   8 +-
 app/DB/postgres.py      |   2 +-
 app/__init__.py         |  24 +-----
 app/models.py           | 169 ++++++++++++++++++++++++++++++----------
 app/templates/user.html |   4 +-
 app/tools.py            |  24 +++---
 app/views.py            |  94 ++++++++++------------
 config.py               |  12 ++-
 pwp.e4p                 |   5 +-
 run.py                  |  34 +++++++-
 setup.sh                |   4 +-
 16 files changed, 354 insertions(+), 187 deletions(-)

diff --git a/app/DB/__init__.py b/app/DB/__init__.py
index c0d9c3d..8b13789 100644
--- a/app/DB/__init__.py
+++ b/app/DB/__init__.py
@@ -1 +1 @@
-from DB import baseorm
+
diff --git a/app/DB/base.py b/app/DB/base.py
index cf3e11a..5f1d501 100644
--- a/app/DB/base.py
+++ b/app/DB/base.py
@@ -1,12 +1,17 @@
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
 class Base(object):
 
-    def query(self, sql):
+    def query(self, sql, placeholders):
         res = None
         cur = self.conn.cursor()
         try:
-            cur.execute(sql)
+            cur.execute(sql, placeholders)
             res = cur.fetchall()
-        except:
+        except Exception as e:
+            logger.error("{0}".format(e))
             pass
         finally:
             cur.close()
diff --git a/app/DB/baseorm.py b/app/DB/baseorm.py
index ef7962e..1d34536 100644
--- a/app/DB/baseorm.py
+++ b/app/DB/baseorm.py
@@ -1,6 +1,9 @@
 import abc
 
-class OrmSubject(metaclass=abc.ABCMeta):
+class QueryException(Exception):
+    pass
+
+class OrmSubject(object, metaclass=abc.ABCMeta):
     @abc.abstractmethod
     def addObserver(self, ormObserver):
         pass
@@ -13,7 +16,7 @@ class OrmSubject(metaclass=abc.ABCMeta):
     def notify(self, subject):
         pass
 
-class Observer(metaclass=abc.ABCMeta):
+class Observer(object, metaclass=abc.ABCMeta):
     @abc.abstractmethod
     def update(self, subject):
         pass
@@ -22,6 +25,7 @@ class BaseOrm(OrmSubject):
 
     def __init__(self):
         self.__observers = []
+        self.__oldValues = {}
 
     def addObserver(self, ormObserver):
         print("Added: {0}".format(ormObserver))
@@ -34,20 +38,44 @@ class BaseOrm(OrmSubject):
         for observer in self.__observers:
             observer.update(object)
 
+    @classmethod
+    def query(cls, **kwargs):
+        objects = []
+        from app import db
+        if kwargs:
+            if hasattr(cls, '__tablename__'):
+                table = cls.__tablename__
+            else:
+                table = cls.__name__
+            o = db.query(table, **kwargs)
+            if table.lower() == 'user':
+                for user in o:
+                    objects.append(cls(user[1], user[2], user[3], user[4], user[0]))
+            elif table.lower() == 'portfolio':
+                for portfolio in o:
+                    user = db.query('user', id = portfolio[2])[0]
+                    objects.append(cls(portfolio[1], user, portfolio[3], portfolio[4], portfolio[0]))
+        else:
+            raise QueryException("{0}: Missing at least one query parameter".format(cls))
+        return objects
+    
+    def commit(self):
+        self.notify(self)
+        self.__oldValues = {}
+        
+    def rollback(self):
+        for k, v in self.__oldValues.items():
+            setattr(self, k, v) 
+        self.__oldValues = {}
+    
     @property
     def id(self):
         return self.__id
 
     @id.setter
     def id(self, id):
-        notify = True
         try:
-            old = self.id
-            notify = False
-        except AttributeError as e:
+            self.__oldValues['id'] = self.id
+        except AttributeError:
             pass
-#            print(e)
-#            notify = False
         self.__id = id
-        if notify:
-            self.notify(self)
diff --git a/app/DB/db.py b/app/DB/db.py
index b381459..08f8da2 100644
--- a/app/DB/db.py
+++ b/app/DB/db.py
@@ -1,17 +1,24 @@
-import sys
-sys.path.append('..')
-sys.path.append('../..')
-from models import User, Portfolio, Album, Photo, AccessRight
 from enum import IntEnum, unique
 import inspect
+from abc import ABCMeta
 from baseorm import Observer
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
 
 @unique
 class DBDriver(IntEnum):
     PG = 1
     MySQL = 2
-    
-class DB(Observer):
+
+class Singleton(ABCMeta):
+    _instances = {}
+    def __call__(cls, password, driver = DBDriver.PG, user = 'pwp', database = 'pwp', host = 'localhost', port = None):
+        if cls not in cls._instances:
+            cls._instances[cls] = super(Singleton, cls).__call__(password, driver = DBDriver.PG, user = 'pwp', database = 'pwp', host = 'localhost', port = None)
+        return cls._instances[cls]
+
+class DB(Observer, metaclass=Singleton):
     __filename__ = None
     
     def __init__(self, password, driver = DBDriver.PG, user = 'pwp', database = 'pwp', host = 'localhost', port = None):
@@ -31,6 +38,7 @@ class DB(Observer):
         if self.driver is DBDriver.PG:
             from postgres import Postgres 
             try:
+                logger.info("Connect: database: {0} user: {1} host: {2} port: {3}".format(self.database, self.user, self.host, self.port))
                 self.db = Postgres(self.database, self.user, self.password, self.host, self.port)
             except Exception as e:
                 raise Exception(e)
@@ -44,7 +52,6 @@ class DB(Observer):
             raise Exception("%s: Unknown DB driver" % self.driver)
     
     def update(self, subject):
-        print("update: {0}".format(subject))
         self.store(subject)
     
     def initDb(self):
@@ -57,6 +64,30 @@ class DB(Observer):
         except:
             raise
 
+    def query(self, object, **kwargs):
+        if kwargs:
+            if object.lower() == 'user' and self.driver is DBDriver.PG:
+                sql = 'select * from "{0}" where '.format(object)
+            else:
+                sql = 'select * from {0} where '.format(object)
+            placeholder = []
+            clause = None
+            for name, value in kwargs.items():
+                if name.lower() == 'user' and self.driver is DBDriver.PG:
+                    attr = '"{0}"'.format(name)
+                else:
+                    attr = name
+                if clause:
+                    clause += ' and {0} = %s '.format(attr)
+                else:
+                    clause = '{0} = %s'.format(attr)
+                placeholder.append(value)
+            sql += clause
+            logger.info("{0} -> {1}".format(sql, placeholder))
+            return self.db.query(sql, placeholder)
+        else:
+            raise Exception("{0}: Missing at least one query parameter".format(object))
+
     def store(self, object):
         if inspect.isclass(object):
             raise Exception("{0}: Class not instance".format(object))
@@ -66,10 +97,7 @@ class DB(Observer):
         else:
             table = object.__class__.__name__
 
-        print("Table<{0}>".format(table))
         v = [i for i in dir(object) if isinstance(getattr(type(object), i, None), property)]
-        for p in v:
-            print("{0}: {1}".format(p, getattr(object, p)))
         
         action = None
         column = []
@@ -92,7 +120,6 @@ class DB(Observer):
                 else:
                     values.append(value)
                 column.append(p)
-        print(action)
         if action == 'insert':
             if table.lower() == 'user' and self.driver is DBDriver.PG:
                 sql = 'insert into "' + table + '" ('
@@ -127,9 +154,6 @@ class DB(Observer):
             sql += ' where id = {0}'.format(id)
             self.db.update(sql, values)
         
-        print(sql)
-        print(values)
-        
     def __repr__(self):
         default = '<%s.%s object at %s>' % (self.__class__.__module__, self.__class__.__name__, hex(id(self)))
         return "%s <Driver: %s, User: %s, database: %s>" % (default, self.driver.name,  self.user, self.database)
@@ -158,22 +182,26 @@ class Test(object):
         else:
             self.__x = x
 
-db = DB('test', DBDriver.MySQL)
-#db = DB('test')
-db.initDb()
+if __name__ == "__main__":
+    import sys
+    sys.path.append('../..')
+    from app.models import User, Portfolio, Album, Photo, AccessRight
+    #db = DB('test', DBDriver.MySQL)
+    db = DB('test')
+    db.initDb()
 
-user = User('test', 'test@test.dk', 'test', 'test')
-user.addObserver(db)
-print(user)
-db.store(user)
-portfolio = Portfolio('test', user)
-print(portfolio)
-album = Album('test', portfolio)
-print(album)
-accessright = AccessRight(user)
-print(accessright)
-photo = Photo('test', album)
-print(photo)
-db.store(portfolio)
-user.name = 'MIR'
-del db
+    user = User('test', 'test@test.dk', 'test', 'sha256$1HX2n73E$ac27f843b4342df7b6c12e5ac340e063ea958d52ce62c3883c124385c96b263a')
+    user.addObserver(db)
+    print(user)
+    db.store(user)
+    portfolio = Portfolio('test', user)
+    print(portfolio)
+    album = Album('test', portfolio)
+    print(album)
+    accessright = AccessRight(user)
+    print(accessright)
+    photo = Photo('test', album)
+    print(photo)
+    db.store(portfolio)
+    user.name = 'MIR'
+    del db
diff --git a/app/DB/db_mysql.sql b/app/DB/db_mysql.sql
index 8a27eab..a3a14f5 100644
--- a/app/DB/db_mysql.sql
+++ b/app/DB/db_mysql.sql
@@ -58,9 +58,9 @@ CREATE TABLE album
   name varchar(255),
   visible boolean NOT NULL,
   public boolean NOT NULL,
-  portfolioid integer NOT NULL,
+  portfolio integer NOT NULL,
   CONSTRAINT album_pkey PRIMARY KEY (id),
-  CONSTRAINT album_portfolioid_fkey FOREIGN KEY (portfolioid)
+  CONSTRAINT album_portfolio_fkey FOREIGN KEY (portfolio)
       REFERENCES portfolio (id) MATCH SIMPLE
       ON UPDATE CASCADE ON DELETE CASCADE
 ) ENGINE = InnoDB;
@@ -82,11 +82,11 @@ CREATE TABLE photo
   name varchar(255),
   exif varchar(255),
   format integer NOT NULL,
-  albumid integer NOT NULL,
+  album integer NOT NULL,
   visible boolean NOT NULL,
   public boolean NOT NULL,
   CONSTRAINT photo_pkey PRIMARY KEY (id),
-  CONSTRAINT photo_albumid_fkey FOREIGN KEY (albumid)
+  CONSTRAINT photo_album_fkey FOREIGN KEY (album)
       REFERENCES album (id) MATCH SIMPLE
       ON UPDATE CASCADE ON DELETE CASCADE,
   CONSTRAINT photo_format_fkey FOREIGN KEY (format)
diff --git a/app/DB/db_postgres.sql b/app/DB/db_postgres.sql
index e302dcf..1b8ac79 100644
--- a/app/DB/db_postgres.sql
+++ b/app/DB/db_postgres.sql
@@ -107,9 +107,9 @@ CREATE TABLE public.album
   name character varying(255),
   visible boolean NOT NULL,
   public boolean NOT NULL,
-  portfolioid integer NOT NULL,
+  portfolio integer NOT NULL,
   CONSTRAINT album_pkey PRIMARY KEY (id),
-  CONSTRAINT album_portfolioid_fkey FOREIGN KEY (portfolioid)
+  CONSTRAINT album_portfolio_fkey FOREIGN KEY (portfolio)
       REFERENCES public.portfolio (id) MATCH SIMPLE
       ON UPDATE CASCADE ON DELETE CASCADE
 );
@@ -149,11 +149,11 @@ CREATE TABLE public.photo
   name character varying(255),
   exif text,
   format integer NOT NULL,
-  albumid integer NOT NULL,
+  album integer NOT NULL,
   visible boolean NOT NULL,
   public boolean NOT NULL,
   CONSTRAINT photo_pkey PRIMARY KEY (id),
-  CONSTRAINT photo_albumid_fkey FOREIGN KEY (albumid)
+  CONSTRAINT photo_album_fkey FOREIGN KEY (album)
       REFERENCES public.album (id) MATCH SIMPLE
       ON UPDATE CASCADE ON DELETE CASCADE,
   CONSTRAINT photo_format_fkey FOREIGN KEY (format)
diff --git a/app/DB/postgres.py b/app/DB/postgres.py
index ce2ef23..6a67caa 100644
--- a/app/DB/postgres.py
+++ b/app/DB/postgres.py
@@ -6,6 +6,6 @@ class Postgres(Base):
         if port is None:
             port = 5432
         try:
-            self.conn = psycopg2.connect("dbname=%s user=%s password=%s host=%s port=%d" % (database, user, password, host, port))
+            self.conn = psycopg2.connect("dbname={0} user={1} password={2} host={3} port={4}".format(database, user, password, host, port))
         except psycopg2.Error as e:
             raise Exception(e)
diff --git a/app/__init__.py b/app/__init__.py
index e5fd11d..195f77b 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -1,9 +1,8 @@
 from flask import Flask
 from flask_bootstrap import Bootstrap
-from config import ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD
+from config import DB_DRIVER, DB_PASSWORD, DB_USER, DB_DATABASE, DB_HOST, DB_PORT
 from flask_login import LoginManager
 from flask_wtf.csrf import CSRFProtect
-
 app = Flask(__name__)
 app.config.from_object('config')
 lm = LoginManager()
@@ -13,25 +12,8 @@ bootstrap = Bootstrap(app)
 csrf = CSRFProtect()
 csrf.init_app(app)
 
-if not app.debug:
-    import logging
-
-    from logging.handlers import SMTPHandler, RotatingFileHandler
-    credentials = None
-    if MAIL_USERNAME or MAIL_PASSWORD:
-        credentials = (MAIL_USERNAME, MAIL_PASSWORD)
-    mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'PWP failure', credentials)
-    mail_handler.setLevel(logging.ERROR)
-    mail_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
-    app.logger.addHandler(mail_handler)
-
-    file_handler = RotatingFileHandler('log/pwp.log', 'a', 1 * 1024 * 1024, 10)
-    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
-    app.logger.addHandler(file_handler)
-
-    app.logger.setLevel(logging.INFO)
-    file_handler.setLevel(logging.INFO)
-    app.logger.info('pwp startup')
+from db import DB
+db = DB(DB_PASSWORD, DB_DRIVER, DB_USER, DB_DATABASE, DB_HOST, DB_PORT)
 
 from app import views, models
 lm.anonymous_user = models.MyAnonymous
diff --git a/app/models.py b/app/models.py
index 3171baf..5c9f74e 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,7 +1,8 @@
 from config import ADMINS
 from enum import IntEnum
-import DB
-#from flask_login import mixins
+from baseorm import BaseOrm
+from flask_login import mixins
+from app import app
 
 class Format(IntEnum):
     jpg = 1
@@ -13,7 +14,7 @@ class Role(IntEnum):
     write = 2
     admin = 3
 
-class User(DB.baseorm.BaseOrm):
+class User(BaseOrm):
     __tablename__ = 'user'
     
     @property
@@ -22,8 +23,11 @@ class User(DB.baseorm.BaseOrm):
 
     @name.setter
     def name(self, name):
+        try:
+            self.__oldValues['name'] = self.name
+        except AttributeError:
+            pass
         self.__name = name
-        self.notify(self)
 
     @property
     def email(self):
@@ -31,8 +35,11 @@ class User(DB.baseorm.BaseOrm):
 
     @email.setter
     def email(self, email):
+        try:
+            self.__oldValues['email'] = self.email
+        except AttributeError:
+            pass
         self.__email = email
-        self.notify(self)
 
     @property
     def username(self):
@@ -40,8 +47,11 @@ class User(DB.baseorm.BaseOrm):
 
     @username.setter
     def username(self, username):
+        try:
+            self.__oldValues['username'] = self.username
+        except AttributeError:
+            pass
         self.__username = username
-        self.notify(self)
 
     @property
     def password(self):
@@ -49,13 +59,16 @@ class User(DB.baseorm.BaseOrm):
 
     @password.setter
     def password(self, password):
+        try:
+            self.__oldValues['password'] = self.password
+        except AttributeError:
+            pass
         self.__password = [REDACTED]
-        self.notify(self)
 
-    def __init__(self, name, email, username, password):
+    def __init__(self, name, email, username, password, id = -1):
         super().__init__()
         self.email = email
-        self.id = -1
+        self.id = id
         self.name = name
         self.username = username
         self.password = [REDACTED]
@@ -68,14 +81,17 @@ class User(DB.baseorm.BaseOrm):
 
     def is_anonymous(self):
         return False
-    
+
+    def get_id(self):
+        return self.id
+
     def is_admin(self):
         return self.email in ADMINS
 
     def __repr__(self):
         return '<User ID: %s Name: %s Email: %s username: %s>' % (self.id, self.name,  self.email,  self.username)
         
-class Portfolio(DB.baseorm.BaseOrm):
+class Portfolio(BaseOrm):
     __tablename__ = 'portfolio'
     
     @property
@@ -84,6 +100,10 @@ class Portfolio(DB.baseorm.BaseOrm):
 
     @name.setter
     def name(self, name):
+        try:
+            self.__oldValues['name'] = self.name
+        except AttributeError:
+            pass
         self.__name = name
 
     @property
@@ -92,6 +112,10 @@ class Portfolio(DB.baseorm.BaseOrm):
         
     @public.setter
     def public(self, public):
+        try:
+            self.__oldValues['public'] = self.public
+        except AttributeError:
+            pass
         self.__public = public
         
     @property
@@ -100,6 +124,10 @@ class Portfolio(DB.baseorm.BaseOrm):
         
     @visible.setter
     def visible(self, visible):
+        try:
+            self.__oldValues['visible'] = self.visible
+        except AttributeError:
+            pass
         self.__visible = visible
         
     @property
@@ -108,25 +136,34 @@ class Portfolio(DB.baseorm.BaseOrm):
         
     @user.setter
     def user(self, user):
+        try:
+            self.__oldValues['user'] = self.user
+        except AttributeError:
+            pass
         self.__user = user
         
-    def __init__(self, name, user):
+    def __init__(self, name, user, public = False, visible = False, id = -1):
         super().__init__()
-        self.id = -1
+        self.id = id
         self.name = name
-        self.public = False
-        self.visible = False
-        if not isinstance(user, User):
+        self.public = public
+        self.visible = visible
+        app.logger.info("User: {0}".format(user))
+        if (isinstance(user, User)):
+            u = user
+        else:
+            u = User(user[1], user[2], user[3], user[4], user[0])
+        if not isinstance(u, User):
             raise Exception("{0}: Not a 'User'".format(user))
-        self.user = user
+        self.user = u
         
     def set_user_count(self, usercount):
         self.usercount = usercount
 
     def __repr__(self):
-        return '<Portfolio Name: %s Owner: %s>' % (self.name,  self.user)
+        return '<Portfolio ID: %s Name: %s Owner: %s>' % (self.id, self.name,  self.user)
 
-class Album(DB.baseorm.BaseOrm):
+class Album(BaseOrm):
     __tablename__ = 'album'
 
     @property
@@ -135,6 +172,10 @@ class Album(DB.baseorm.BaseOrm):
 
     @name.setter
     def name(self, name):
+        try:
+            self.__oldValues['name'] = self.name
+        except AttributeError:
+            pass
         self.__name = name
 
     @property
@@ -143,6 +184,10 @@ class Album(DB.baseorm.BaseOrm):
         
     @public.setter
     def public(self, public):
+        try:
+            self.__oldValues['public'] = self.public
+        except AttributeError:
+            pass
         self.__public = public
         
     @property
@@ -151,6 +196,10 @@ class Album(DB.baseorm.BaseOrm):
         
     @visible.setter
     def visible(self, visible):
+        try:
+            self.__oldValues['visible'] = self.visible
+        except AttributeError:
+            pass
         self.__visible = visible
         
     @property
@@ -159,6 +208,10 @@ class Album(DB.baseorm.BaseOrm):
         
     @portfolio.setter
     def portfolio(self, portfolio):
+        try:
+            self.__oldValues['portfolio'] = self.portfolio
+        except AttributeError:
+            pass
         self.__portfolio = portfolio
         
     def __init__(self, name, portfolio):
@@ -177,7 +230,7 @@ class Album(DB.baseorm.BaseOrm):
     def __repr__(self):
         return '<Album Name: %r Portfolio: %r>' % (self.name,  self.portfolio)
 
-class Photo(DB.baseorm.BaseOrm):
+class Photo(BaseOrm):
     __tablename__ = 'photo'
 
     @property
@@ -186,6 +239,10 @@ class Photo(DB.baseorm.BaseOrm):
 
     @name.setter
     def name(self, name):
+        try:
+            self.__oldValues['name'] = self.name
+        except AttributeError:
+            pass
         self.__name = name
 
     @property
@@ -194,6 +251,10 @@ class Photo(DB.baseorm.BaseOrm):
         
     @public.setter
     def public(self, public):
+        try:
+            self.__oldValues['public'] = self.public
+        except AttributeError:
+            pass
         self.__public = public
         
     @property
@@ -202,6 +263,10 @@ class Photo(DB.baseorm.BaseOrm):
         
     @visible.setter
     def visible(self, visible):
+        try:
+            self.__oldValues['visible'] = self.visible
+        except AttributeError:
+            pass
         self.__visible = visible
         
     @property
@@ -210,6 +275,10 @@ class Photo(DB.baseorm.BaseOrm):
         
     @album.setter
     def album(self, album):
+        try:
+            self.__oldValues['album'] = self.album
+        except AttributeError:
+            pass
         self.__album = album
         
     @property
@@ -218,6 +287,10 @@ class Photo(DB.baseorm.BaseOrm):
 
     @file.setter
     def file(self, file):
+        try:
+            self.__oldValues['file'] = self.file
+        except AttributeError:
+            pass
         self.__file = file
 
     @property
@@ -226,6 +299,10 @@ class Photo(DB.baseorm.BaseOrm):
 
     @exif.setter
     def exif(self, exif):
+        try:
+            self.__oldValues['exif'] = self.exif
+        except AttributeError:
+            pass
         self.__exif = exif
 
     @property
@@ -234,6 +311,10 @@ class Photo(DB.baseorm.BaseOrm):
         
     @format.setter
     def format(self, format):
+        try:
+            self.__oldValues['format'] = self.format
+        except AttributeError:
+            pass
         self.__format = format
         
     def __init__(self, name, album):
@@ -252,7 +333,7 @@ class Photo(DB.baseorm.BaseOrm):
     def __repr__(self):
         return '<Photo Name: %r File: %r Format: %r Album: %r>' % (self.name,  self.file,  self.format,  self.album)
 
-class AccessRight(DB.baseorm.BaseOrm):
+class AccessRight(BaseOrm):
     __tablename__ = 'accessright'
 
     @property
@@ -261,6 +342,10 @@ class AccessRight(DB.baseorm.BaseOrm):
 
     @perm.setter
     def perm(self, perm):
+        try:
+            self.__oldValues['perm'] = self.perm
+        except AttributeError:
+            pass
         self.__perm = perm
 
     @property
@@ -269,6 +354,10 @@ class AccessRight(DB.baseorm.BaseOrm):
         
     @user.setter
     def user(self, user):
+        try:
+            self.__oldValues['user'] = self.user
+        except AttributeError:
+            pass
         self.__user = user
 
     def __init__(self, user):
@@ -282,22 +371,22 @@ class AccessRight(DB.baseorm.BaseOrm):
     def __repr__(self):
         return '<AccessRight Right: %r User: %r>' % (self.perm,  self.user)
 
-#class MyAnonymous(mixins.AnonymousUserMixin):
-#    id = -1
-#    name = 'Anonymous User'
-#    email = 'no@email.com'
-#    username = 'None'
-#    
-#    @property
-#    def is_authenticated(self):
-#        return False
-#
-#    @property
-#    def is_admin(self):
-#        return False
-#
-#    def get_id(self):
-#        return self.id
-#        
-#    def __repr__(self):
-#        return '<User Name: %r Email: %r username: %r>' % (self.name,  self.email,  self.username)
+class MyAnonymous(mixins.AnonymousUserMixin):
+    id = -1
+    name = 'Anonymous User'
+    email = 'no@email.com'
+    username = 'None'
+    
+    @property
+    def is_authenticated(self):
+        return False
+
+    @property
+    def is_admin(self):
+        return False
+
+    def get_id(self):
+        return self.id
+        
+    def __repr__(self):
+        return '<User Name: %r Email: %r username: %r>' % (self.name,  self.email,  self.username)
diff --git a/app/templates/user.html b/app/templates/user.html
index e5b0da1..0f7d46c 100644
--- a/app/templates/user.html
+++ b/app/templates/user.html
@@ -110,7 +110,7 @@ $(document).on('click', '#deleteResponse', function () {
                         <td>{{p['portfolio']['name']}}</td>
                         <td>{{p['portfolio']['visible']}}</td>
                         <td>{{p['portfolio']['public']}}</td>
-                        <td>{{p['portfolio']['users']}}</td>
+                        <td>{{p['portfolio']['usercount']}}</td>
                         <td>
                             <div class="btn-group">
                                 <a role="button" class="btn btn-info btn-xs" href="{{ url_for('portfolio', id=p['portfolio']['id']) }}">
@@ -128,7 +128,7 @@ $(document).on('click', '#deleteResponse', function () {
                             <td>{{p['portfolio']['name']}} <span class="glyphicon glyphicon-arrow-right"></span> {{a.name}}</td>
                             <td>{{a.visible}}</td>
                             <td>{{a.public}}</td>
-                            <td>{{a.users}}</td>
+                            <td>{{a.usercount}}</td>
                             <td>
                                 <div class="btn-group">
                                     <a role="button" class="btn btn-info btn-xs" href="{{ url_for('album', id=a.id) }}">
diff --git a/app/tools.py b/app/tools.py
index e930a2e..f8e05d5 100644
--- a/app/tools.py
+++ b/app/tools.py
@@ -1,4 +1,5 @@
 from .models import User, Portfolio, Album, Photo, AccessRight, Role, MyAnonymous
+from app import app
 #from flask_login import mixins
 
 class DBQueryException(Exception):
@@ -10,11 +11,11 @@ class DBQuery:
         albums = []
         if hasattr(object, '__tablename__'):
             if isinstance(object, User):
-                portfolios = Portfolio.query.filter_by(owner=object)
+                portfolios = Portfolio.query(user=object.id)
                 for p in portfolios:
                     albums.extend(Album.query.filter_by(portfolio=p))
             elif isinstance(object, Portfolio):
-                albums = Album.query.filter_by(portfolio=object)
+                albums = Album.query(portfolio=object.id)
             elif isinstance(object, Photo):
                 albums.append(object.album)
             else:
@@ -30,7 +31,7 @@ class DBQuery:
         portfolios = []
         if hasattr(object, '__tablename__'):
             if isinstance(object, User):
-                portfolios = Portfolio.query.filter_by(owner=object)
+                portfolios = Portfolio.query(user=object.id)
             elif isinstance(object, Album):
                 portfolios.append(object.portfolio)
             elif isinstance(object, Photo):
@@ -48,14 +49,14 @@ class DBQuery:
     def get_owner(self, object):
         if hasattr(object, '__tablename__'):
             if isinstance(object, Portfolio):
-                return object.owner
+                return object.user
             elif isinstance(object, Album):
                 p = object.portfolio
-                return p.owner
+                return p.user
             elif isinstance(object, Photo):
                 a = object.album
                 p = a.portfolio
-                return p.owner
+                return p.user
             else:
                 raise DBQueryException("%s: not supported" % object.__tablename__)
         else:
@@ -65,6 +66,7 @@ class DBQuery:
         if hasattr(object, '__tablename__'):
             users = []
             if isinstance(object, Album):
+                # TODO: get rit of query.join
                 objects = AccessRight.query.join(AccessRight.album).filter(Album.id == object.id)
                 for o in objects:
                     users.append(o.user)
@@ -229,7 +231,7 @@ class DBQuery:
             # Last get all visible and public albums
             u = MyAnonymous()
             temp = self.get_albums_for_user(u)
-            print("1) Other albums: %s" % temp)
+            app.logger.info("1) Other albums: %s" % temp)
             albs = []
             portfolios = self.get_portfolios(user)
             for p in portfolios:
@@ -240,10 +242,10 @@ class DBQuery:
                     if not hidden and not a.visible:
                         continue
                     albums.append(a)
-            print("2) Own albums: %s" % albums)
+            app.logger.info("2) Own albums: %s" % albums)
             # Remove own albums
             temp = list(set(temp) - set(albs))
-            print("3) Other albums: %s" % temp)
+            app.logger.info("3) Other albums: %s" % temp)
             # Merge albums with temp removing duplicates
             albums = albums + temp
         else:
@@ -251,10 +253,10 @@ class DBQuery:
                 # Anonymous user
                 if user.is_anonymous:
                     # Find all albums which is public and visible
-                    albums = Album.query.filter_by(public=True,visible=True).all()
+                    albums = Album.query(public=True, visible=True)
             except AttributeError:
                 raise DBQueryException("%s: Not a User object" % user)
-            print("Anonymous albums: %s" % albums)
+            app.logger.info("Anonymous albums: %s" % albums)
         return albums
 
 def is_safe_url(target):
diff --git a/app/views.py b/app/views.py
index 768786e..170d4e0 100644
--- a/app/views.py
+++ b/app/views.py
@@ -1,6 +1,6 @@
 from flask import render_template, flash, redirect, session, url_for, request, g, abort
 from flask_login import login_user, logout_user, current_user, login_required
-from app import app, lm, tools
+from app import app, lm, tools, db
 from .forms import LoginForm, RegisterForm, UpdateForm, PwForm, SearchForm, DeleteForm, PortfolioForm, AlbumForm
 from .models import User, Portfolio, Album, AccessRight, Role, MyAnonymous
 from werkzeug.security import generate_password_hash, check_password_hash
@@ -21,7 +21,7 @@ def before_request():
 
 @lm.user_loader
 def load_user(id):
-    return User.query.get(int(id))
+    return User.query(id=id)[0]
 
 @app.route('/')
 @app.route('/index')
@@ -37,14 +37,15 @@ def login():
         return redirect(request.referrer)#redirect(url_for('index'))
     form = LoginForm()
     if form.validate_on_submit():
-        user = User.query.filter_by(username=form.username.data).first()
+        user = User.query(username=form.username.data)[0]
         if user is None:
             flash('Unknown username. Please try again or register.')
         else:
-            app.logger.info("db-pwd: %s form-pwd: %s" % (user.password, form.password.data))
             if check_password_hash(user.password, form.password.data):
                 app.logger.info("Login: %s" % user)
                 login_user(user, remember=False)
+                g.user = user
+                user.addObserver(db)
                 next = request.args.get('next')
                 if next is not None:
                     if not tools.is_safe_url(next):
@@ -110,18 +111,14 @@ def album():
             query = tools.DBQuery()
             portfolios = query.get_portfolios(u)
             new_album = Album(name=form.name.data, public=form.public.data, visible=form.visible.data, portfolio_id=portfolios[0].id)
-            db.session.add(new_album)
-            db.session.commit()
+            db.store(new_album)
             app.logger.warning("Created album: %s" % new_album)
         except DBException as ex:
-            db.session.rollback()
             flash(ex)
-        except exc.IntegrityError as ex:
-            db.session.rollback()
-            flash('Create album failed: %s' % ex)
         except Exception as ex:
-            db.session.rollback()
-            flash("Unknown error {0}".format(ex))
+            flash('Create album failed: %s' % ex)
+#        except Exception as ex:
+#            flash("Unknown error {0}".format(ex))
         return redirect(request.referrer)
     return render_template('album.html',
                            title='Create Album', 
@@ -163,76 +160,74 @@ def user(username):
             uname=form.username.data
             email=form.email.data
             name=form.name.data
-            user = User.query.filter_by(username=username).first()
+            user = User.query(username=username)[0]
             if uname != username:
-                u = User.query.filter_by(username=uname).first()
+                u = User.query(username=uname)[0]
                 if u is not None:
                     raise DBException("%s: Username exist" % uname)
                 user.username = uname
                 referrer = "/user/%s" % user.username
             if email != user.email:
-                e = User.query.filter_by(email=email).first()
+                e = User.query(email=email)[0]
                 if e is not None:
                     raise DBException("%s: Email exist" % email)
                 user.email = email
             if name != user.name:
                 user.name = name
         except DBException as ex:
-            db.session.rollback()
+            user.rollback()
             flash("{0}".format(ex))
             app.logger.warning("Update user failed: {0}".format(ex))
-        except exc.IntegrityError as ex:
-            db.session.rollback()
+        except Exception as ex:
+            user.rollback()
             flash("Update user failed: {0}".format(ex))
             app.logger.warning("Update user failed: {0}".format(ex))
-        except Exception as ex:
-            db.session.rollback()
-            flash("Unknown error {0}".format(ex))
-            app.logger.warning("Update user failed: Unknown error {0}".format(ex))
+#        except Exception as ex:
+#            db.rollback()
+#            flash("Unknown error {0}".format(ex))
+#            app.logger.warning("Update user failed: Unknown error {0}".format(ex))
         else:
             try:
-                db.session.commit()
+                user.commit()
                 login_user(user, remember=False)
                 app.logger.warning("Updated user: %s" % user)
                 flash("Userdata successfully updated")
-            except exc.IntegrityError as ex:
-                db.session.rollback()
-                flash("Update user failed: {0}".format(ex))
             except Exception as ex:
-                db.session.rollback()
-                flash("Unknown error {0}".format(ex))
+                flash("Update user failed: {0}".format(ex))
+#            except Exception as ex:
+#                flash("Unknown error {0}".format(ex))
         return redirect(referrer)
     elif pwform.pwchange.data and pwform.validate_on_submit():
-        user = User.query.filter_by(username=username).first()
+        user = User.query(username=username)[0]
         if pwform.password.data == pwform.passwordchk.data and check_password_hash(user.password, pwform.passwordcur.data):
             hashed_password = [REDACTED] method='sha256')
             user.password = [REDACTED]
             try:
-                db.session.commit()
+                user.commit()
                 login_user(user, remember=False)
                 app.logger.warning("Updated user - password: %s" % user)
                 flash("Password successfully changed")
-            except exc.IntegrityError as ex:
-                db.session.rollback()
-                flash("Update user failed: {0}".format(ex))
             except Exception as ex:
-                db.session.rollback()
-                flash("Unknown error {0}".format(ex))
+                flash("Update user failed: {0}".format(ex))
+#            except Exception as ex:
+#                flash("Unknown error {0}".format(ex))
         else:
             flash('Current password does not match or password different from password check')
         return redirect(referrer)
     else:
-        user = User.query.filter_by(username=username).first()
+        user = User.query(username=username)[0]
         if user is None:
             flash('User %s not found.' % username)
             return redirect(url_for('index'))
         app.logger.info("Show profile: %s" % user)
         query = tools.DBQuery()
         portfolios = query.get_portfolios(user)
+        app.logger.info("Portfolios: {0}".format(portfolios))
         private = []
         for p in portfolios:
             albums = query.get_albums(p)
-            p.set_user_count(len(query.get_users(p)))
+            #p.set_user_count(len(query.get_users(p)))
+            p.set_user_count(1)
             a1 = []
             for a in albums:
                 a.set_user_count(len(query.get_users(a)))
@@ -273,36 +268,31 @@ def register():
                 hashed_password = [REDACTED] method='sha256')
                 new_user = User(name=form.name.data, username=form.username.data, email=form.email.data, password=[REDACTED]
             except DBException as ex:
-                db.session.rollback()
                 flash(ex)
-            except exc.IntegrityError as ex:
-                db.session.rollback()
-                flash('Create user failed: %s' % ex)
             except Exception as ex:
-                db.session.rollback()
-                flash("Unknown error {0}".format(ex))
+                flash('Create user failed: %s' % ex)
+#            except Exception as ex:
+#                flash("Unknown error {0}".format(ex))
             else:
                 try:
                     portfolio = Portfolio(name = new_user.name, owner = new_user)
-                    db.session.add(portfolio)
+                    #db.session.add(portfolio)
                     new_user.portfolios.append(portfolio)
-                    db.session.add(new_user)
+                    #db.session.add(new_user)
                     acl = AccessRight(right = Role.read, user = new_user)
-                    db.session.add(acl)
+                    #db.session.add(acl)
                     acl = AccessRight(right = Role.write, user = new_user)
-                    db.session.add(acl)
+                    #db.session.add(acl)
                     acl = AccessRight(right = Role.admin, user = new_user)
-                    db.session.add(acl)
-                    db.session.commit()
+                    #db.session.add(acl)
+                    #db.session.commit()
                     app.logger.warning("Registered: %s" % new_user)
                     flash("You have been registered with username: " + form.username.data + os.linesep)
                     flash("Default Portfolio: " + portfolio.name)
                     return redirect(url_for('login'))
-                except exc.IntegrityError as ex:
-                    db.session.rollback()
+                except Exception as ex:
                     flash('Create user failed: %s' % ex)
                 except Exception as ex:
-                    db.session.rollback()
                     flash("Unknown error {0}".format(ex))
         else:
             flash('Password did not match password check')
diff --git a/config.py b/config.py
index 0f573ff..1e35193 100644
--- a/config.py
+++ b/config.py
@@ -1,5 +1,15 @@
-import os
+import os, sys
 basedir = os.path.abspath(os.path.dirname(__file__))
+sys.path.append('..')
+sys.path.append(basedir + '/app/DB')
+
+from db import DBDriver
+DB_DRIVER = DBDriver.PG
+DB_PASSWORD = '[REDACTED]
+DB_USER = 'pwp'
+DB_DATABASE = 'pwp'
+DB_HOST = 'localhost'
+DB_PORT = 5432
 
 WTF_CSRF_ENABLED = True
 SECRET_KEY = os.urandom(32)
diff --git a/pwp.e4p b/pwp.e4p
index c7e0a2b..5f59bb9 100644
--- a/pwp.e4p
+++ b/pwp.e4p
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Project SYSTEM "Project-5.1.dtd">
 <!-- eric project file for project pwp -->
-<!-- Saved: 2018-05-13, 06:42:02 -->
+<!-- Saved: 2018-05-14, 02:11:06 -->
 <!-- Copyright (C) 2018 Michael Rasmussen, mir@datanom.net -->
 <Project version="5.1">
   <Language></Language>
@@ -14,11 +14,14 @@
   <Eol index="0"/>
   <Sources>
     <Source>.gitignore</Source>
+    <Source>app/DB/__init__.py</Source>
     <Source>app/DB/base.py</Source>
+    <Source>app/DB/baseorm.py</Source>
     <Source>app/DB/db.py</Source>
     <Source>app/DB/db_mysql.sql</Source>
     <Source>app/DB/db_postgres.sql</Source>
     <Source>app/DB/mysqld.py</Source>
+    <Source>app/DB/observer.py</Source>
     <Source>app/DB/postgres.py</Source>
     <Source>app/__init__.py</Source>
     <Source>app/forms.py</Source>
diff --git a/run.py b/run.py
index ea7ae15..1812f48 100755
--- a/run.py
+++ b/run.py
@@ -1,5 +1,33 @@
 #!flask/bin/python
 from app import app
-app.jinja_env.auto_reload = True
-app.config['TEMPLATES_AUTO_RELOAD'] = True
-app.run(debug=True)
+from config import ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD
+import logging
+
+app.debug = True
+
+if not app.debug:
+    from logging.handlers import SMTPHandler, RotatingFileHandler
+    credentials = None
+    if MAIL_USERNAME or MAIL_PASSWORD:
+        credentials = (MAIL_USERNAME, MAIL_PASSWORD)
+    mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'PWP failure', credentials)
+    mail_handler.setLevel(logging.ERROR)
+    mail_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
+    app.logger.addHandler(mail_handler)
+
+    file_handler = RotatingFileHandler('log/pwp.log', 'a', 1 * 1024 * 1024, 10)
+    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
+    app.logger.addHandler(file_handler)
+    file_handler.setLevel(logging.DEBUG)
+else:
+#    from flask_debugtoolbar import DebugToolbarExtension
+    
+#    toolbar = DebugToolbarExtension(app)
+    app.jinja_env.auto_reload = True
+    app.config['TEMPLATES_AUTO_RELOAD'] = True
+
+app.logger.setLevel(logging.DEBUG)
+app.logger.info('pwp startup')
+#from werkzeug.security import generate_password_hash
+#print (generate_password_hash('test', method='sha256'))
+app.run()
diff --git a/setup.sh b/setup.sh
index 93319a7..d29e6d7 100755
--- a/setup.sh
+++ b/setup.sh
@@ -4,8 +4,10 @@ flask/bin/pip install flask
 flask/bin/pip install flask-login
 flask/bin/pip install flask-mail
 flask/bin/pip install flask-wtf
+# Use host binary psycopg2 driver
 flask/bin/pip install psycopg2-binary
-flask/bin/pip install psycopg2
+# Download source and compile in environment
+#flask/bin/pip install pip install --no-binary :all: psycopg2
 flask/bin/pip install mysql-connector
 flask/bin/pip install flask-bootstrap
 
-- 
2.39.2

