From 6635118ef55d218d92428287d0b781a07078038a Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Wed, 20 Jul 2022 19:24:35 +0200 Subject: [PATCH 01/20] [ITEM TABLE] + form and actions in table --- app/controllers/basket_controller.py | 2 + app/controllers/item_controller.py | 11 +- app/framework/injector.py | 21 +++- app/static/js/components/table-component.js | 133 ++++++++++++++++---- app/templates/items/list.html | 111 +++++++++++++++- app/templates/users/list.html | 9 +- 6 files changed, 248 insertions(+), 39 deletions(-) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index d5f0eba..77e506c 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -3,6 +3,7 @@ from app import app from app.framework.decorators.auth_required import auth_required from app.forms.basket.basket_add_item_form import BasketAddItemForm +from app.framework.decorators.inject import inject from app.services.basket_service import BasketService from app.services.item_service import ItemService from app.services.user_service import UserService @@ -18,6 +19,7 @@ def getAllBaskets(): @app.route('/basket') @auth_required() +@inject def getBasketDetail(basketService: BasketService): basket = basketService.find_one_by(userid=session.get('userid'), basketclosed=False) return render_template('baskets/details.html', diff --git a/app/controllers/item_controller.py b/app/controllers/item_controller.py index 6d78a64..3e17c22 100644 --- a/app/controllers/item_controller.py +++ b/app/controllers/item_controller.py @@ -1,15 +1,22 @@ -from flask import render_template, request, redirect, url_for +from flask import render_template, request, redirect, url_for, jsonify from app import app from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.forms.item.item_form import ItemForm +from app.framework.decorators.inject import inject from app.services.item_service import ItemService itemService = ItemService() @app.route('/items') def getItemList(): - return render_template('items/list.html', items=itemService.find_all()) + form = ItemForm(); + return render_template('items/list.html', items=itemService.find_all(), form=form) + +@app.route('/api/items') +@inject +def getItemListAsJson(itemService: ItemService): + return jsonify([item.get_json_parsable() for item in itemService.find_all()]) @app.route('/items/') def getItemDetails(itemid): diff --git a/app/framework/injector.py b/app/framework/injector.py index 563423e..97d94a6 100644 --- a/app/framework/injector.py +++ b/app/framework/injector.py @@ -1,6 +1,6 @@ from enum import Enum -from flask import Flask +from flask import Flask, session, request class Scope(Enum): @@ -39,13 +39,21 @@ def __init__(self, app: Flask, config): app.before_request(self.__request_start) app.after_request(self.__request_end) + def __get_session_id(self): + for cookie, value in request.cookies.items(): + if cookie == 'session': + return value + def __request_start(self, *args, **kwargs): # print("START SCOPED") - self.__scoped = {} + # print(session.get('csrf_token')) + sessionid = self.__get_session_id() + self.__scoped[sessionid] = {} def __request_end(self, response): # print("SCOPE END") - self.__scoped = {} + sessionid = self.__get_session_id() + self.__scoped[sessionid] = {} return response def __del__(self): @@ -71,10 +79,11 @@ def __get_singleton(self, dependency: DependencyConfig): return self.__singleton[dependency.base.__name__] def __get_scoped(self, dependency: DependencyConfig): - if self.__scoped.get(dependency.base.__name__) is None: - self.__scoped[dependency.base.__name__] = dependency.implement() + sessionid = self.__get_session_id() + if self.__scoped.get(sessionid).get(dependency.base.__name__) is None: + self.__scoped.get(sessionid)[dependency.base.__name__] = dependency.implement() - return self.__scoped[dependency.base.__name__] + return self.__scoped.get(sessionid)[dependency.base.__name__] def __get_transient(self, dependency: DependencyConfig): return dependency.implement() diff --git a/app/static/js/components/table-component.js b/app/static/js/components/table-component.js index 217de38..b1bcdf5 100644 --- a/app/static/js/components/table-component.js +++ b/app/static/js/components/table-component.js @@ -2,6 +2,7 @@ export class TableComponent { constructor(tableConfig) { this.html = document.createElement('table'); + this.form = []; this.initTable(tableConfig); } @@ -14,43 +15,122 @@ export class TableComponent this.thead = document.createElement('thead'); this.tbody = document.createElement('tbody'); - let first = true; let thead_tr = document.createElement('tr'); + let tr = document.createElement('tr'); + for(const col of tableConfig.columns) { + const th = document.createElement('th'); + th.scope = 'col'; + th.innerText = col.columnName; + thead_tr.appendChild(th); + } + this.thead.appendChild(thead_tr); + for(const row of tableConfig.data) { let tr = document.createElement('tr'); - for(const col of tableConfig.columns) - { - if(first) - { - const th = document.createElement('th'); - th.scope = 'col'; - th.innerText = col.columnName; - thead_tr.appendChild(th); - } - if(col.value) - { - const value = this.processRow(row, col.value); - const td = document.createElement('td'); - td.innerText = value; - tr.appendChild(td); - } else if(col.valueCb) - { - const value = col.valueCb(row); - const td = document.createElement('td'); - td.innerText = value; - tr.appendChild(td); - } - } + + this.processRowData(row, tableConfig, tr); + // this.processAction(row, tableConfig, tr); + this.tbody.appendChild(tr); - first = false; } - this.thead.appendChild(thead_tr); + this.html.appendChild(this.thead); this.html.appendChild(this.tbody); } + processRowData(row, tableConfig, tr) + { + for(const col of tableConfig.columns) + { + if(col.value) + { + const value = this.processRow(row, col.value); + const td = document.createElement('td'); + td.innerText = value; + tr.appendChild(td); + } else if(col.valueCb) + { + const value = col.valueCb(row); + const td = document.createElement('td'); + td.innerText = value; + tr.appendChild(td); + } else if(col.type === 'FORM') + { + this.processForm(row, tableConfig, tr); + } else if(col.type === 'ACTIONS') + { + this.processAction(row, tableConfig, tr); + } + } + } + + processForm(row, tableConfig, tr) + { + if(!tableConfig.form) + { + return; + } + let td = document.createElement('td'); + const formId = tableConfig.form.formName + row[tableConfig.form.formIdValue]; + this.form[formId] = document.createElement('form'); + td.appendChild(this.form[formId]); + + for(const field of tableConfig.form.formFields) + { + console.log(field); + const input = document.createElement('input'); + input.name = field.fieldName; + input.value = this.processRow(row, field.value); + input.type = field.type; + + this.form[formId].appendChild(input); + } + tr.appendChild(td); + } + + processAction(row, tableConfig, tr) + { + if(!tableConfig.actions) + { + return; + } + + let actionsTd = document.createElement('td'); + let div = document.createElement('div'); + div.classList.add('btn-group'); + + for(const action of tableConfig.actions) + { + // { actionName: '', actionHref: '', actionCb: (event) => T, buttonType: } + let actionBtn = document.createElement('button'); + actionBtn.innerText = action.actionName; + + actionBtn.addEventListener('click', (event) => + { + event.preventDefault(); + + let data = row; + + if(action.buttonType === 3) + { + const formId = tableConfig.form.formName + row[tableConfig.form.formIdValue]; + data = new FormData(this.form[formId]); + } + + action.actionCb(data); + }); + + + actionBtn.classList.add('btn'); + actionBtn.classList.add(action.buttonType === 1 ? 'btn-success' : action.buttonType === 2 ? 'btn-danger' : 'btn-primary'); + div.appendChild(actionBtn); + actionsTd.appendChild(div); + } + tr.appendChild(actionsTd); + } + /** * @param {any} row * @param {string} value @@ -58,6 +138,7 @@ export class TableComponent processRow(row, value) { console.log(row, value) + // itemtype.itemtypename if(value.indexOf('.') !== -1) { let objMemberName = value.split('.'); diff --git a/app/templates/items/list.html b/app/templates/items/list.html index 04dae22..648f3c6 100644 --- a/app/templates/items/list.html +++ b/app/templates/items/list.html @@ -1,7 +1,114 @@ {% extends "layout/layout.html" %} {% block body %} - {% include 'items/item_list.html' %} +
+
{% if "ADMIN" in session.get('userroles') %} - Add item +
+ {{ form.csrf_token }} + + {% if form.errors and "itemname" in form.errors.keys() %} + {{ form.errors['itemnames'] }} + {% endif %} + + {% if form.errors and "itemdescription" in form.errors.keys() %} + {{ form.errors['itemdescription'] }} + {% endif %} + + {% if form.errors and "itemstock" in form.errors.keys() %} + {{ form.errors['itemstock'] }} + {% endif %} + + +
{% endif %} + {% endblock %} \ No newline at end of file diff --git a/app/templates/users/list.html b/app/templates/users/list.html index 4cf5386..c6b74de 100644 --- a/app/templates/users/list.html +++ b/app/templates/users/list.html @@ -28,7 +28,7 @@ { refreshUserTable(); document.forms['addUser'].reset(); - }) + }); }) refreshUserTable(); @@ -39,6 +39,7 @@ .then((res) => { const data = JSON.parse(res); + console.log(res); console.log(data); const tableConfig = { @@ -54,11 +55,10 @@ roles.push(role.rolename); } return roles.join(', '); - }}, + } }, ] } - let table = new TableComponent(tableConfig); const userTable = document.getElementById('userTable'); while(userTable.firstChild) @@ -66,6 +66,9 @@ userTable.removeChild(userTable.firstChild); } userTable.appendChild(table.html); + }, (err) => + { + console.log(err) }) } From ba4c094cc8ebdde3a1fada1b67d21c58f15f8067 Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Fri, 22 Jul 2022 10:13:04 +0200 Subject: [PATCH 02/20] [AJAX CONFIG] add global header config --- app/__init__.py | 2 +- app/static/js/ajax-tools.js | 17 +++++++++++++++++ app/templates/items/item_list.html | 2 +- app/templates/layout/layout.html | 11 +++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 9b6f896..db64ecb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -12,7 +12,7 @@ app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://app:1234@127.0.0.1:5435/app' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# csrf_protect = CSRFProtect(app) +csrf_protect = CSRFProtect(app) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/static/js/ajax-tools.js b/app/static/js/ajax-tools.js index 040e25c..c667dfb 100644 --- a/app/static/js/ajax-tools.js +++ b/app/static/js/ajax-tools.js @@ -1,3 +1,7 @@ +export let ajaxConfig = { + headers: [] +}; + export function sendAjax(url, method='get', data = null) { return new Promise((resolve, reject) => @@ -21,6 +25,19 @@ export function sendAjax(url, method='get', data = null) }); request.open(method, url, true); + + if(ajaxConfig.headers.length > 0) + { + for(let header of ajaxConfig.headers) + { + const methods = ajaxConfig.headers.methods; + if(methods.includes(method) || methods.includes('ALL')) + { + request.setRequestHeader(header.key, header.value); + } + } + } + request.send(data); }) } \ No newline at end of file diff --git a/app/templates/items/item_list.html b/app/templates/items/item_list.html index d625972..c79476a 100644 --- a/app/templates/items/item_list.html +++ b/app/templates/items/item_list.html @@ -50,7 +50,7 @@ e.preventDefault(); {#const data = new FormData(document.forms['formItemID{{ item.itemid }}']);#} - sendAjax("{{ url_for('add_item_to_basket') }}", 'post') + sendAjax("{{ url_for('remove_item_to_basket', itemid=item.itemid) }}", 'post') .then((res) => { console.log(res) diff --git a/app/templates/layout/layout.html b/app/templates/layout/layout.html index 455a091..00803b9 100644 --- a/app/templates/layout/layout.html +++ b/app/templates/layout/layout.html @@ -9,6 +9,17 @@ + + From 4e53bb8aa519d45b90069ef606c0313b8f6c68ab Mon Sep 17 00:00:00 2001 From: CrenierAmaury Date: Fri, 22 Jul 2022 10:13:42 +0200 Subject: [PATCH 03/20] tableV2 to test --- app/controllers/item_controller.py | 8 +- app/dtos/user_dto.py | 2 +- .../js/components/table_component-v2.js | 54 ++++++ app/templates/items/item_list.html | 164 +++++++----------- app/templates/items/list.html | 3 - 5 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 app/static/js/components/table_component-v2.js diff --git a/app/controllers/item_controller.py b/app/controllers/item_controller.py index 0cd5926..a16599b 100644 --- a/app/controllers/item_controller.py +++ b/app/controllers/item_controller.py @@ -1,5 +1,6 @@ -from flask import render_template, request, redirect, url_for +from flask import render_template, request, redirect, url_for, jsonify from app import app +from app.framework.decorators.inject import inject from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.forms.item.item_form import ItemForm from app.services.item_service import ItemService @@ -10,6 +11,11 @@ def getItemList(): return render_template('items/list.html', items=itemService.find_all()) +@app.route('/api/items') +@inject +def getItemsAsJson(item_service: ItemService): + return jsonify([item.get_json_parsable() for item in item_service.find_all()]) + @app.route('/items/') def getItemDetails(itemid): form = BasketAddItemForm() diff --git a/app/dtos/user_dto.py b/app/dtos/user_dto.py index c38fbc0..005a45f 100644 --- a/app/dtos/user_dto.py +++ b/app/dtos/user_dto.py @@ -28,7 +28,7 @@ def build_from_entity(user): user_dto.userdescription = user.userdescription user_dto.userroles = [] for role in user.roles: - user_dto.userroles.append(RoleDTO.build_from_entity(role.rel_role).__dict__) + user_dto.userroles.append(RoleDTO.build_from_entity(role.rel_role)) return user_dto diff --git a/app/static/js/components/table_component-v2.js b/app/static/js/components/table_component-v2.js new file mode 100644 index 0000000..7e75b29 --- /dev/null +++ b/app/static/js/components/table_component-v2.js @@ -0,0 +1,54 @@ +export class TableComponentV2 { + constructor(tableConfig) { + this.table = document.createElement('table'); + this.tableCaption = document.createElement('caption'); + this.tableHead = document.createElement('thead'); + this.tableBody = document.createElement('tbody'); + + this.initTable(tableConfig); + } + + initTable(tableConfig) { + + if (tableConfig.classList && tableConfig.classList.length != 0) { + const tableClasslist = this.table.classList; + tableConfig.classList.array.forEach(c => { + tableClasslist.add(c); + }); + } + + if (tableConfig.caption && tableConfig.caption != '') { + this.tableCaption.innerText = tableConfig.caption; + this.table.appendChild(this.tableCaption); + } + + if (tableConfig.columns && tableConfig.columns.length != 0) { + const tableHead_tr = document.createElement('tr'); + + } + } +} + +conf = { + data: data, + caption: "List of items", + classList: [ + 'table', + 'table-stripped', + 'table-hover' + ], + columns: [ + {name: 'ID', value: 'userid'}, + {name: 'Name', value: 'username'}, + {name: 'Email', value: 'useremail'}, + {name: 'Descritpion', value: 'userdescritpion'}, + {name: 'test', value: 'usertest', input: 'text'}, + {name: 'Roles', value: () => {}}, + ], + actions: [ + {name: 'update', type: 'button', classList: ['btn btn-primary'], cb: () => {}, event: 'click', displayType: 'row'}, + {name: 'remove', type: 'button', classList: ['btn btn-primary'], cb: () => {}, event: 'click', displayType: 'row'}, + {name: 'test', type: 'a', classList: ['btn btn-primary'], cb: "href", displayType: 'row'} + ] +} + diff --git a/app/templates/items/item_list.html b/app/templates/items/item_list.html index 4d7ebf3..b73375d 100644 --- a/app/templates/items/item_list.html +++ b/app/templates/items/item_list.html @@ -1,108 +1,60 @@ {% block body %} - - - - - - - - - - - - {% for item in items %} - - - - - - - - {% endfor %} - -
#NameDescriptionQuantityActions
{{ item.itemid }} {{ item.itemname }} {{ item.itemdescription }} {{ item.itemquantity }} - {% if is_basket %} -
-
- - - - -
-
- - {% else %} -
- Details - {% if "ADMIN" in session.get('userroles') %} - Edit - {% endif %} -
- - - -
-
- - {% endif %} -
+
+
+
+ + + +
+
+ {% endblock %} \ No newline at end of file diff --git a/app/templates/items/list.html b/app/templates/items/list.html index 04dae22..9161da3 100644 --- a/app/templates/items/list.html +++ b/app/templates/items/list.html @@ -1,7 +1,4 @@ {% extends "layout/layout.html" %} {% block body %} {% include 'items/item_list.html' %} - {% if "ADMIN" in session.get('userroles') %} - Add item - {% endif %} {% endblock %} \ No newline at end of file From 11ade11afa9f83ba721f142f68f06316dcb913f5 Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Fri, 22 Jul 2022 10:13:04 +0200 Subject: [PATCH 04/20] [AJAX CONFIG] add global header config --- app/__init__.py | 2 +- app/services/basket_service.py | 9 ++++++--- app/services/item_service.py | 9 ++++++--- app/services/user_service.py | 6 ++++-- app/static/js/ajax-tools.js | 17 +++++++++++++++++ app/templates/items/item_list.html | 2 +- app/templates/layout/layout.html | 11 +++++++++++ 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 9b6f896..db64ecb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -12,7 +12,7 @@ app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://app:1234@127.0.0.1:5435/app' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# csrf_protect = CSRFProtect(app) +csrf_protect = CSRFProtect(app) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/services/basket_service.py b/app/services/basket_service.py index 7841135..c2470a7 100644 --- a/app/services/basket_service.py +++ b/app/services/basket_service.py @@ -27,7 +27,8 @@ def insert(self, data): try: db.session.add(basket) db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return self.find_one(basket.basketid) @@ -40,7 +41,8 @@ def update(self, entity_id: int, data): BasketMapper.form_to_entity(data, basket) try: db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return self.find_one(entity_id) @@ -53,7 +55,8 @@ def delete(self, entity_id: int): try: db.session.delete(basket) db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return basket.basketid diff --git a/app/services/item_service.py b/app/services/item_service.py index 2c13d73..03e1784 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -22,7 +22,8 @@ def insert(self, data): try: db.session.add(item) db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return self.find_one(item.itemid) @@ -36,7 +37,8 @@ def update(self, entity_id: int, data): ItemMapper.form_to_entity(data, item) try: db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return self.find_one(entity_id) @@ -50,7 +52,8 @@ def delete(self, entity_id: int): try: db.session.delete(item) db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return item.itemid \ No newline at end of file diff --git a/app/services/user_service.py b/app/services/user_service.py index aad7392..cf0c3d1 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -42,7 +42,8 @@ def insert(self, form: UserRegisterForm): try: db.session.add(user) db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return self.find_one(user.userid) @@ -62,7 +63,8 @@ def update(self, entity_id: int, form: UserUpdateForm): try: db.session.commit() - except: + except Exception as e: + print(e) db.session.rollback() return None diff --git a/app/static/js/ajax-tools.js b/app/static/js/ajax-tools.js index 040e25c..75acfde 100644 --- a/app/static/js/ajax-tools.js +++ b/app/static/js/ajax-tools.js @@ -1,3 +1,7 @@ +export let ajaxConfig = { + headers: [] +}; + export function sendAjax(url, method='get', data = null) { return new Promise((resolve, reject) => @@ -21,6 +25,19 @@ export function sendAjax(url, method='get', data = null) }); request.open(method, url, true); + + if(ajaxConfig.headers.length > 0) + { + for(let header of ajaxConfig.headers) + { + const methods = header.methods; + if(methods.includes(method) || methods.includes('ALL')) + { + request.setRequestHeader(header.key, header.value); + } + } + } + request.send(data); }) } \ No newline at end of file diff --git a/app/templates/items/item_list.html b/app/templates/items/item_list.html index d625972..c79476a 100644 --- a/app/templates/items/item_list.html +++ b/app/templates/items/item_list.html @@ -50,7 +50,7 @@ e.preventDefault(); {#const data = new FormData(document.forms['formItemID{{ item.itemid }}']);#} - sendAjax("{{ url_for('add_item_to_basket') }}", 'post') + sendAjax("{{ url_for('remove_item_to_basket', itemid=item.itemid) }}", 'post') .then((res) => { console.log(res) diff --git a/app/templates/layout/layout.html b/app/templates/layout/layout.html index 455a091..00803b9 100644 --- a/app/templates/layout/layout.html +++ b/app/templates/layout/layout.html @@ -9,6 +9,17 @@ + + From a4f58610aedbf8ede21c42a2e5e69a0210ed6e33 Mon Sep 17 00:00:00 2001 From: CrenierAmaury Date: Fri, 22 Jul 2022 11:09:37 +0200 Subject: [PATCH 05/20] basket items base --- app/controllers/basket_controller.py | 12 ++++++ app/dtos/basket_dto.py | 2 +- app/templates/baskets/details.html | 63 +++++++++++++--------------- app/templates/items/item_list.html | 8 +--- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index 36822aa..c52f507 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -17,6 +17,11 @@ def getAllBaskets(): return render_template('baskets/list.html', baskets=basketService.find_all()) +@app.route('/api/baskets') +@inject +def getBasketsAsJson(basketService: BasketService): + return jsonify([basket.get_json_parsable() for basket in basketService.find_all()]) + @app.route('/basket') @auth_required() @inject @@ -25,6 +30,13 @@ def getBasketDetail(basketService: BasketService): return render_template('baskets/details.html', basket=basket, items=basket.items, is_basket=True) +@app.route('/api/basket') +@auth_required() +@inject +def getBasketDetailAsJson(basketService: BasketService): + basket = basketService.find_one_by(userid=session.get('userid'), basketclosed=False) + return jsonify([basket.get_json_parsable() for basket in basketService.find_one_by(userid=session.get('userid'), basketclosed=False)]) + @app.route('/basket/add', methods=['POST']) @auth_required() def add_item_to_basket(): diff --git a/app/dtos/basket_dto.py b/app/dtos/basket_dto.py index 1c7f01d..78a85b7 100644 --- a/app/dtos/basket_dto.py +++ b/app/dtos/basket_dto.py @@ -25,4 +25,4 @@ def build_from_entity(basket: Basket): return basket_dto def get_json_parsable(self): - pass \ No newline at end of file + return self.__dict__ \ No newline at end of file diff --git a/app/templates/baskets/details.html b/app/templates/baskets/details.html index 54fb226..976afa9 100644 --- a/app/templates/baskets/details.html +++ b/app/templates/baskets/details.html @@ -3,47 +3,40 @@ {{ basket.user.username }}
- {% include 'items/item_list.html' %} - +
{# href="{{ url_for('checkout_basket') }}"#} Checkout
- {% endblock %} \ No newline at end of file From b431f762bbe1c364020743e4589f305d1f01198b Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Tue, 23 Aug 2022 15:03:36 +0200 Subject: [PATCH 08/20] [USER] with JWT --- app/__init__.py | 4 +- app/controllers/basket_controller.py | 9 ++- app/controllers/home_controller.py | 4 +- app/controllers/user_controller.py | 92 +++++++++-------------- app/dtos/basket_dto.py | 6 +- app/dtos/user_dto.py | 1 + app/forms/user/user_login_form.py | 3 + app/framework/decorators/auth_required.py | 31 +++++++- app/services/auth_service.py | 3 + app/services/auth_service_impl.py | 3 +- app/services/basket_service.py | 4 +- requirements.txt | 5 +- 12 files changed, 94 insertions(+), 71 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index db64ecb..1efc29c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,4 @@ +import wtforms_json from flask import Flask from flask_debugtoolbar import DebugToolbarExtension from flask_sqlalchemy import SQLAlchemy @@ -7,12 +8,13 @@ app = Flask('app') app.debug = True app.secret_key = 'superSecretKey01@' +wtforms_json.init() toolbar = DebugToolbarExtension(app) app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://app:1234@127.0.0.1:5435/app' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -csrf_protect = CSRFProtect(app) +# csrf_protect = CSRFProtect(app) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index b412aad..2d68c28 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -4,6 +4,7 @@ from app.framework.decorators.auth_required import auth_required from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.framework.decorators.inject import inject +from app.services.auth_service import AuthService from app.services.basket_service import BasketService from app.services.item_service import ItemService from app.services.user_service import UserService @@ -20,10 +21,10 @@ def getAllBaskets(): @app.route('/basket') @auth_required() @inject -def getBasketDetail(basketService: BasketService): - basket = basketService.find_one_by(userid=session.get('userid'), basketclosed=False) - return render_template('baskets/details.html', - basket=basket, items=basket.items, is_basket=True) +def getBasketDetail(basketService: BasketService, authService: AuthService): + user = authService.get_current_user() + basket = basketService.find_one_by(userid=user.userid, basketclosed=False) + return jsonify(basket.get_json_parsable()) @app.route('/api/basket/items') @auth_required() diff --git a/app/controllers/home_controller.py b/app/controllers/home_controller.py index 5303bdd..107716b 100644 --- a/app/controllers/home_controller.py +++ b/app/controllers/home_controller.py @@ -1,7 +1,7 @@ from app import app -from flask import render_template, redirect +from flask import render_template, redirect, jsonify @app.route('/', methods=['GET']) def index(): - return render_template('home/home.html') + return jsonify({'error': 'WRONG URL'}), 500 diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 4dca050..249262b 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,3 +1,7 @@ +import datetime + +import jwt + from app.framework.decorators.auth_required import auth_required from app.framework.decorators.inject import inject from app.services.auth_service import AuthService @@ -13,94 +17,70 @@ # http://localhost:8080/users -> GET @app.route('/users') @inject -def getUserList(userService: UserService): - form = UserRegisterForm() - - return render_template('users/list.html', users=userService.find_all(), form=form) - -@app.route('/api/users') -@inject -def getUsersAsJson(user_service: UserService): +def getUserList(user_service: UserService): return jsonify([user.get_json_parsable() for user in user_service.find_all()]) # http://localhost:8080/users/5 -> GET @app.route('/users/', methods=["GET"]) @auth_required() @inject -def getOneUser(userid: int, userService: UserService): - user = userService.find_one(userid) +def getOneUser(userid: int, user_service: UserService): + user = user_service.find_one(userid) - return render_template('users/profile.html', user=user) + return jsonify('users/profile.html', user=user.get_json_parsable()) # http://localhost:8080/users/register -> GET | POST -@app.route('/users/register', methods=["GET", "POST"]) +@app.route('/users/register', methods=["POST"]) @inject def register(userService: UserService): form = UserRegisterForm(request.form) - if request.method == 'POST': - if form.validate(): - user = userService.insert(form) + if form.validate(): + user = userService.insert(form) - return redirect(url_for('getOneUser', userid=user.userid)) + return jsonify(user.get_json_parsable()) - return render_template('users/register.html', form=form) + return jsonify({ 'errors': 'wrong credentials!'}) -@app.route('/users/update/', methods=["GET", "POST"]) +@app.route('/users/update/', methods=["PUT"]) @auth_required(level="ADMIN", or_is_current_user=True) @inject def userUpdate(userid: int, userService: UserService, roleService: RoleService): form = UserUpdateForm(request.form) - roles = roleService.find_all() - if request.method == 'POST': - if form.validate(): - user = userService.update(userid, form) + if form.validate(): + user = userService.update(userid, form) - return redirect(url_for('getOneUser', userid=userid)) + return jsonify(user.get_json_parsable()) - user = userService.find_one(userid) - return render_template('users/update.html', form=form, user=user, roles=roles) + return jsonify(form.errors) -@app.route('/login', methods=["GET", "POST"]) +@app.route('/login', methods=["POST"]) @inject def login(userService: UserService): - form = UserLoginForm(request.form) - - if request.method == 'POST': - if form.validate(): - user = userService.login(form) + form = UserLoginForm.from_json(request.json) - if user != None: - session['username'] = user.username - session['userid'] = user.userid - session['userroles'] = user.get_roles() - return redirect(url_for('getOneUser', userid=user.userid)) + if form.validate(): + user = userService.login(form) - errors = {} - errors['authentication'] = 'Wrong user or password!' + if user != None: + token = jwt.encode({ + 'userid' : user.userid, + 'username' : user.username, + 'roles' : user.get_roles(), + 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=45) + }, + app.config['SECRET_KEY'], + "HS256") - return render_template('users/login.html', form=form, errors=errors) + return jsonify({ 'token': token }) - return render_template('users/login.html', form=form, errors=form.errors) + errors = {} + errors['authentication'] = 'Wrong user or password!' + return jsonify(errors) -@app.route('/logout', methods=["GET"]) -@inject -def logout(): - session.pop('userid', None) - session.pop('username', None) - session.pop('userroles', None) - return redirect(url_for('index')) - - -@app.route('/profile', methods=["GET"]) -@auth_required() -@inject -def profile(authService: AuthService): - userid = authService.get_current_user().userid - - return redirect(url_for('getOneUser', userid=userid)) + return jsonify(form.errors) diff --git a/app/dtos/basket_dto.py b/app/dtos/basket_dto.py index 1c7f01d..ab5dfd4 100644 --- a/app/dtos/basket_dto.py +++ b/app/dtos/basket_dto.py @@ -25,4 +25,8 @@ def build_from_entity(basket: Basket): return basket_dto def get_json_parsable(self): - pass \ No newline at end of file + items = [item.__dict__ for item in self.items] + self.items = items + self.user = self.user.get_json_parsable() + + return self.__dict__ \ No newline at end of file diff --git a/app/dtos/user_dto.py b/app/dtos/user_dto.py index 005a45f..2ec41bd 100644 --- a/app/dtos/user_dto.py +++ b/app/dtos/user_dto.py @@ -27,6 +27,7 @@ def build_from_entity(user): user_dto.useremail = user.useremail user_dto.userdescription = user.userdescription user_dto.userroles = [] + for role in user.roles: user_dto.userroles.append(RoleDTO.build_from_entity(role.rel_role)) diff --git a/app/forms/user/user_login_form.py b/app/forms/user/user_login_form.py index 4c38720..65644f3 100644 --- a/app/forms/user/user_login_form.py +++ b/app/forms/user/user_login_form.py @@ -4,5 +4,8 @@ from app.models.user import User class UserLoginForm(FlaskForm): + class Meta: + csrf = False + username = StringField('username', validators=[DataRequired()]) userpassword = StringField('userpassword', validators=[DataRequired()]) \ No newline at end of file diff --git a/app/framework/decorators/auth_required.py b/app/framework/decorators/auth_required.py index c23897b..fbbb9a5 100644 --- a/app/framework/decorators/auth_required.py +++ b/app/framework/decorators/auth_required.py @@ -1,8 +1,11 @@ from functools import wraps -from flask import redirect, session, url_for +from flask import redirect, session, url_for, request, jsonify from app.framework.decorators.inject import inject from app.services.auth_service import AuthService +from jwt import PyJWTError +import jwt +from app import app def auth_required(level="USER", or_is_current_user=False): @@ -10,11 +13,31 @@ def auth_required_decorator(func): @wraps(func) @inject def function_wrapper(authService: AuthService, *args, **kwargs): - current_user = authService.get_current_user() - if level in current_user.get_roles(): + token = None + + if 'x-access-tokens' in request.headers: + token = request.headers['x-access-tokens'] + + if 'Authorization' in request.headers: + token = request.headers['Authorization'] + + if not token: + return jsonify({ "error": "invalid credentials!"}) + + try: + token = token.replace('Bearer ', '') + current_user = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + print(current_user) + print(type(current_user)) + except PyJWTError as e: + print(e) + return redirect(url_for('index')) + + authService.set_current_user(current_user) + if level in current_user['roles']: return func(*args, **kwargs) - if or_is_current_user and current_user.userid == kwargs['userid']: + if or_is_current_user and current_user['userid'] == kwargs['userid']: return func(*args, **kwargs) return redirect(url_for('index')) diff --git a/app/services/auth_service.py b/app/services/auth_service.py index f644ef2..8936d79 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -12,4 +12,7 @@ def __del__(self): @abstractmethod def get_current_user(self) -> UserDTO: + pass + + def set_current_user(self, user): pass \ No newline at end of file diff --git a/app/services/auth_service_impl.py b/app/services/auth_service_impl.py index 8855f41..575bdf5 100644 --- a/app/services/auth_service_impl.py +++ b/app/services/auth_service_impl.py @@ -19,4 +19,5 @@ def __init__(self, userService: UserService): def get_current_user(self) -> UserDTO: return self.__current_user - + def set_current_user(self, user): + self.__current_user = self.__user_service.find_one(user['userid']) diff --git a/app/services/basket_service.py b/app/services/basket_service.py index c2470a7..b0cc806 100644 --- a/app/services/basket_service.py +++ b/app/services/basket_service.py @@ -18,7 +18,9 @@ def find_one(self, entity_id: int): return BasketDTO.build_from_entity(Basket.query.filter_by(basketid=entity_id).one()) def find_one_by(self, **kwargs): - return BasketDTO.build_from_entity(Basket.query.filter_by(**kwargs).one()) + basket = Basket.query.filter_by(**kwargs).one() + print(basket) + return BasketDTO.build_from_entity(basket) def insert(self, data): basket = Basket() diff --git a/requirements.txt b/requirements.txt index c25119b..0a91175 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Flask Flask-WTF +WTForms-JSON Jinja2 Flask-DebugToolbar psycopg2 @@ -7,4 +8,6 @@ bcrypt Flask-SQLAlchemy Flask-Migrate dependency-injector -pyyaml \ No newline at end of file +pyyaml +pyjwt +uuid \ No newline at end of file From ed82d32ce5b8e3ec7f13f7f0e94b608afb5d0069 Mon Sep 17 00:00:00 2001 From: CrenierAmaury Date: Tue, 23 Aug 2022 15:05:16 +0200 Subject: [PATCH 09/20] add last changes --- app/controllers/basket_controller.py | 18 ++++++------- app/controllers/item_controller.py | 35 ++++++++++++++++++-------- app/services/item_service.py | 3 +++ app/templates/items/add_or_update.html | 10 +++++--- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index f1b9313..169dd9b 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -1,20 +1,15 @@ from flask import render_template, session, request, redirect, url_for, jsonify - from app import app from app.framework.decorators.auth_required import auth_required from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.framework.decorators.inject import inject from app.services.basket_service import BasketService -from app.services.item_service import ItemService -from app.services.user_service import UserService -basketService = BasketService() -itemService = ItemService() -userService = UserService() @app.route('/basket/all') @auth_required(level="ADMIN") -def getAllBaskets(): +@inject +def getAllBaskets(basketService: BasketService): return render_template('baskets/list.html', baskets=basketService.find_all()) @app.route('/api/baskets') @@ -40,7 +35,8 @@ def getBasketDetailJson(basketService: BasketService): @app.route('/basket/add', methods=['POST']) @auth_required() -def add_item_to_basket(): +@inject +def add_item_to_basket(basketService: BasketService): item_to_add = BasketAddItemForm(request.form) basketService.add_item(item_to_add) @@ -49,14 +45,16 @@ def add_item_to_basket(): @app.route('/basket/remove/', methods=['POST']) @auth_required() -def remove_item_to_basket(itemid: int): +@inject +def remove_item_to_basket(basketService: BasketService, itemid: int): basketService.remove_item(itemid) return '/basket/remove response' @app.route('/basket/checkout', methods=['POST']) @auth_required() -def checkout_basket(): +@inject +def checkout_basket(basketService: BasketService): basketService.checkout_basket() return '/basket/checkout response' diff --git a/app/controllers/item_controller.py b/app/controllers/item_controller.py index b40f1a8..456d6ea 100644 --- a/app/controllers/item_controller.py +++ b/app/controllers/item_controller.py @@ -6,10 +6,9 @@ from app.framework.decorators.inject import inject from app.services.item_service import ItemService -itemService = ItemService() - @app.route('/items') -def getItemList(): +@inject +def getItemList(itemService: ItemService): form = ItemForm(); return render_template('items/list.html', items=itemService.find_all(), form=form) @@ -24,30 +23,44 @@ def getItemsAsJson(item_service: ItemService): return jsonify([item.get_json_parsable() for item in item_service.find_all()]) @app.route('/items/') -def getItemDetails(itemid): +@inject +def getItemDetails(itemService: ItemService, itemid): form = BasketAddItemForm() return render_template('items/details.html', item=itemService.find_one(itemid), form=form) @app.route('/items/add', methods=['GET','POST']) -def addItem(): +@inject +def addItem(itemService: ItemService): form = ItemForm(request.form) + error = { + "code": 0, + "message": "" + } if request.method == 'POST': if form.validate(): - item = itemService.insert(form) + try: + item = itemService.insert(form) + return redirect(url_for('getItemList')) + except Exception as e: + error["code"] = 600 + error["message"] = e.args + + if form.errors: + error["code"] = 700 + error["message"] = form.errors + - return redirect(url_for('getItemList')) - - print(form.errors) form.itemname.data = '' form.itemdescription.data = '' form.itemstock.data = 1 - return render_template('items/add_or_update.html', form=form) + return render_template('items/add_or_update.html', form=form, error=error) @app.route('/items/update/', methods=['GET','POST']) -def updateItem(itemid: int): +@inject +def updateItem(itemService: ItemService, itemid: int): item = itemService.find_one(itemid) if item is None: diff --git a/app/services/item_service.py b/app/services/item_service.py index 03e1784..9839c8b 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -24,6 +24,7 @@ def insert(self, data): db.session.commit() except Exception as e: print(e) + raise e db.session.rollback() return self.find_one(item.itemid) @@ -39,6 +40,7 @@ def update(self, entity_id: int, data): db.session.commit() except Exception as e: print(e) + raise e db.session.rollback() return self.find_one(entity_id) @@ -54,6 +56,7 @@ def delete(self, entity_id: int): db.session.commit() except Exception as e: print(e) + raise e db.session.rollback() return item.itemid \ No newline at end of file diff --git a/app/templates/items/add_or_update.html b/app/templates/items/add_or_update.html index d2c7ea4..1269524 100644 --- a/app/templates/items/add_or_update.html +++ b/app/templates/items/add_or_update.html @@ -15,11 +15,15 @@ {% endif %} - {% if form.errors and "itemstock" in form.errors.keys() %} - {{ form.errors['itemstock'] }} - {% endif %} +
+

+ {% if error %} + error code {{ error.code }} : {{ error.message }} + {% endif %} +

+
{% endblock %} \ No newline at end of file From 3af22d0ac14df8906d23a2cfd662ca04eac5df56 Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Tue, 23 Aug 2022 15:03:36 +0200 Subject: [PATCH 10/20] [USER] with JWT --- app/__init__.py | 4 +- app/controllers/basket_controller.py | 9 ++- app/controllers/home_controller.py | 4 +- app/controllers/user_controller.py | 92 +++++++++-------------- app/dtos/basket_dto.py | 6 +- app/dtos/user_dto.py | 1 + app/forms/user/user_login_form.py | 3 + app/framework/decorators/auth_required.py | 31 +++++++- app/services/auth_service.py | 3 + app/services/auth_service_impl.py | 3 +- app/services/basket_service.py | 4 +- requirements.txt | 5 +- 12 files changed, 94 insertions(+), 71 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index db64ecb..1efc29c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,4 @@ +import wtforms_json from flask import Flask from flask_debugtoolbar import DebugToolbarExtension from flask_sqlalchemy import SQLAlchemy @@ -7,12 +8,13 @@ app = Flask('app') app.debug = True app.secret_key = 'superSecretKey01@' +wtforms_json.init() toolbar = DebugToolbarExtension(app) app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://app:1234@127.0.0.1:5435/app' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -csrf_protect = CSRFProtect(app) +# csrf_protect = CSRFProtect(app) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index b412aad..2d68c28 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -4,6 +4,7 @@ from app.framework.decorators.auth_required import auth_required from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.framework.decorators.inject import inject +from app.services.auth_service import AuthService from app.services.basket_service import BasketService from app.services.item_service import ItemService from app.services.user_service import UserService @@ -20,10 +21,10 @@ def getAllBaskets(): @app.route('/basket') @auth_required() @inject -def getBasketDetail(basketService: BasketService): - basket = basketService.find_one_by(userid=session.get('userid'), basketclosed=False) - return render_template('baskets/details.html', - basket=basket, items=basket.items, is_basket=True) +def getBasketDetail(basketService: BasketService, authService: AuthService): + user = authService.get_current_user() + basket = basketService.find_one_by(userid=user.userid, basketclosed=False) + return jsonify(basket.get_json_parsable()) @app.route('/api/basket/items') @auth_required() diff --git a/app/controllers/home_controller.py b/app/controllers/home_controller.py index 5303bdd..107716b 100644 --- a/app/controllers/home_controller.py +++ b/app/controllers/home_controller.py @@ -1,7 +1,7 @@ from app import app -from flask import render_template, redirect +from flask import render_template, redirect, jsonify @app.route('/', methods=['GET']) def index(): - return render_template('home/home.html') + return jsonify({'error': 'WRONG URL'}), 500 diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 4dca050..86005c5 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,3 +1,7 @@ +import datetime + +import jwt + from app.framework.decorators.auth_required import auth_required from app.framework.decorators.inject import inject from app.services.auth_service import AuthService @@ -13,94 +17,70 @@ # http://localhost:8080/users -> GET @app.route('/users') @inject -def getUserList(userService: UserService): - form = UserRegisterForm() - - return render_template('users/list.html', users=userService.find_all(), form=form) - -@app.route('/api/users') -@inject -def getUsersAsJson(user_service: UserService): +def getUserList(user_service: UserService): return jsonify([user.get_json_parsable() for user in user_service.find_all()]) # http://localhost:8080/users/5 -> GET @app.route('/users/', methods=["GET"]) @auth_required() @inject -def getOneUser(userid: int, userService: UserService): - user = userService.find_one(userid) +def getOneUser(userid: int, user_service: UserService): + user = user_service.find_one(userid) - return render_template('users/profile.html', user=user) + return jsonify(user.get_json_parsable()) # http://localhost:8080/users/register -> GET | POST -@app.route('/users/register', methods=["GET", "POST"]) +@app.route('/users/register', methods=["POST"]) @inject def register(userService: UserService): form = UserRegisterForm(request.form) - if request.method == 'POST': - if form.validate(): - user = userService.insert(form) + if form.validate(): + user = userService.insert(form) - return redirect(url_for('getOneUser', userid=user.userid)) + return jsonify(user.get_json_parsable()) - return render_template('users/register.html', form=form) + return jsonify(form.errors) -@app.route('/users/update/', methods=["GET", "POST"]) +@app.route('/users/', methods=["PUT"]) @auth_required(level="ADMIN", or_is_current_user=True) @inject def userUpdate(userid: int, userService: UserService, roleService: RoleService): form = UserUpdateForm(request.form) - roles = roleService.find_all() - if request.method == 'POST': - if form.validate(): - user = userService.update(userid, form) + if form.validate(): + user = userService.update(userid, form) - return redirect(url_for('getOneUser', userid=userid)) + return jsonify(user.get_json_parsable()) - user = userService.find_one(userid) - return render_template('users/update.html', form=form, user=user, roles=roles) + return jsonify(form.errors) -@app.route('/login', methods=["GET", "POST"]) +@app.route('/login', methods=["POST"]) @inject def login(userService: UserService): - form = UserLoginForm(request.form) - - if request.method == 'POST': - if form.validate(): - user = userService.login(form) + form = UserLoginForm.from_json(request.json) - if user != None: - session['username'] = user.username - session['userid'] = user.userid - session['userroles'] = user.get_roles() - return redirect(url_for('getOneUser', userid=user.userid)) + if form.validate(): + user = userService.login(form) - errors = {} - errors['authentication'] = 'Wrong user or password!' + if user != None: + token = jwt.encode({ + 'userid' : user.userid, + 'username' : user.username, + 'roles' : user.get_roles(), + 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=45) + }, + app.config['SECRET_KEY'], + "HS256") - return render_template('users/login.html', form=form, errors=errors) + return jsonify({ 'token': token }) - return render_template('users/login.html', form=form, errors=form.errors) + errors = {} + errors['authentication'] = 'Wrong user or password!' + return jsonify(errors) -@app.route('/logout', methods=["GET"]) -@inject -def logout(): - session.pop('userid', None) - session.pop('username', None) - session.pop('userroles', None) - return redirect(url_for('index')) - - -@app.route('/profile', methods=["GET"]) -@auth_required() -@inject -def profile(authService: AuthService): - userid = authService.get_current_user().userid - - return redirect(url_for('getOneUser', userid=userid)) + return jsonify(form.errors) diff --git a/app/dtos/basket_dto.py b/app/dtos/basket_dto.py index 1c7f01d..ab5dfd4 100644 --- a/app/dtos/basket_dto.py +++ b/app/dtos/basket_dto.py @@ -25,4 +25,8 @@ def build_from_entity(basket: Basket): return basket_dto def get_json_parsable(self): - pass \ No newline at end of file + items = [item.__dict__ for item in self.items] + self.items = items + self.user = self.user.get_json_parsable() + + return self.__dict__ \ No newline at end of file diff --git a/app/dtos/user_dto.py b/app/dtos/user_dto.py index 005a45f..2ec41bd 100644 --- a/app/dtos/user_dto.py +++ b/app/dtos/user_dto.py @@ -27,6 +27,7 @@ def build_from_entity(user): user_dto.useremail = user.useremail user_dto.userdescription = user.userdescription user_dto.userroles = [] + for role in user.roles: user_dto.userroles.append(RoleDTO.build_from_entity(role.rel_role)) diff --git a/app/forms/user/user_login_form.py b/app/forms/user/user_login_form.py index 4c38720..65644f3 100644 --- a/app/forms/user/user_login_form.py +++ b/app/forms/user/user_login_form.py @@ -4,5 +4,8 @@ from app.models.user import User class UserLoginForm(FlaskForm): + class Meta: + csrf = False + username = StringField('username', validators=[DataRequired()]) userpassword = StringField('userpassword', validators=[DataRequired()]) \ No newline at end of file diff --git a/app/framework/decorators/auth_required.py b/app/framework/decorators/auth_required.py index c23897b..fbbb9a5 100644 --- a/app/framework/decorators/auth_required.py +++ b/app/framework/decorators/auth_required.py @@ -1,8 +1,11 @@ from functools import wraps -from flask import redirect, session, url_for +from flask import redirect, session, url_for, request, jsonify from app.framework.decorators.inject import inject from app.services.auth_service import AuthService +from jwt import PyJWTError +import jwt +from app import app def auth_required(level="USER", or_is_current_user=False): @@ -10,11 +13,31 @@ def auth_required_decorator(func): @wraps(func) @inject def function_wrapper(authService: AuthService, *args, **kwargs): - current_user = authService.get_current_user() - if level in current_user.get_roles(): + token = None + + if 'x-access-tokens' in request.headers: + token = request.headers['x-access-tokens'] + + if 'Authorization' in request.headers: + token = request.headers['Authorization'] + + if not token: + return jsonify({ "error": "invalid credentials!"}) + + try: + token = token.replace('Bearer ', '') + current_user = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + print(current_user) + print(type(current_user)) + except PyJWTError as e: + print(e) + return redirect(url_for('index')) + + authService.set_current_user(current_user) + if level in current_user['roles']: return func(*args, **kwargs) - if or_is_current_user and current_user.userid == kwargs['userid']: + if or_is_current_user and current_user['userid'] == kwargs['userid']: return func(*args, **kwargs) return redirect(url_for('index')) diff --git a/app/services/auth_service.py b/app/services/auth_service.py index f644ef2..8936d79 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -12,4 +12,7 @@ def __del__(self): @abstractmethod def get_current_user(self) -> UserDTO: + pass + + def set_current_user(self, user): pass \ No newline at end of file diff --git a/app/services/auth_service_impl.py b/app/services/auth_service_impl.py index 8855f41..575bdf5 100644 --- a/app/services/auth_service_impl.py +++ b/app/services/auth_service_impl.py @@ -19,4 +19,5 @@ def __init__(self, userService: UserService): def get_current_user(self) -> UserDTO: return self.__current_user - + def set_current_user(self, user): + self.__current_user = self.__user_service.find_one(user['userid']) diff --git a/app/services/basket_service.py b/app/services/basket_service.py index c2470a7..b0cc806 100644 --- a/app/services/basket_service.py +++ b/app/services/basket_service.py @@ -18,7 +18,9 @@ def find_one(self, entity_id: int): return BasketDTO.build_from_entity(Basket.query.filter_by(basketid=entity_id).one()) def find_one_by(self, **kwargs): - return BasketDTO.build_from_entity(Basket.query.filter_by(**kwargs).one()) + basket = Basket.query.filter_by(**kwargs).one() + print(basket) + return BasketDTO.build_from_entity(basket) def insert(self, data): basket = Basket() diff --git a/requirements.txt b/requirements.txt index c25119b..0a91175 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Flask Flask-WTF +WTForms-JSON Jinja2 Flask-DebugToolbar psycopg2 @@ -7,4 +8,6 @@ bcrypt Flask-SQLAlchemy Flask-Migrate dependency-injector -pyyaml \ No newline at end of file +pyyaml +pyjwt +uuid \ No newline at end of file From 883df21058c4e69297f340d9ac4969fcf8834b8d Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Wed, 24 Aug 2022 11:14:54 +0200 Subject: [PATCH 11/20] [API] --- app/__init__.py | 2 + app/controllers/basket_controller.py | 46 +++++++----------- app/controllers/home_controller.py | 2 +- app/controllers/item_controller.py | 58 +++++++++-------------- app/controllers/user_controller.py | 17 ++++--- app/framework/decorators/auth_required.py | 2 +- app/services/item_service.py | 1 - app/services/user_service.py | 3 +- app/templates/layout/layout.html | 16 +++---- requirements.txt | 3 +- 10 files changed, 67 insertions(+), 83 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 1efc29c..5df3b79 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,6 @@ import wtforms_json from flask import Flask +from flask_cors import CORS from flask_debugtoolbar import DebugToolbarExtension from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate @@ -16,6 +17,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # csrf_protect = CSRFProtect(app) +cors = CORS(app, resources={'/api/*': {'origins': '*'}}) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/controllers/basket_controller.py b/app/controllers/basket_controller.py index 2d68c28..2ea15f7 100644 --- a/app/controllers/basket_controller.py +++ b/app/controllers/basket_controller.py @@ -6,19 +6,14 @@ from app.framework.decorators.inject import inject from app.services.auth_service import AuthService from app.services.basket_service import BasketService -from app.services.item_service import ItemService -from app.services.user_service import UserService -basketService = BasketService() -itemService = ItemService() -userService = UserService() - -@app.route('/basket/all') +@app.route('/api/basket/all') @auth_required(level="ADMIN") -def getAllBaskets(): - return render_template('baskets/list.html', baskets=basketService.find_all()) +@inject +def getAllBaskets(basketService: BasketService): + return jsonify([basket.get_json_parsable() for basket in basketService.find_all()]) -@app.route('/basket') +@app.route('/api/basket') @auth_required() @inject def getBasketDetail(basketService: BasketService, authService: AuthService): @@ -26,33 +21,28 @@ def getBasketDetail(basketService: BasketService, authService: AuthService): basket = basketService.find_one_by(userid=user.userid, basketclosed=False) return jsonify(basket.get_json_parsable()) -@app.route('/api/basket/items') +@app.route('/api/basket/', methods=['PUT']) @auth_required() @inject -def getBasketDetailJson(basketService: BasketService): - basket = basketService.find_one_by(userid=session.get('userid'), basketclosed=False) +def add_item_to_basket(basketService: BasketService): + item_to_add = BasketAddItemForm.from_json(request.json) - return jsonify([item.get_json_parsable() for item in basket.items]) - -@app.route('/basket/add', methods=['POST']) -@auth_required() -def add_item_to_basket(): - item_to_add = BasketAddItemForm(request.form) + basket = basketService.add_item(item_to_add) - basketService.add_item(item_to_add) + return jsonify({ 'status': 'added' }) - return '/basket/add response' - -@app.route('/basket/remove/', methods=['POST']) +@app.route('/api/basket/', methods=['DELETE']) @auth_required() -def remove_item_to_basket(itemid: int): +@inject +def remove_item_to_basket(itemid: int, basketService: BasketService): basketService.remove_item(itemid) - return '/basket/remove response' + return jsonify({ 'status': 'removed' }) -@app.route('/basket/checkout', methods=['POST']) +@app.route('/api/basket/checkout', methods=['POST']) @auth_required() -def checkout_basket(): +@inject +def checkout_basket(basketService: BasketService): basketService.checkout_basket() - return '/basket/checkout response' \ No newline at end of file + return jsonify({ 'status': 'checkout' }) \ No newline at end of file diff --git a/app/controllers/home_controller.py b/app/controllers/home_controller.py index 107716b..dcb8a91 100644 --- a/app/controllers/home_controller.py +++ b/app/controllers/home_controller.py @@ -4,4 +4,4 @@ @app.route('/', methods=['GET']) def index(): - return jsonify({'error': 'WRONG URL'}), 500 + return jsonify({'error': 'invalid credentials'}), 500 diff --git a/app/controllers/item_controller.py b/app/controllers/item_controller.py index 3e17c22..94be63b 100644 --- a/app/controllers/item_controller.py +++ b/app/controllers/item_controller.py @@ -1,64 +1,52 @@ from flask import render_template, request, redirect, url_for, jsonify from app import app -from app.forms.basket.basket_add_item_form import BasketAddItemForm from app.forms.item.item_form import ItemForm +from app.framework.decorators.auth_required import auth_required from app.framework.decorators.inject import inject from app.services.item_service import ItemService -itemService = ItemService() - -@app.route('/items') -def getItemList(): - form = ItemForm(); - return render_template('items/list.html', items=itemService.find_all(), form=form) - @app.route('/api/items') @inject def getItemListAsJson(itemService: ItemService): return jsonify([item.get_json_parsable() for item in itemService.find_all()]) -@app.route('/items/') -def getItemDetails(itemid): - form = BasketAddItemForm() - - return render_template('items/details.html', item=itemService.find_one(itemid), form=form) +@app.route('/api/items/') +@inject +def getItemDetails(itemid, itemService: ItemService): + return jsonify(itemService.find_one(itemid).get_json_parsable()) -@app.route('/items/add', methods=['GET','POST']) -def addItem(): - form = ItemForm(request.form) +@app.route('/api/items/add', methods=['POST']) +@auth_required(level="ADMIN") +@inject +def addItem(itemService: ItemService): + form = ItemForm.from_json(request.json) - if request.method == 'POST': - if form.validate(): - item = itemService.insert(form) + if form.validate(): + item = itemService.insert(form) - return redirect(url_for('getItemList')) + return jsonify(item.get_json_parsable()) - print(form.errors) form.itemname.data = '' form.itemdescription.data = '' form.itemstock.data = 1 - return render_template('items/add_or_update.html', form=form) + return jsonify(form.errors) -@app.route('/items/update/', methods=['GET','POST']) -def updateItem(itemid: int): +@app.route('/api/items/', methods=['PUT']) +@auth_required(level="ADMIN") +@inject +def updateItem(itemid: int, itemService: ItemService): item = itemService.find_one(itemid) if item is None: - return redirect(url_for('getItemList')) + return jsonify({ 'errors': "item not found" }) form = ItemForm(request.form) - if request.method == 'POST': - if form.validate(): - item = itemService.update(itemid, form) - - return redirect(url_for('getItemList')) + if form.validate(): + item = itemService.update(itemid, form) - print(form.errors) - form.itemname.data = item.itemname - form.itemdescription.data = item.itemdescription - form.itemstock.data = item.itemquantity + return jsonify(item.get_json_parsable()) - return render_template('items/add_or_update.html', form=form) + return jsonify(form.errors) diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 86005c5..7247f6d 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -2,6 +2,7 @@ import jwt +from app.dtos.user_dto import UserDTO from app.framework.decorators.auth_required import auth_required from app.framework.decorators.inject import inject from app.services.auth_service import AuthService @@ -15,13 +16,14 @@ # http://localhost:8080/users -> GET -@app.route('/users') +@app.route('/api/users') +@auth_required() @inject def getUserList(user_service: UserService): return jsonify([user.get_json_parsable() for user in user_service.find_all()]) # http://localhost:8080/users/5 -> GET -@app.route('/users/', methods=["GET"]) +@app.route('/api/users/', methods=["GET"]) @auth_required() @inject def getOneUser(userid: int, user_service: UserService): @@ -30,10 +32,10 @@ def getOneUser(userid: int, user_service: UserService): return jsonify(user.get_json_parsable()) # http://localhost:8080/users/register -> GET | POST -@app.route('/users/register', methods=["POST"]) +@app.route('/api/users/register', methods=["POST"]) @inject def register(userService: UserService): - form = UserRegisterForm(request.form) + form = UserRegisterForm.from_json(request.json) if form.validate(): user = userService.insert(form) @@ -43,11 +45,11 @@ def register(userService: UserService): return jsonify(form.errors) -@app.route('/users/', methods=["PUT"]) +@app.route('/api/users/', methods=["PUT"]) @auth_required(level="ADMIN", or_is_current_user=True) @inject def userUpdate(userid: int, userService: UserService, roleService: RoleService): - form = UserUpdateForm(request.form) + form = UserUpdateForm.from_json(request.json) if form.validate(): user = userService.update(userid, form) @@ -57,7 +59,7 @@ def userUpdate(userid: int, userService: UserService, roleService: RoleService): return jsonify(form.errors) -@app.route('/login', methods=["POST"]) +@app.route('/api/login', methods=["POST"]) @inject def login(userService: UserService): form = UserLoginForm.from_json(request.json) @@ -67,6 +69,7 @@ def login(userService: UserService): if user != None: token = jwt.encode({ + 'user': user.get_json_parsable(), 'userid' : user.userid, 'username' : user.username, 'roles' : user.get_roles(), diff --git a/app/framework/decorators/auth_required.py b/app/framework/decorators/auth_required.py index fbbb9a5..e4c4dee 100644 --- a/app/framework/decorators/auth_required.py +++ b/app/framework/decorators/auth_required.py @@ -22,7 +22,7 @@ def function_wrapper(authService: AuthService, *args, **kwargs): token = request.headers['Authorization'] if not token: - return jsonify({ "error": "invalid credentials!"}) + return redirect(url_for('index')) try: token = token.replace('Bearer ', '') diff --git a/app/services/item_service.py b/app/services/item_service.py index 03e1784..dd8c86b 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -38,7 +38,6 @@ def update(self, entity_id: int, data): try: db.session.commit() except Exception as e: - print(e) db.session.rollback() return self.find_one(entity_id) diff --git a/app/services/user_service.py b/app/services/user_service.py index cf0c3d1..2937d90 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,6 +1,7 @@ from flask import session from app import db +from app.dtos.user_dto import UserDTO from app.forms.user.user_login_form import UserLoginForm from app.forms.user.user_register_form import UserRegisterForm from app.forms.user.user_update_form import UserUpdateForm @@ -85,6 +86,6 @@ def login(self, form: UserLoginForm): to_login = self.find_one_by(username=user.username) if to_login is not None and bcrypt.checkpw(user.userpassword.encode('utf-8'), to_login.userpassword.encode('utf-8')): - return to_login + return UserDTO.build_from_entity(to_login) return None \ No newline at end of file diff --git a/app/templates/layout/layout.html b/app/templates/layout/layout.html index 00803b9..9c97127 100644 --- a/app/templates/layout/layout.html +++ b/app/templates/layout/layout.html @@ -11,14 +11,14 @@ crossorigin="anonymous"> diff --git a/requirements.txt b/requirements.txt index 0a91175..9b88268 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,8 @@ psycopg2 bcrypt Flask-SQLAlchemy Flask-Migrate +flask-cors dependency-injector pyyaml pyjwt -uuid \ No newline at end of file +uuid From 776caa4b429a42a18dcc37f2becb749ec35c9790 Mon Sep 17 00:00:00 2001 From: "philippe.stons" Date: Thu, 25 Aug 2022 09:55:36 +0200 Subject: [PATCH 12/20] [DOTENV] --- .env | 5 +++++ .env.local | 7 +++++++ .gitignore | 1 - app/__init__.py | 19 +++++++++++++++---- app/controllers/item_controller.py | 4 ---- app/forms/item/item_form.py | 4 ++-- app/services/item_service.py | 2 ++ requirements.txt | 1 + 8 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 .env create mode 100644 .env.local diff --git a/.env b/.env new file mode 100644 index 0000000..1b1faaf --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +DEBUG=True +FLASK_ENV=development +DATABASE_URL=postgresql://app:1234@127.0.0.1:5435/app +JWT_KEY=1234 +CORS_ORIGIN=* diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..0d2af71 --- /dev/null +++ b/.env.local @@ -0,0 +1,7 @@ +# commentaire +# exemple d'une db de prod +# le .env.local sur une vraie application ne sera jamais push sur un git et ne servira qu'aux configurations locale +# d'un serveur de production ou d'acceptance/test. +# DATABASE_URL=postgresql://proddbuser:ASkjkmKAMM19os88!@*mamksjdui@94.32.55.5:5432/proddb +JWT_KEY=asdkj9AK2j1ak5jsmnAs152MAJnKJAMskjad53 +# CORS_ORIGIN=https://www.odoo.front.com/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0fdefb4..76779a7 100644 --- a/.gitignore +++ b/.gitignore @@ -97,7 +97,6 @@ celerybeat-schedule *.sage.py # Environments -.env .venv env/ venv/ diff --git a/app/__init__.py b/app/__init__.py index 5df3b79..d4d4b6f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,6 @@ +import os +from pathlib import Path + import wtforms_json from flask import Flask from flask_cors import CORS @@ -5,19 +8,27 @@ from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_wtf import CSRFProtect +from dotenv import load_dotenv + +envvars = Path().cwd() / '.env.local' +load_dotenv() + +if os.path.exists(envvars): + load_dotenv(envvars, override=True, verbose=True) app = Flask('app') -app.debug = True -app.secret_key = 'superSecretKey01@' +app.debug = os.environ.get("DEBUG") +app.secret_key = os.environ.get("JWT_KEY") wtforms_json.init() toolbar = DebugToolbarExtension(app) app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False -app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://app:1234@127.0.0.1:5435/app' +app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("DATABASE_URL") +print(os.environ.get("DATABASE_URL")) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # csrf_protect = CSRFProtect(app) -cors = CORS(app, resources={'/api/*': {'origins': '*'}}) +cors = CORS(app, resources={'/api/*': {'origins': os.environ.get("CORS_ORIGIN")}}) db = SQLAlchemy(app) migrate = Migrate(app, db) diff --git a/app/controllers/item_controller.py b/app/controllers/item_controller.py index 94be63b..604f6d8 100644 --- a/app/controllers/item_controller.py +++ b/app/controllers/item_controller.py @@ -27,10 +27,6 @@ def addItem(itemService: ItemService): return jsonify(item.get_json_parsable()) - form.itemname.data = '' - form.itemdescription.data = '' - form.itemstock.data = 1 - return jsonify(form.errors) @app.route('/api/items/', methods=['PUT']) diff --git a/app/forms/item/item_form.py b/app/forms/item/item_form.py index 5baaefa..c51cb00 100644 --- a/app/forms/item/item_form.py +++ b/app/forms/item/item_form.py @@ -1,9 +1,9 @@ from flask_wtf import FlaskForm from wtforms import StringField, IntegerField -from wtforms.validators import DataRequired, NumberRange +from wtforms.validators import DataRequired, NumberRange, Length class ItemForm(FlaskForm): - itemname = StringField('itemname', validators=[DataRequired()]) + itemname = StringField('itemname', validators=[DataRequired(), Length(min=2, max=255)]) itemdescription = StringField('itemdescription', validators=[DataRequired()]) itemstock = IntegerField('itemstock', validators=[DataRequired(), NumberRange(min=0)]) \ No newline at end of file diff --git a/app/services/item_service.py b/app/services/item_service.py index dd8c86b..181ba59 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -1,3 +1,5 @@ +import datetime + from app import db from app.dtos.item_dto import ItemDTO from app.mappers.item_mapper import ItemMapper diff --git a/requirements.txt b/requirements.txt index 9b88268..d33ea31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ dependency-injector pyyaml pyjwt uuid +python-dotenv From 8a6a17e084acddf31463eea3a179a2468186362e Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:34:20 +0200 Subject: [PATCH 13/20] Update README.md --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index ead5504..cd83d0e 100644 --- a/README.md +++ b/README.md @@ -1 +1,49 @@ # pythonORM + +## 1) models + ex + class Example: + def __init__(self, data): + self.data = data +## 2) formulares + from app import db + from app.models.base_entity import BaseEntity + + class Role(BaseEntity, db.Model): + __tablename__ = "roles" + roleid = db.Column(db.Integer, primary_key=True) + rolename = db.Column(db.String(50), nullable=False, unique=True, index=True) + +### 2.2) relationship one to many + class Parent(BaseEntity, db.Model): + __tablename__ = "parents" + parentid = db.Column(db.Integer, primary_key=True) + childid = db.Column(db.ForeignKey('children.childid')) + children = db.relationship("Child", cascade='all, delete-orphan') + + class Child(BaseEntity, db.Model): + __tablename__ = "children" + childid = db.Column(db.Integer, primary_key=True) + parent = db.relationship("User", back_populates="children") +## 3) mappers + Créer les mapper pour passer des entités au dtos, et mettre à jours les entité à partir des forms. + +## 4) services + - des fonctions pour contacter la db : + - findOne => retourne une entité en fonction de son id + - findOneBy => retourne une entité en fonction d'un paramètre passé (données d'une des colonnes) + - findAll => retourne toutes les data + - update => met à jours les donnés en DB + - insert => insert des nouvelles donnée en DB + - delete => delete une entrée en DB + +## 5) controllers + class ExampleController: + # http://servername/path + @app.route('/path', methods=['GET', 'POST']) + def getAllData(): + return jsonify([example.get_json_parseable() for example in service.findAll()]) + # http://servername/path/1 + @app.route('/path/', methods=['GET', 'POST']) + def getAllData(dataid): + return jsonify(service.findOne().get_json_parseable()) From 7aa8e03e762dfc2fa99b87e8f89297ef367eac13 Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:40:17 +0200 Subject: [PATCH 14/20] Update README.md --- README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cd83d0e..16e3212 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # pythonORM ## 1) models - ex - class Example: - def __init__(self, data): - self.data = data -## 2) formulares from app import db from app.models.base_entity import BaseEntity @@ -13,8 +8,9 @@ __tablename__ = "roles" roleid = db.Column(db.Integer, primary_key=True) rolename = db.Column(db.String(50), nullable=False, unique=True, index=True) - -### 2.2) relationship one to many + self.data = data + +### 1.2) relationship one to many class Parent(BaseEntity, db.Model): __tablename__ = "parents" parentid = db.Column(db.Integer, primary_key=True) @@ -25,6 +21,15 @@ __tablename__ = "children" childid = db.Column(db.Integer, primary_key=True) parent = db.relationship("User", back_populates="children") + +## 2) formulares + class ExampleForm(FlaskForm): + # désactiver CSRF pour les api + class Meta: + csrf = False + exampleData = StringField('exampleData', validators=[]) + exampleData2 = StringField('exampleData2', validators=[DataRequired()]) + ## 3) mappers Créer les mapper pour passer des entités au dtos, et mettre à jours les entité à partir des forms. From 9601ea1b39312090a119957f398284de3b8a26b6 Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:55:40 +0200 Subject: [PATCH 15/20] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 16e3212..824698b 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,9 @@ @app.route('/path/', methods=['GET', 'POST']) def getAllData(dataid): return jsonify(service.findOne().get_json_parseable()) + + +## 6) Dépendence + les mappers dépendent des modèles, des DTOs et des forms + les services dépendent des modèles et des mappers + les controllers dépendent des services. From 64920a683942c70a8c69185bc5e6473e05e4cc4b Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:58:44 +0200 Subject: [PATCH 16/20] Update README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 824698b..fa1a5d2 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,16 @@ les mappers dépendent des modèles, des DTOs et des forms les services dépendent des modèles et des mappers les controllers dépendent des services. + +## 7) database + la connection string doit être modifiée dans le .env ou .env.local + pour initialiser la db utiliser : + $ ./sqlAlchemy.sh -i + + pour créer de nouvelles migrations : + $ ./sqlAlchemy.sh -m nom_de_la_migration + + Le fichier de la migration peut être modifier si nécéssaire pour mettre à jours ou rajouter des données dans la DB. + + une fois les migrations créées on peut les appliquer avec la commande : + $ ./sqlAlchemy.sh -u From 01577ed7637b54a5d11ff3daca729d5bde7b9e1d Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 11:01:16 +0200 Subject: [PATCH 17/20] Update README.md --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fa1a5d2..1318854 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,14 @@ exampleData2 = StringField('exampleData2', validators=[DataRequired()]) ## 3) mappers - Créer les mapper pour passer des entités au dtos, et mettre à jours les entité à partir des forms. + Créer les mapper pour passer des entités au dtos, et mettre à jours les entité + à partir des forms. ## 4) services - des fonctions pour contacter la db : - findOne => retourne une entité en fonction de son id - - findOneBy => retourne une entité en fonction d'un paramètre passé (données d'une des colonnes) + - findOneBy => retourne une entité en fonction d'un + paramètre passé (données d'une des colonnes) - findAll => retourne toutes les data - update => met à jours les donnés en DB - insert => insert des nouvelles donnée en DB @@ -67,7 +69,16 @@ pour créer de nouvelles migrations : $ ./sqlAlchemy.sh -m nom_de_la_migration - Le fichier de la migration peut être modifier si nécéssaire pour mettre à jours ou rajouter des données dans la DB. + Le fichier de la migration peut être modifier si nécéssaire pour mettre à jours ou rajouter + des données dans la DB. une fois les migrations créées on peut les appliquer avec la commande : $ ./sqlAlchemy.sh -u + +## 8) lancer le serveur + $ python3 runserver.py + + pour lancer le serveur en debug depuis votre ide, il faudra soit créer un launch.json + via l'onglet de debugging de VSCode. + Soit on peut créer une nouvelle configuration de debugging dans Pycharm et mettre le runserver.py + comme programme de lancement. From 94a035c3095b3ef40ec9343df4a301b7eabf5416 Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:48:29 +0200 Subject: [PATCH 18/20] Update .env.local --- .env.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.local b/.env.local index 0d2af71..69cb543 100644 --- a/.env.local +++ b/.env.local @@ -2,6 +2,6 @@ # exemple d'une db de prod # le .env.local sur une vraie application ne sera jamais push sur un git et ne servira qu'aux configurations locale # d'un serveur de production ou d'acceptance/test. -# DATABASE_URL=postgresql://proddbuser:ASkjkmKAMM19os88!@*mamksjdui@94.32.55.5:5432/proddb +# DATABASE_URL=postgresql://proddbuser:ASkjkmKAMM19os88!@*mamksjdui@prod url and port/proddb JWT_KEY=asdkj9AK2j1ak5jsmnAs152MAJnKJAMskjad53 -# CORS_ORIGIN=https://www.odoo.front.com/ \ No newline at end of file +# CORS_ORIGIN=https://www.odoo.front.com/ From 30e871548d803cc99d6ff75c60a6341a85ac7031 Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:06:24 +0200 Subject: [PATCH 19/20] Update sqlAlchemy.sh --- sqlAlchemy.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlAlchemy.sh b/sqlAlchemy.sh index a0c3e0f..b17e901 100755 --- a/sqlAlchemy.sh +++ b/sqlAlchemy.sh @@ -1,11 +1,12 @@ export FLASK_APP=app -while getopts 'm:iu' flag +while getopts 'm:iue:' flag do case "${flag}" in m) DB_CMD="flask db migrate -m ${OPTARG}";; i) DB_CMD="flask db init";; u) DB_CMD="flask db upgrade";; + e) DB_CMD="flask db revision --autogenerate ${OPTARG}";; esac done @@ -17,4 +18,4 @@ then echo -e '-i \t init project database' else $DB_CMD -fi \ No newline at end of file +fi From 7ff907b526766f6202f13bf8bc63066fafa7c12e Mon Sep 17 00:00:00 2001 From: phil-form <84183611+phil-form@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:12:42 +0200 Subject: [PATCH 20/20] Update sqlAlchemy.sh --- sqlAlchemy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlAlchemy.sh b/sqlAlchemy.sh index b17e901..b709bb8 100755 --- a/sqlAlchemy.sh +++ b/sqlAlchemy.sh @@ -1,12 +1,12 @@ export FLASK_APP=app -while getopts 'm:iue:' flag +while getopts 'm:iue' flag do case "${flag}" in m) DB_CMD="flask db migrate -m ${OPTARG}";; i) DB_CMD="flask db init";; u) DB_CMD="flask db upgrade";; - e) DB_CMD="flask db revision --autogenerate ${OPTARG}";; + e) DB_CMD="flask db revision --autogenerate";; esac done