= Advanced Application Development = == Transactions == Во !BiblioPremium се користат трансакции кај сценарија во кои една корисничка акција предизвикува повеќе поврзани промени во базата. На овој начин се обезбедува сите операции да бидат успешно извршени како една целина или, во случај на грешка, сите промени да бидат поништени. === Трансакција при купување книга === При купување книга се извршуваат повеќе операции во рамки на една трансакција: 1. Се проверува цената и достапната количина на книгата. 2. Се креира нова нарачка во табелата `naracki`. 3. Се додава ставката во табелата `sodrzi`. 4. Се евидентира плаќањето во табелата `plakanja`. 5. Се намалува количината на залиха во табелата `knigi`. Доколку сите операции се успешни се повикува: {{{ conn.commit() }}} Доколку настане грешка, се повикува: {{{ conn.rollback() }}} со што се поништуваат промените направени во рамки на трансакцијата. Дел од имплементацијата: {{{ try: cursor.execute(""" INSERT INTO project.naracki (datum, status, vkupna_cena, korisnik_id) VALUES (CURRENT_TIMESTAMP, %s, %s, %s) RETURNING naracka_id; """, ("ZAVRSENA", vkupna_cena, session["korisnik_id"])) naracka_id = cursor.fetchone()[0] cursor.execute(""" INSERT INTO project.sodrzi (naracka_id, kniga_id, kolicina, edinechna_cena) VALUES (%s, %s, %s, %s); """, (naracka_id, kniga_id, kolicina, cena)) cursor.execute(""" INSERT INTO project.plakanja (datum, iznos, nacin_na_plakjanje, status, naracka_id) VALUES (CURRENT_TIMESTAMP, %s, %s, %s, %s); """, (vkupna_cena, "KARTICKA", "USPESNO", naracka_id)) cursor.execute(""" UPDATE project.knigi SET kolicina_na_zaliha = kolicina_na_zaliha - %s WHERE kniga_id = %s; """, (kolicina, kniga_id)) conn.commit() except Exception as e: conn.rollback() }}} Трансакцијата беше тестирана преку апликацијата со успешно купување на книгата „Ikigai“. Беше креирана нарачка #16 со количина 1 и вкупна цена 750 денари. Нарачката беше успешно прикажана и во историјата „Мои нарачки“. === Трансакција при зачувување кориснички преференции === Трансакција се користи и при промена на корисничките преференции. Старите жанровски и категориски преференции се бришат, а потоа се внесуваат новоизбраните вредности. Доколку сите операции се успешни се извршува `commit()`, а при грешка `rollback()`. На овој начин корисникот не може да остане со делумно ажурирани преференции. == Database Connection Pooling == За управување со конекциите кон PostgreSQL базата е имплементиран connection pool со `psycopg2.pool`. При стартување на апликацијата се креира pool со минимално 1 и максимално 10 конекции: {{{ from psycopg2 import pool db_pool = pool.SimpleConnectionPool( 1, 10, host="localhost", port=tunnel.local_bind_port, database=DB_NAME, user=DB_USER, password=DB_PASSWORD ) }}} За земање и враќање конекција се користат функции: {{{ def get_db_connection(): return db_pool.getconn() def release_db_connection(conn): db_pool.putconn(conn) }}} Пред пристап до базата, потребната конекција се зема од pool: {{{ conn = get_db_connection() cursor = conn.cursor() }}} По завршување на работата cursor-от се затвора, а конекцијата се враќа во pool: {{{ cursor.close() release_db_connection(conn) }}} Овој пристап се користи во рутите кои пристапуваат до базата, вклучувајќи пребарување и приказ на книги, препораки, омилени книги, нарачки, преференции и административните функционалности. Connection pool-от беше успешно тестиран при стартување и користење на апликацијата. Апликацијата успешно зема конекции од pool-от, извршува операции кон базата и ги враќа конекциите за повторна употреба. == Пагинација на административни податоци == Во администраторскиот дел е имплементирана пагинација за прегледот на нарачките и плаќањата. Наместо сите нарачки и плаќања да се вчитуваат и прикажуваат одеднаш, податоците се прикажуваат по 10 записи на една страница. За ова во SQL пребарувањата се користат `LIMIT` и `OFFSET`. `LIMIT` го ограничува бројот на записи што се прикажуваат на една страница, додека `OFFSET` определува од кој запис треба да започне прикажувањето. Тековната страница се зема преку параметарот `page`: {{{ page = request.args.get("page", 1, type=int) per_page = 10 offset = (page - 1) * per_page }}} Потоа во SQL пребарувањето се користат: {{{ LIMIT %s OFFSET %s; }}} со вредностите: {{{ (per_page, offset) }}} Пред прикажувањето се пресметува и вкупниот број на записи: {{{ SELECT COUNT(*) FROM project.naracki; }}} односно за плаќањата: {{{ SELECT COUNT(*) FROM project.plakanja; }}} Вкупниот број на страници се пресметува според вкупниот број на записи и бројот на записи што се прикажуваат на една страница: {{{ total_pages = (total_orders + per_page - 1) // per_page }}} За плаќањата на ист начин се користи вкупниот број на плаќања. Во административниот интерфејс се прикажуваат тековната страница и вкупниот број на страници, а администраторот може да се движи помеѓу нив преку копчињата „Претходна“ и „Следна“. Пагинацијата е имплементирана на: * прегледот на сите нарачки; * прегледот на сите плаќања. Со ова административниот дел е попрегледен и не е потребно сите записи да се вчитуваат и прикажуваат одеднаш кога бројот на нарачки и плаќања ќе се зголеми. == Заклучок == Во оваа фаза апликацијата е надградена со трансакциско извршување на операции кои бараат атомарност, connection pooling за поефикасно управување со конекциите кон PostgreSQL базата и пагинација за поефикасен и попрегледен приказ на административните податоци.