Browse Source

Feb 12 [UPDT] : Updated 'login_using_qr'

pull/267/merge
AjmalCybro 1 year ago
parent
commit
1003d59601
  1. 24
      login_using_qr/README.rst
  2. 4
      login_using_qr/__init__.py
  3. 25
      login_using_qr/__manifest__.py
  4. 12
      login_using_qr/authenticate.py
  5. 6
      login_using_qr/controllers/__init__.py
  6. 41
      login_using_qr/controllers/login_using_qr.py
  7. 68
      login_using_qr/controllers/qr_scanner.py
  8. 7
      login_using_qr/doc/RELEASE_NOTES.md
  9. 4
      login_using_qr/models/__init__.py
  10. 31
      login_using_qr/models/res_users.py
  11. BIN
      login_using_qr/static/description/assets/misc/Cybrosys R.png
  12. BIN
      login_using_qr/static/description/assets/modules/1.jpg
  13. BIN
      login_using_qr/static/description/assets/modules/1.png
  14. BIN
      login_using_qr/static/description/assets/modules/2.jpg
  15. BIN
      login_using_qr/static/description/assets/modules/2.png
  16. BIN
      login_using_qr/static/description/assets/modules/3.png
  17. BIN
      login_using_qr/static/description/assets/modules/4.png
  18. BIN
      login_using_qr/static/description/assets/modules/5.gif
  19. BIN
      login_using_qr/static/description/assets/modules/5.jpg
  20. BIN
      login_using_qr/static/description/assets/modules/6.jpg
  21. BIN
      login_using_qr/static/description/assets/modules/6.png
  22. BIN
      login_using_qr/static/description/assets/screenshots/1.png
  23. BIN
      login_using_qr/static/description/assets/screenshots/2.png
  24. BIN
      login_using_qr/static/description/assets/screenshots/3.png
  25. BIN
      login_using_qr/static/description/assets/screenshots/4.png
  26. BIN
      login_using_qr/static/description/assets/screenshots/5.png
  27. BIN
      login_using_qr/static/description/assets/screenshots/hero.gif
  28. BIN
      login_using_qr/static/description/assets/screenshots/screenshot1.png
  29. BIN
      login_using_qr/static/description/assets/screenshots/screenshot2.png
  30. BIN
      login_using_qr/static/description/assets/screenshots/screenshot3.png
  31. BIN
      login_using_qr/static/description/assets/screenshots/screenshot4.png
  32. BIN
      login_using_qr/static/description/assets/screenshots/screenshot5.png
  33. BIN
      login_using_qr/static/description/banner.jpg
  34. BIN
      login_using_qr/static/description/icon.png
  35. 1249
      login_using_qr/static/description/index.html
  36. 34
      login_using_qr/static/src/css/login_with_qr.css
  37. 54
      login_using_qr/static/src/js/login_with_qr.js
  38. 21
      login_using_qr/views/login_templates.xml
  39. 33
      login_using_qr/views/login_using_qr_templates.xml
  40. 18
      login_using_qr/views/redirect_page_templates.xml
  41. 19
      login_using_qr/views/res_users_views.xml

24
login_using_qr/README.rst

@ -1,18 +1,32 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
Login By QR Code
======================
Login using Qr code
=============
Internal users can login by scanning QR Code.
Configuration
=============
* No additional configurations needed
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
License
-------
General Public License, Version 3 (AGPL v3).
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Credits
-------
Developer: (v16) muzammil @ cybrosys
Developer:
(V15) Mohamed Muzammil VP,
(v16) muzammil,
(v17) Anjhana A K,
Contact: odoo@cybrosys.com
Contacts
--------

4
login_using_qr/__init__.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.

25
login_using_qr/__manifest__.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
@ -23,25 +23,28 @@
'name': 'Login using QR Code',
'version': '16.0.1.0.0',
'category': 'Extra Tools',
'summary': """Users can login by scanning QR Code""",
'description': """A QR code is generated inside the users model,
a Internal user can use this QR code to login to their
account by scanning it""",
'summary': 'Users can login by scanning QR Code',
'description': 'A QR code is generate corresponding to each internal user'
' and,they can use this QR code to login to their account by'
' scanning it',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': 'http://www.cybrosys.com',
'depends': ['base', 'website'],
'data': [
'views/login_templates.xml',
'views/redirect_page_templates.xml',
'views/res_users_views.xml',
'views/login_using_qr_templates.xml',
],
'external_dependencies': {
'python': ['pyzbar', 'cv2'],
'assets': {
'web.assets_frontend': [
'login_using_qr/static/src/js/login_with_qr.js',
'login_using_qr/static/src/css/login_with_qr.css',
'https://cdn.rawgit.com/cozmo/jsQR/master/dist/jsQR.js',
],
},
'license': 'AGPL-3',
'images': ['static/description/banner.jpg'],
'license': 'AGPL-3',
'installable': True,
'auto_install': False,
'application': False,

12
login_using_qr/authenticate.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
@ -19,6 +19,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import odoo
from odoo.http import request
from odoo.modules.registry import Registry
@ -28,23 +29,18 @@ from odoo.http import Session
def authenticate_without_passwd(self, dbname, login):
"""This function creates a authentication methode without password"""
registry = Registry(dbname)
pre_uid = request.env['res.users'].search([('login', '=', login)]).id
pre_uid = request.env['res.users'].sudo().search([('login', '=', login)]).id
self.uid = None
self.pre_login = login
self.pre_uid = pre_uid
with registry.cursor() as cr:
env = odoo.api.Environment(cr, pre_uid, {})
user = env['res.users'].browse(pre_uid)
if not user._mfa_url():
self.finalize(env)
if request and request.session is self and request.db == dbname:
request.env = odoo.api.Environment(request.env.cr, self.uid,
self.context)
request.update_context(**self.context)
return pre_uid
Session.authenticate_without_passwd = authenticate_without_passwd

6
login_using_qr/controllers/__init__.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
@ -19,4 +19,4 @@
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from . import qr_scanner
from . import login_using_qr

41
login_using_qr/controllers/login_using_qr.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from odoo import http
from odoo.http import Controller, request
class LoginController(Controller):
"""Controller that works when Login With QR clicked"""
@http.route(['/web/redirect'], type='json', auth='none', website=True,
csrf=False, csrf_token=None)
def scanner(self, scanned_qr):
"""This code scans the QR provided and Login to the corresponding user
note: Only Internal User can log in through it"""
users = request.env['res.users'].sudo().search([('share', '=', False)])
login = users.mapped('login')
if scanned_qr in login:
request.session.authenticate_without_passwd(
request.session.db, scanned_qr)
return request.redirect('/')
else:
return False

68
login_using_qr/controllers/qr_scanner.py

@ -1,68 +0,0 @@
import cv2
from pyzbar.pyzbar import decode
from odoo.http import Controller, request
from odoo import http
SIGN_UP_REQUEST_PARAMS = {'db', 'login', 'debug', 'token', 'message', 'error',
'scope', 'mode',
'redirect', 'redirect_hostname', 'email', 'name',
'partner_id',
'password', 'confirm_password', 'city', 'country_id',
'lang', 'signup_email'}
class LoginController(Controller):
"""controller that works when Login With QR clicked"""
@http.route(['/web/redirect'], type='http', auth='none', website=True,
csrf=False, csrf_token=None)
def open_scanner(self, *args, **kw):
"""This code scan the QR provided and Login to the corresponding user
note: Only Internal User can login through it"""
try:
cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)
while True:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
for qr_code in decode(gray):
(x, y, w, h) = qr_code.rect
cv2.rectangle(frame, (x, y), (x + w, y + h),
(0, 255, 0), 2)
decoded_text = qr_code.data.decode("utf-8")
users = request.env['res.users'].search(
[('share', '=', False)])
login = users.mapped('login')
if decoded_text in login:
request.session.authenticate_without_passwd(
request.session.db, decoded_text)
cap.release()
cv2.destroyAllWindows()
return request.redirect('/')
else:
cap.release()
cv2.destroyAllWindows()
# Use the overridden web_login method to show error message
values = {k: v for k, v in request.params.items() if
k in SIGN_UP_REQUEST_PARAMS}
values['error'] = ("Wrong QR Code")
request.update_env(user=request.session.uid)
request.env["ir.http"]._auth_method_public()
response = request.render('web.login', values)
return response
# Display the resulting frame
cv2.imshow('scanner- to exit press "q"', frame)
code = cv2.waitKey(1)
if code == ord('q'):
cap.release()
cv2.destroyAllWindows()
return request.redirect('/web/login')
except Exception:
return request.render("login_using_qr.be_patient")

7
login_using_qr/doc/RELEASE_NOTES.md

@ -1,7 +1,6 @@
## Module <login_using_qr>
#### 29.07.2023
#### Version 16.0.1.0.0
#### 09.02.2024
#### Version 17.0.1.0.0
#### ADD
- Initial commit for Login using QR Code
- Initial commit for Login using Qr code

4
login_using_qr/models/__init__.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.

31
login_using_qr/models/res_users.py

@ -3,8 +3,8 @@
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Mohamed Muzammil VP (odoo@cybrosys.com)
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Anjhana A K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
@ -19,7 +19,6 @@
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
try:
import qrcode
except ImportError:
@ -34,28 +33,26 @@ from odoo.exceptions import UserError
class ResUsers(models.Model):
""" inherit res.users to add a field in users """
""" Inherit the model to add a field and methods"""
_inherit = "res.users"
qr_code = fields.Binary('QRcode', compute="_compute_generate_qr",
qr_code = fields.Binary(string='QRcode', compute="_compute_qr_code",
help="Use this to login (only for internal users)")
def _compute_generate_qr(self):
"""method to generate QR code"""
def _compute_qr_code(self):
"""Method to generate QR code"""
for detail in self:
if qrcode and base64:
qr = qrcode.QRCode(
qr_code = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=1,
)
qr.add_data(detail.login)
qr.make(fit=True)
img = qr.make_image()
box_size=3,
border=4,)
qr_code.add_data(detail.login)
qr_code.make(fit=True)
temp = BytesIO()
img.save(temp, format="PNG",optimise=True)
qr_image = base64.b64encode(temp.getvalue())
detail.update({'qr_code': qr_image})
qr_code.make_image().save(temp, format="PNG")
detail.update({'qr_code': base64.b64encode(temp.getvalue())})
else:
raise UserError(
_('Necessary Requirements To Run This Operation Is '

BIN
login_using_qr/static/description/assets/misc/Cybrosys R.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
login_using_qr/static/description/assets/modules/1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
login_using_qr/static/description/assets/modules/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

BIN
login_using_qr/static/description/assets/modules/2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
login_using_qr/static/description/assets/modules/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

BIN
login_using_qr/static/description/assets/modules/3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 76 KiB

BIN
login_using_qr/static/description/assets/modules/4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 46 KiB

BIN
login_using_qr/static/description/assets/modules/5.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

BIN
login_using_qr/static/description/assets/modules/5.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
login_using_qr/static/description/assets/modules/6.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
login_using_qr/static/description/assets/modules/6.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

BIN
login_using_qr/static/description/assets/screenshots/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

BIN
login_using_qr/static/description/assets/screenshots/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
login_using_qr/static/description/assets/screenshots/3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

BIN
login_using_qr/static/description/assets/screenshots/4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
login_using_qr/static/description/assets/screenshots/5.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
login_using_qr/static/description/assets/screenshots/hero.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

BIN
login_using_qr/static/description/assets/screenshots/screenshot1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
login_using_qr/static/description/assets/screenshots/screenshot2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

BIN
login_using_qr/static/description/assets/screenshots/screenshot3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

BIN
login_using_qr/static/description/assets/screenshots/screenshot4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

BIN
login_using_qr/static/description/assets/screenshots/screenshot5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

BIN
login_using_qr/static/description/banner.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 73 KiB

BIN
login_using_qr/static/description/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

1249
login_using_qr/static/description/index.html

File diff suppressed because it is too large

34
login_using_qr/static/src/css/login_with_qr.css

@ -0,0 +1,34 @@
.clearfix.oe_login_buttons.text-center.mb-1.pt-3{
position:relative;
}
.qr_video {
position: absolute;
top: -353px;
left: -269px;
}
.video-container{
width:717px;
}
.video-container video{
width:100% !important;
height:100% !important;
}
.o_database_list .input-group-prepend .btn, .input-group-append a{
z-index:0 !important;
}
@media (max-width: 767.98px) {
.qr_video{
position: absolute;
top: -281px;
min-width: 200px;
left: -63px;
}
.video-container {
width:360px;
}}

54
login_using_qr/static/src/js/login_with_qr.js

@ -0,0 +1,54 @@
/** @odoo-module **/
import rpc from 'web.rpc';
var publicWidget = require('web.public.widget');
var ajax = require('web.ajax');
publicWidget.registry.QrLogin = publicWidget.Widget.extend({
selector: '.oe_qr_login',
events: {
'click #login_click': '_onLoginClick',
'click #close_qr_scanner':'_onClickClose'
},
_onClickClose: function(ev) {
window.location.reload();
},
async _onLoginClick(ev) {
ev.target.offsetParent.querySelector('.close_button').classList.remove('d-none');
const video = ev.target.offsetParent.querySelector('#video');
var cam_stream = await navigator.mediaDevices.getUserMedia({ video: true});
video.srcObject = cam_stream;
video.addEventListener('loadedmetadata', (event) => {
// Adjust video size once metadata is loaded
video.width = video.videoWidth;
video.height = video.videoHeight;
});
video.addEventListener('canplay', () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = video.width;
canvas.height = video.height;
setInterval(() => {
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
ajax.jsonRpc('/web/redirect', 'call', {'scanned_qr': code.data}
).then((token) => {
if(token){
cam_stream.getTracks().forEach(function(track) {
track.stop();
window.location.href = '/';
});
}
else{
alert('Scanned QR does not exist. Please try again.');
window.location.reload();
}
});
}
}, 1000); // Adjust the interval as needed
});
}
})

21
login_using_qr/views/login_templates.xml

@ -1,11 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--calling the controller of scanner from login page-->
<!--Calling the controller of scanner from login page-->
<template id="qr_login" inherit_id="web.login" name="QR scanner">
<xpath expr="//button[hasclass('btn-primary')]" position="after">
<div class="justify-content-between mt-2 d-flex small">
<a href="/web/redirect">Login With QR</a>
<xpath expr="//div[hasclass('o_login_auth')]" position="before">
<div class="justify-content-between mt-2 d-flex small oe_qr_login">
<a href="#" id="login_click" t-on-click='_onLoginClick'>Login With QR</a>
<div class="qr_video">
<div class="close_button d-none position-absolute" t-ref="close_button">
<button id="close_qr_scanner" style="position: absolute; right: 0px; z-index: 111">
X
</button>
<div class="video-container">
<video id="video" width="" height="" autoplay="true"/>
</div>
</div>
</div>
</div>
</xpath>
</template>
</odoo>

33
login_using_qr/views/login_using_qr_templates.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Template to show if the scanned QR Code is not accepted -->
<template id="redirect_to">
<div>
<center>
<h5>
This feature is currently not available for you
</h5>
<a href="/web/login">
<h5>
Redirect to Login Page
</h5>
</a>
</center>
</div>
</template>
<!-- Template to show if any error occurs in opening of scanner -->
<template id="be_patient">
<div>
<center>
<h5>
Scan the QR, or else,
</h5>
<a href="/web/login">
<h5>
Redirect to Login Page
</h5>
</a>
</center>
</div>
</template>
</odoo>

18
login_using_qr/views/redirect_page_templates.xml

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- template to show if any error occur in opening of scanner -->
<template id="be_patient">
<div>
<center>
<h5>
Scan the QR, or else,
</h5>
<a href="/web/login">
<h5>
Redirect to login page
</h5>
</a>
</center>
</div>
</template>
</odoo>

19
login_using_qr/views/res_users_views.xml

@ -1,20 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--adding field in res.users-->
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.view.form.inherit.login.using.qr</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<data>
<!--Adding field in res.users-->
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.view.form.inherit.login.using.qr</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='access_rights']" position="after">
<page string="Login QR Code" name="qr_page">
<span>LOGIN QR</span><br/>
<field name="qr_code" class="oe_form_binary_file" widget="image"/>
</page>
</xpath>
</data>
</field>
</record>
</field>
</record>
</odoo>

Loading…
Cancel
Save