[MERGE] forward port branch saas-6 up to 9d04d14669
This commit is contained in:
@@ -86,10 +86,15 @@ class EscposDriver(Thread):
|
||||
printers = usb.core.find(find_all=True, idVendor=0x0519)
|
||||
|
||||
for printer in printers:
|
||||
try:
|
||||
description = usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct)
|
||||
except Exception as e:
|
||||
_logger.error("Can not get printer description: %s" % (e.message or repr(e)))
|
||||
description = 'Unknown printer'
|
||||
connected.append({
|
||||
'vendor': printer.idVendor,
|
||||
'product': printer.idProduct,
|
||||
'name': usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct)
|
||||
'name': description
|
||||
})
|
||||
|
||||
return connected
|
||||
|
||||
@@ -58,7 +58,10 @@ index_template = """
|
||||
If you need to grant remote debugging access to a developer, you can do it <a href='/remote_connect'>here</a>.
|
||||
</p>
|
||||
<p>
|
||||
The PosBox software installed on this posbox is <b>version 15</b>,
|
||||
If you need to display the current customer basket on another device, you can do it <a href='/point_of_sale/display'>here</a>.
|
||||
</p>
|
||||
<p>
|
||||
The PosBox software installed on this posbox is <b>version 16</b>,
|
||||
the posbox version number is independent from Odoo. You can upgrade
|
||||
the software on the <a href='/hw_proxy/upgrade/'>upgrade page</a>.
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import controllers
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': 'Screen Driver',
|
||||
'version': '1.0',
|
||||
'category': 'Hardware Drivers',
|
||||
'sequence': 6,
|
||||
'summary': 'Provides support for customer facing displays',
|
||||
'website': 'https://www.odoo.com/page/point-of-sale',
|
||||
'description': """
|
||||
Screen Driver
|
||||
=============
|
||||
|
||||
This module allows the POS client to send rendered HTML to a remotely
|
||||
installed screen. This module then displays this HTML using a web
|
||||
browser.
|
||||
""",
|
||||
'depends': ['hw_proxy'],
|
||||
'installable': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import main
|
||||
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import http
|
||||
from openerp.tools import config
|
||||
from openerp.addons.web.controllers import main as web
|
||||
|
||||
import logging
|
||||
import netifaces as ni
|
||||
import os
|
||||
from subprocess import call
|
||||
import time
|
||||
import threading
|
||||
|
||||
self_port = str(config['xmlrpc_port'] or 8069)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HardwareScreen(web.Home):
|
||||
|
||||
event_data = threading.Event()
|
||||
pos_client_data = {'rendered_html': False,
|
||||
'ip_from': False}
|
||||
display_in_use = ''
|
||||
failure_count = {}
|
||||
|
||||
def _call_xdotools(self, keystroke):
|
||||
os.environ['DISPLAY'] = ":0.0"
|
||||
os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
|
||||
try:
|
||||
call(['xdotool', 'key', keystroke])
|
||||
return "xdotool succeeded in stroking " + keystroke
|
||||
except:
|
||||
return "xdotool threw an error, maybe it is not installed on the posbox"
|
||||
|
||||
@http.route('/hw_proxy/display_refresh', type='json', auth='none', cors='*')
|
||||
def display_refresh(self):
|
||||
return self._call_xdotools('F5')
|
||||
|
||||
# POS CASHIER'S ROUTES
|
||||
@http.route('/hw_proxy/customer_facing_display', type='json', auth='none', cors='*')
|
||||
def update_user_facing_display(self, html=None):
|
||||
request_ip = http.request.httprequest.remote_addr
|
||||
if request_ip == HardwareScreen.pos_client_data.get('ip_from', ''):
|
||||
HardwareScreen.pos_client_data['rendered_html'] = html
|
||||
HardwareScreen.event_data.set()
|
||||
|
||||
return {'status': 'updated'}
|
||||
else:
|
||||
return {'status': 'failed'}
|
||||
|
||||
@http.route('/hw_proxy/take_control', type='json', auth='none', cors='*')
|
||||
def take_control(self, html=None):
|
||||
# ALLOW A CASHIER TO TAKE CONTROL OVER THE POSBOX, IN CASE OF MULTIPLE CASHIER PER POSBOX
|
||||
HardwareScreen.pos_client_data['rendered_html'] = html
|
||||
HardwareScreen.pos_client_data['ip_from'] = http.request.httprequest.remote_addr
|
||||
HardwareScreen.event_data.set()
|
||||
|
||||
return {'status': 'success',
|
||||
'message': 'You now have access to the display'}
|
||||
|
||||
@http.route('/hw_proxy/test_ownership', type='json', auth='none', cors='*')
|
||||
def test_ownership(self):
|
||||
if HardwareScreen.pos_client_data.get('ip_from') == http.request.httprequest.remote_addr:
|
||||
return {'status': 'OWNER'}
|
||||
else:
|
||||
return {'status': 'NOWNER'}
|
||||
|
||||
# POSBOX ROUTES (SELF)
|
||||
@http.route('/point_of_sale/display', type='http', auth='none')
|
||||
def render_main_display(self):
|
||||
return self._get_html()
|
||||
|
||||
@http.route('/point_of_sale/get_serialized_order', type='json', auth='none')
|
||||
def get_serialized_order(self):
|
||||
request_addr = http.request.httprequest.remote_addr
|
||||
result = HardwareScreen.pos_client_data
|
||||
if HardwareScreen.display_in_use and request_addr != HardwareScreen.display_in_use:
|
||||
if not HardwareScreen.failure_count.get(request_addr):
|
||||
HardwareScreen.failure_count[request_addr] = 0
|
||||
if HardwareScreen.failure_count[request_addr] > 0:
|
||||
time.sleep(10)
|
||||
HardwareScreen.failure_count[request_addr] += 1
|
||||
return {'rendered_html': """<div class="pos-customer_facing_display"><p>Not Authorized. Another browser is in use to display for the client. Please refresh.</p></div> """,
|
||||
'stop_longpolling': True,
|
||||
'ip_from': request_addr}
|
||||
|
||||
# IMPLEMENTATION OF LONGPOLLING
|
||||
# Times out 2 seconds before the JS request does
|
||||
if HardwareScreen.event_data.wait(28):
|
||||
HardwareScreen.event_data.clear()
|
||||
HardwareScreen.failure_count[request_addr] = 0
|
||||
return result
|
||||
return {'rendered_html': False,
|
||||
'ip_from': HardwareScreen.pos_client_data['ip_from']}
|
||||
|
||||
def _get_html(self):
|
||||
cust_js = None
|
||||
interfaces = ni.interfaces()
|
||||
my_ip = '127.0.0.1'
|
||||
HardwareScreen.display_in_use = http.request.httprequest.remote_addr
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "../static/src/js/worker.js")) as js:
|
||||
cust_js = js.read()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "../static/src/css/cust_css.css")) as css:
|
||||
cust_css = css.read()
|
||||
|
||||
display_ifaces = ""
|
||||
for iface_id in interfaces:
|
||||
iface_obj = ni.ifaddresses(iface_id)
|
||||
ifconfigs = iface_obj.get(ni.AF_INET, [])
|
||||
for conf in ifconfigs:
|
||||
if conf.get('addr'):
|
||||
display_ifaces += "<tr><td>" + iface_id + "</td>"
|
||||
display_ifaces += "<td>" + conf.get('addr') + "</td>"
|
||||
display_ifaces += "<td>" + conf.get('netmask') + "</td></tr>"
|
||||
# What is my external IP ?
|
||||
if iface_id != 'lo':
|
||||
my_ip = conf.get('addr')
|
||||
|
||||
my_ip_port = my_ip + ":" + self_port
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title class="origin">Odoo -- Point of Sale</title>
|
||||
<script type="text/javascript" class="origin" src="http://""" + my_ip_port + """/web/static/lib/jquery/jquery.js" >
|
||||
</script>
|
||||
<script type="text/javascript" class="origin">
|
||||
""" + cust_js + """
|
||||
</script>
|
||||
<link rel="stylesheet" class="origin" href="http://""" + my_ip_port + """/web/static/lib/bootstrap/css/bootstrap.css" >
|
||||
</link>
|
||||
<script class="origin" src="http://""" + my_ip_port + """/web/static/lib/bootstrap/js/bootstrap.min.js"></script>
|
||||
<style class="origin">
|
||||
""" + cust_css + """
|
||||
</style>
|
||||
</head>
|
||||
<body class="original_body">
|
||||
<div hidden class="shadow"></div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<h1>Odoo Point of Sale</h1>
|
||||
<h2>POSBox Client display</h2>
|
||||
<h3>My IPs</h3>
|
||||
<table id="table_ip" class="table table-condensed">
|
||||
<tr>
|
||||
<th>Interface</th>
|
||||
<th>IP</th>
|
||||
<th>Netmask</th>
|
||||
</tr>
|
||||
""" + display_ifaces + """
|
||||
</table>
|
||||
<p>The customer cart will be displayed here once a Point of Sale session is started.</p>
|
||||
<p>Odoo version 11 or above is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return html
|
||||
@@ -0,0 +1,21 @@
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.original_body {
|
||||
background-color: #797083;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ajax_got_body {
|
||||
color: black;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
$(function() {
|
||||
"use strict";
|
||||
// mergedHead will be turned to true the first time we receive something from a new host
|
||||
// It allows to transform the <head> only once
|
||||
var mergedHead = false;
|
||||
var current_client_url = "";
|
||||
var stop_longpolling = false;
|
||||
|
||||
function longpolling() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'http://'+window.location.host+'/point_of_sale/get_serialized_order',
|
||||
dataType: 'json',
|
||||
beforeSend: function(xhr){xhr.setRequestHeader('Content-Type', 'application/json');},
|
||||
data: JSON.stringify({jsonrpc: '2.0'}),
|
||||
|
||||
success: function(data) {
|
||||
if (typeof data.result.stop_longpolling !== 'undefined') {
|
||||
stop_longpolling = data.result.stop_longpolling;
|
||||
}
|
||||
if (data.result.ip_from && data.result.rendered_html) {
|
||||
var trimmed = $.trim(data.result.rendered_html);
|
||||
var $parsedHTML = $('<div>').html($.parseHTML(trimmed,true)); // WARNING: the true here will executes any script present in the string to parse
|
||||
var new_client_url = $parsedHTML.find(".resources > base").attr('href');
|
||||
|
||||
if (!mergedHead || (current_client_url !== new_client_url)) {
|
||||
|
||||
mergedHead = true;
|
||||
current_client_url = new_client_url;
|
||||
$("body").removeClass('original_body').addClass('ajax_got_body');
|
||||
$("head").children().not('.origin').remove();
|
||||
$("head").append($parsedHTML.find(".resources").html());
|
||||
}
|
||||
|
||||
$(".container").html($parsedHTML.find('.pos-customer_facing_display').html());
|
||||
$(".container").attr('class', 'container').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
|
||||
|
||||
var d = $('.pos_orderlines_list');
|
||||
d.scrollTop(d.prop("scrollHeight"));
|
||||
|
||||
// Here we execute the code coming from the pos, apparently $.parseHTML() executes scripts right away,
|
||||
// Since we modify the dom afterwards, the script might not have any effect
|
||||
if (typeof foreign_js !== 'undefined' && $.isFunction(foreign_js)) {
|
||||
foreign_js();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
complete: function(jqXHR,err) {
|
||||
if (!stop_longpolling) {
|
||||
longpolling();
|
||||
}
|
||||
},
|
||||
|
||||
timeout: 30000,
|
||||
});
|
||||
};
|
||||
|
||||
longpolling();
|
||||
});
|
||||
@@ -11,7 +11,7 @@ create_ramdisk () {
|
||||
echo "Creating ramdisk for ${1} of size ${SIZE}..."
|
||||
|
||||
mount -t tmpfs -o size="${SIZE}" tmpfs "${RAMDISK}"
|
||||
rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" "${ORIGINAL}/" "${RAMDISK}/"
|
||||
rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" --exclude=".mozilla" "${ORIGINAL}/" "${RAMDISK}/"
|
||||
mount --bind "${RAMDISK}" "${ORIGINAL}"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
[LightDM]
|
||||
user-authority-in-system-dir=true
|
||||
|
||||
[SeatDefaults]
|
||||
xserver-command=/usr/bin/X -s 0 dpms -nolisten tcp
|
||||
greeter-hide-users=false
|
||||
autologin-user=pi
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
xset s off
|
||||
xset -dpms
|
||||
|
||||
export HOME=/tmp
|
||||
/usr/bin/firefox http://localhost:8069/point_of_sale/display &
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
Manifest-Version: 1.0
|
||||
|
||||
Name: install.rdf
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: xnFKNbJgc33dy/FJ+4cmHA==
|
||||
SHA1-Digest: 4YLgHgdgCw4lGCfsX7+r4zVmKJ4=
|
||||
|
||||
Name: chrome.manifest
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: O5ce4bajAqUxG6iVul0K2w==
|
||||
SHA1-Digest: wb6VleWnV5GgLypmg1ij9TfmPLE=
|
||||
|
||||
Name: content/rkioskbrowser.js
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: /cF15tfVnbkfbxRVTL5N+g==
|
||||
SHA1-Digest: lvbY92PbsjwY+0TguEYoXWw0lxQ=
|
||||
|
||||
Name: content/rkioskbrowser.xul
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: NFOIpAmZtn20Y+XfTo4cpw==
|
||||
SHA1-Digest: VVIo7GdG+4mvAOtgR0DJUsSc/yg=
|
||||
|
||||
Name: content/rkioskunknownContentType.xul
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: 6cNGoHJS9Xvm+wmvo4IesQ==
|
||||
SHA1-Digest: T8i1MkMKjmn5HICqWv+iXSPRKIU=
|
||||
|
||||
Name: content/rkioskxpinstallConfirm.xul
|
||||
Digest-Algorithms: MD5 SHA1
|
||||
MD5-Digest: SxN1YnRJiuood2WhU85v9g==
|
||||
SHA1-Digest: Msj21u4bTAKUi496xld5YCGdjiA=
|
||||
|
||||
BIN
Binary file not shown.
+4
@@ -0,0 +1,4 @@
|
||||
Signature-Version: 1.0
|
||||
MD5-Digest-Manifest: 7hFyhNW67+H+rf9OhWGDkA==
|
||||
SHA1-Digest-Manifest: HvfAQQLXphSZ9m8KBorJMX8MUnE=
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
The R-kiosk module has been downloaded from https://addons.mozilla.org/en-US/firefox/addon/r-kiosk/
|
||||
It is in the public domain as stated on https://addons.mozilla.org/en-US/firefox/addon/r-kiosk/eula/
|
||||
This mozilla module is shipped by Odoo to provide out of the box Client facing display to Odoo's Posbox
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
content RKiosk content/
|
||||
overlay chrome://browser/content/browser.xul chrome://rkiosk/content/rkioskbrowser.xul
|
||||
overlay chrome://mozapps/content/xpinstall/xpinstallConfirm.xul chrome://rkiosk/content/rkioskxpinstallConfirm.xul
|
||||
overlay chrome://mozapps/content/downloads/unknownContentType.xul chrome://rkiosk/content/rkioskunknownContentType.xul
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
function Rkiosk_donothing()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
function rkioskclose()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
function Rkiosk_navbar_setting()
|
||||
{
|
||||
var rkiosk_navbar_enable="true";
|
||||
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
|
||||
getService(Components.interfaces.nsIPrefBranch);
|
||||
if (prefs.getPrefType("rkiosk.navbar") == prefs.PREF_BOOL){
|
||||
if (prefs.getBoolPref("rkiosk.navbar")) rkiosk_navbar_enable = "false";
|
||||
}
|
||||
var rkiosk_element = document.getElementById("navigator-toolbox");
|
||||
rkiosk_element.setAttribute("hidden", rkiosk_navbar_enable);
|
||||
}
|
||||
|
||||
function RkioskBrowserStartup()
|
||||
{
|
||||
Rkiosk_navbar_setting();
|
||||
BrowserStartup();
|
||||
setTimeout(RkioskdelayedStartup, 1000);
|
||||
}
|
||||
|
||||
function RkioskdelayedStartup()
|
||||
{
|
||||
window.fullScreen = true;
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<window id="main-window"
|
||||
onload="RkioskBrowserStartup()">
|
||||
</window>
|
||||
|
||||
<script type="application/x-javascript" src="rkioskbrowser.js"/>
|
||||
|
||||
<menubar id="main-menubar" hidden="true" fullscreentoolbar="false" disabled="true">
|
||||
<menuitem id="menu_preferences" disabled="true"/>
|
||||
</menubar>
|
||||
|
||||
<popup id="contentAreaContextMenu"
|
||||
onpopupshowing="if (event.target != this) return true; return false;"
|
||||
onpopuphiding="if (event.target == this) gContextMenu = null;">
|
||||
</popup>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
<command id="cmd_newNavigator" disabled="true"/>
|
||||
<command id="cmd_handleBackspace" disabled="true"/>
|
||||
<command id="cmd_handleShiftBackspace" disabled="true"/>
|
||||
<command id="cmd_newNavigatorTab" disabled="true"/>
|
||||
<command id="Browser:OpenFile" disabled="true"/>
|
||||
<command id="Browser:SavePage" disabled="true"/>
|
||||
<command id="Browser:SaveFrame" disabled="true"/>
|
||||
<command id="Browser:SendLink" disabled="true"/>
|
||||
<command id="cmd_pageSetup" disabled="true"/>
|
||||
<command id="cmd_print" oncommand="PrintUtils.print();"/>
|
||||
<command id="cmd_printPreview" disabled="true"/>
|
||||
<command id="cmd_close" disabled="true"/>
|
||||
<command id="cmd_closeWindow" disabled="true"/>
|
||||
<command id="cmd_ToggleTabsOnTop" disabled="true"/>
|
||||
<command id="cmd_quitApplication" disabled="true"/>
|
||||
<command id="cmd_toggleTaskbar" disabled="true"/>
|
||||
<command id="cmd_CustomizeToolbars" disabled="true"/>
|
||||
<commandset id="editMenuCommands"/>
|
||||
<command id="View:PageSource" disabled="true"/>
|
||||
<command id="View:PageInfo" disabled="true"/>
|
||||
<command id="View:FullScreen" disabled="true"/>
|
||||
<command id="cmd_find" disabled="true"/>
|
||||
<command id="cmd_findAgain" disabled="true"/>
|
||||
<command id="cmd_findPrevious" disabled="true"/>
|
||||
<command id="Browser:AddBookmarkAs" disabled="true"/>
|
||||
<command id="Browser:BookmarkAllTabs" disabled="true"/>
|
||||
<command id="Browser:Home" oncommand="BrowserHome();"/>
|
||||
<command id="Browser:Back" disabled="true"/>
|
||||
<command id="Browser:Forward" disabled="true"/>
|
||||
<command id="Browser:Stop" disabled="true"/>
|
||||
<command id="Browser:Reload" disabled="true"/>
|
||||
<command id="Browser:ReloadSkipCache" disabled="true"/>
|
||||
<command id="Browser:BackOrBackDuplicate" disabled="true"/>
|
||||
<command id="Browser:Forward" disabled="true"/>
|
||||
<command id="Browser:ForwardOrForwardDuplicate" disabled="true"/>
|
||||
<command id="Browser:ReloadOrDuplicate" disabled="true"/>
|
||||
<command id="Browser:NextTab" disabled="true"/>
|
||||
<command id="Browser:PrevTab" disabled="true"/>
|
||||
<command id="Browser:ShowAllTabs" disabled="true"/>
|
||||
<command id="Browser:ToggleTabView" disabled="true"/>
|
||||
<command id="Browser:ToggleAddonBar" disabled="true"/>
|
||||
<command id="cmd_fullZoomReduce" disabled="true"/>
|
||||
<command id="cmd_fullZoomEnlarge" disabled="true"/>
|
||||
<command id="cmd_fullZoomReset" disabled="true"/>
|
||||
<command id="cmd_fullZoomToggle" disabled="true"/>
|
||||
<command id="Browser:OpenLocation" disabled="true"/>
|
||||
<command id="Tools:Search" disabled="true"/>
|
||||
<command id="Tools:Downloads" disabled="true"/>
|
||||
<command id="Tools:Addons" disabled="true"/>
|
||||
<command id="Tools:Sanitize" disabled="true"/>
|
||||
<command id="Tools:Inspect" disabled="true"/>
|
||||
<command id="Tools:PrivateBrowsing" disabled="true"/>
|
||||
<command id="History:UndoCloseTab" disabled="true"/>
|
||||
<command id="History:UndoCloseWindow" disabled="true"/>
|
||||
<command id="cmd_bm_open" disabled="true"/>
|
||||
<command id="cmd_bm_openinnewwindow" disabled="true"/>
|
||||
<command id="cmd_bm_openinnewtab" disabled="true"/>
|
||||
<command id="cmd_bm_openfolder" disabled="true"/>
|
||||
<command id="cmd_bm_managefolder" disabled="true"/>
|
||||
<command id="cmd_bm_newfolder" disabled="true"/>
|
||||
<command id="cmd_bm_newbookmark" disabled="true"/>
|
||||
<command id="cmd_bm_newseparator" disabled="true"/>
|
||||
<command id="cmd_bm_properties" disabled="true"/>
|
||||
<command id="cmd_bm_refreshlivemark" disabled="true"/>
|
||||
<command id="cmd_bm_refreshmicrosummary" disabled="true"/>
|
||||
<command id="cmd_bm_rename" disabled="true"/>
|
||||
<command id="cmd_bm_moveBookmark" disabled="true"/>
|
||||
<command id="cmd_bm_sortbyname" disabled="true"/>
|
||||
<command id="cmd_copyLink" disabled="true"/>
|
||||
<command id="cmd_copyImageLocation" disabled="true"/>
|
||||
<command id="cmd_copyImageContents" disabled="true"/>
|
||||
<command id="cmd_undo" disabled="true"/>
|
||||
<command id="cmd_redo" disabled="true"/>
|
||||
<command id="cmd_cut" disabled="true"/>
|
||||
<command id="cmd_copy" disabled="true"/>
|
||||
<command id="cmd_paste" disabled="true"/>
|
||||
<command id="cmd_delete" disabled="true"/>
|
||||
<command id="cmd_selectAll" disabled="true"/>
|
||||
<command id="cmd_switchTextDirection" disabled="true"/>
|
||||
<command id="cmd_textZoomReduce" disabled="true"/>
|
||||
<command id="cmd_textZoomEnlarge" disabled="true"/>
|
||||
<command id="cmd_textZoomReset" disabled="true"/>
|
||||
</commandset>
|
||||
<commandset id="placesCommands">
|
||||
<command id="Browser:ShowAllBookmarks" disabled="true"/>
|
||||
<command id="Browser:ShowAllHistory" disabled="true"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="rkiosk_f1" keycode="VK_F1" oncommand="Rkiosk_donothing();"/>
|
||||
<key id="key_newNavigator" disabled="true"/>
|
||||
<key id="key_newNavigatorTab" disabled="true"/>
|
||||
<key id="focusURLBar" disabled="true"/>
|
||||
<key id="focusURLBar2" disabled="true"/>
|
||||
<key id="key_search" disabled="true"/>
|
||||
<key id="key_search2" disabled="true"/>
|
||||
<key id="key_openDownloads" disabled="true"/>
|
||||
<key id="openFileKb" disabled="true"/>
|
||||
<key id="key_savePage" disabled="true"/>
|
||||
<key id="printKb" disabled="true"/>
|
||||
<key id="key_close" disabled="true"/>
|
||||
<key id="key_closeWindow" disabled="true"/>
|
||||
<key id="key_undo" disabled="true"/>
|
||||
<key id="key_redo" disabled="true"/>
|
||||
<key id="key_cut" disabled="true"/>
|
||||
<key id="key_copy" disabled="true"/>
|
||||
<key id="key_paste" disabled="true"/>
|
||||
<key id="key_delete" disabled="true"/>
|
||||
<key id="key_selectAll" disabled="true"/>
|
||||
<key id="goBackKb" disabled="true"/>
|
||||
<key id="goForwardKb" disabled="true"/>
|
||||
<key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
|
||||
<key id="key_viewSource" disabled="true"/>
|
||||
<key id="key_find" disabled="true"/>
|
||||
<key id="key_findAgain" disabled="true"/>
|
||||
<key id="key_findPrevious" disabled="true"/>
|
||||
<key id="addBookmarkAsKb" disabled="true"/>
|
||||
<key id="bookmarkAllTabsKb" disabled="true"/>
|
||||
<key id="key_stop" disabled="true"/>
|
||||
<key id="key_gotoHistory" command="Rkiosk_donothing();"/>
|
||||
<key id="key_switchTextDirection" disabled="true"/>
|
||||
<key id="key_sanitize" disabled="true"/>
|
||||
<key id="key_undoCloseTab" disabled="true"/>
|
||||
<key id="viewBookmarksSidebarKb" command="Rkiosk_donothing();"/>
|
||||
<key id="viewBookmarksSidebarWinKb" command="Rkiosk_donothing();"/>
|
||||
<key id="key_fullScreen" disabled="true"/>
|
||||
<key id="key_textZoomReduce" disabled="true"/>
|
||||
<key id="key_textZoomEnlarge" disabled="true"/>
|
||||
<key id="key_textZoomReset" disabled="true"/>
|
||||
<key id="showAllHistoryKb" disabled="true"/>
|
||||
<key id="key_errorConsole" disabled="true"/>
|
||||
<key id="manBookmarkKb" disabled="true"/>
|
||||
<key id="key_fullZoomReduce" disabled="true"/>
|
||||
<key id="key_fullZoomEnlarge" disabled="true"/>
|
||||
<key id="key_fullZoomReset" disabled="true"/>
|
||||
<key id="key_openAddons" disabled="true"/>
|
||||
<key id="key_webConsole" disabled="true"/>
|
||||
<key id="key_inspect" disabled="true"/>
|
||||
<key id="key_scratchpad" disabled="true"/>
|
||||
<key id="key_showAllTabs" disabled="true"/>
|
||||
<key id="key_tabview" disabled="true"/>
|
||||
<key id="key_privatebrowsing" disabled="true"/>
|
||||
<key id="key_undoCloseWindow" disabled="true"/>
|
||||
<key id="key_selectTab1" disabled="true"/>
|
||||
<key id="key_selectTab2" disabled="true"/>
|
||||
<key id="key_selectTab3" disabled="true"/>
|
||||
<key id="key_selectTab4" disabled="true"/>
|
||||
<key id="key_selectTab5" disabled="true"/>
|
||||
<key id="key_selectTab6" disabled="true"/>
|
||||
<key id="key_selectTab7" disabled="true"/>
|
||||
<key id="key_selectTab8" disabled="true"/>
|
||||
<key id="key_selectLastTab" disabled="true"/>
|
||||
<key id="key_toggleAddonBar" disabled="true"/>
|
||||
</keyset>
|
||||
</overlay>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<overlay>
|
||||
<dialog id="unknownContentType"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="rkioskclose();">
|
||||
<script src="rkioskbrowser.js"/>
|
||||
</dialog>
|
||||
</overlay>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<overlay>
|
||||
<dialog id="xpinstallConfirm"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="rkioskclose();">
|
||||
<script src="rkioskbrowser.js"/>
|
||||
</dialog>
|
||||
</overlay>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
|
||||
<em:id>{4D498D0A-05AD-4fdb-97B5-8A0AABC1FC5B}</em:id>
|
||||
<em:name>R-kiosk</em:name>
|
||||
<em:version>0.9.0.1-signed.1-signed</em:version>
|
||||
<em:description>RKiosk (Real Kiosk), fullscreen kiosk mode: all menus, keys etc. disabled</em:description>
|
||||
<em:creator>Kimmo Heinaaro</em:creator>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>2.0</em:minVersion>
|
||||
<em:maxVersion>6.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
</Description>
|
||||
</RDF>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Preferences to allow unattended install of R-Kiosk extension
|
||||
// Needed for Odoo posbox Client display
|
||||
pref("app.update.checkInstallTime", false);
|
||||
pref("devtools.webide.widget.autoinstall", false);
|
||||
pref("xpinstall.customConfirmationUI", false);
|
||||
pref("xpinstall.signatures.required", false);
|
||||
pref("browser.shell.checkDefaultBrowser",false)
|
||||
@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
|
||||
set -e
|
||||
|
||||
function _start() {
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be,hw_screen
|
||||
}
|
||||
|
||||
function _stop() {
|
||||
|
||||
@@ -8,17 +8,25 @@ __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
|
||||
__base="$(basename ${__file} .sh)"
|
||||
|
||||
# Since we are emulating, the real /boot is not mounted,
|
||||
# leading to mismatch between kernel image and modules.
|
||||
mount /dev/sda1 /boot
|
||||
|
||||
# Recommends: antiword, graphviz, ghostscript, postgresql, python-gevent, poppler-utils
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
|
||||
|
||||
mount /dev/sda1 /boot
|
||||
|
||||
apt-get update
|
||||
apt-get -y dist-upgrade
|
||||
# Do not be too fast to upgrade to more recent firmware and kernel than 4.38
|
||||
# Firmware 4.44 seems to prevent the LED mechanism from working
|
||||
|
||||
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-tz python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data"
|
||||
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-tz python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data lightdm xserver-xorg-video-fbdev xserver-xorg-input-evdev iceweasel xdotool unclutter x11-utils openbox python-netifaces rpi-update"
|
||||
|
||||
apt-get -y install ${PKGS_TO_INSTALL}
|
||||
# KEEP OWN CONFIG FILES DURING PACKAGE CONFIGURATION
|
||||
# http://serverfault.com/questions/259226/automatically-keep-current-version-of-config-files-when-apt-get-install
|
||||
apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes install ${PKGS_TO_INSTALL}
|
||||
|
||||
apt-get clean
|
||||
localepurge
|
||||
@@ -32,13 +40,20 @@ pip install pyusb==1.0.0b1
|
||||
pip install qrcode
|
||||
pip install evdev
|
||||
|
||||
# --upgrade because websocket_client in wheezy is bad:
|
||||
# https://github.com/docker/compose/issues/1288
|
||||
pip install --upgrade websocket_client
|
||||
|
||||
groupadd usbusers
|
||||
usermod -a -G usbusers pi
|
||||
usermod -a -G lp pi
|
||||
usermod -a -G input lightdm
|
||||
|
||||
sudo -u postgres createuser -s pi
|
||||
mkdir /var/log/odoo
|
||||
chown pi:pi /var/log/odoo
|
||||
chown pi:pi -R /home/pi/odoo/
|
||||
chmod 770 -R /home/pi/odoo/
|
||||
|
||||
# logrotate is very picky when it comes to file permissions
|
||||
chown -R root:root /etc/logrotate.d/
|
||||
@@ -54,6 +69,31 @@ update-rc.d -f isc-dhcp-server remove
|
||||
systemctl daemon-reload
|
||||
systemctl enable ramdisks.service
|
||||
systemctl disable dphys-swapfile.service
|
||||
systemctl enable ssh
|
||||
|
||||
# USER PI AUTO LOGIN (from nano raspi-config)
|
||||
# We take the whole algorithm from raspi-config in order to stay compatible with raspbian infrastructure
|
||||
if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
|
||||
SYSTEMD=1
|
||||
elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
|
||||
SYSTEMD=0
|
||||
else
|
||||
echo "Unrecognised init system"
|
||||
return 1
|
||||
fi
|
||||
if [ $SYSTEMD -eq 1 ]; then
|
||||
systemctl set-default graphical.target
|
||||
ln -fs /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty1.service
|
||||
else
|
||||
update-rc.d lightdm enable 2
|
||||
fi
|
||||
|
||||
# disable overscan in /boot/config.txt, we can't use
|
||||
# overwrite_after_init because it's on a different device
|
||||
# (/dev/mmcblk0p1) and we don't mount that afterwards.
|
||||
# This option disables any black strips around the screen
|
||||
# cf: https://www.raspberrypi.org/documentation/configuration/raspi-config.md
|
||||
echo "disable_overscan=1" >> /boot/config.txt
|
||||
|
||||
# https://www.raspberrypi.org/forums/viewtopic.php?p=79249
|
||||
# to not have "setting up console font and keymap" during boot take ages
|
||||
@@ -68,5 +108,6 @@ create_ramdisk_dir "/var"
|
||||
create_ramdisk_dir "/etc"
|
||||
create_ramdisk_dir "/tmp"
|
||||
mkdir /root_bypass_ramdisks
|
||||
umount /dev/sda1
|
||||
|
||||
reboot
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
SUBSYSTEM=="input", GROUP="input", MODE="0660"
|
||||
KERNEL=="tty[0-9]*", GROUP="tty", MODE="0660"
|
||||
@@ -36,20 +36,25 @@ fi
|
||||
cp -a *raspbian*.img posbox.img
|
||||
|
||||
CLONE_DIR="${OVERWRITE_FILES_BEFORE_INIT_DIR}/home/pi/odoo"
|
||||
|
||||
rm -rf "${CLONE_DIR}"
|
||||
mkdir "${CLONE_DIR}"
|
||||
git clone -b 8.0 --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
|
||||
cd "${CLONE_DIR}"
|
||||
git config core.sparsecheckout true
|
||||
echo "addons/web
|
||||
|
||||
if [ ! -d $CLONE_DIR ]; then
|
||||
echo "Clone Github repo"
|
||||
mkdir -p "${CLONE_DIR}"
|
||||
git clone -b 8.0 --no-local --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
|
||||
cd "${CLONE_DIR}"
|
||||
git config core.sparsecheckout true
|
||||
echo "addons/web
|
||||
addons/web_kanban
|
||||
addons/hw_*
|
||||
addons/point_of_sale/tools/posbox/configuration
|
||||
openerp/
|
||||
odoo.py" | tee --append .git/info/sparse-checkout > /dev/null
|
||||
git read-tree -mu HEAD
|
||||
cd "${__dir}"
|
||||
git read-tree -mu HEAD
|
||||
fi
|
||||
|
||||
cd "${__dir}"
|
||||
USR_BIN="${OVERWRITE_FILES_BEFORE_INIT_DIR}/usr/bin/"
|
||||
mkdir -p "${USR_BIN}"
|
||||
cd "/tmp"
|
||||
@@ -60,9 +65,11 @@ cd "${__dir}"
|
||||
mv /tmp/ngrok "${USR_BIN}"
|
||||
|
||||
# zero pad the image to be around 3.5 GiB, by default the image is only ~1.3 GiB
|
||||
echo "Enlarging the image..."
|
||||
dd if=/dev/zero bs=1M count=2048 >> posbox.img
|
||||
|
||||
# resize partition table
|
||||
echo "Fdisking"
|
||||
START_OF_ROOT_PARTITION=$(fdisk -l posbox.img | tail -n 1 | awk '{print $2}')
|
||||
(echo 'p'; # print
|
||||
echo 'd'; # delete
|
||||
@@ -75,7 +82,7 @@ START_OF_ROOT_PARTITION=$(fdisk -l posbox.img | tail -n 1 | awk '{print $2}')
|
||||
echo 'p'; # print
|
||||
echo 'w') | fdisk posbox.img # write and quit
|
||||
|
||||
LOOP_MAPPER_PATH=$(kpartx -av posbox.img | tail -n 1 | cut -d ' ' -f 3)
|
||||
LOOP_MAPPER_PATH=$(kpartx -avs posbox.img | tail -n 1 | cut -d ' ' -f 3)
|
||||
LOOP_MAPPER_PATH="/dev/mapper/${LOOP_MAPPER_PATH}"
|
||||
sleep 5
|
||||
|
||||
|
||||
@@ -850,31 +850,61 @@ function eval_contexts (contexts, evaluation_context) {
|
||||
function eval_domains (domains, evaluation_context) {
|
||||
evaluation_context = _.extend(pycontext(), evaluation_context || {});
|
||||
var result_domain = [];
|
||||
// Normalize only if the first domain is the array ["|"] or ["!"]
|
||||
var need_normalization = (
|
||||
domains.length > 0 &&
|
||||
domains[0].length === 1 &&
|
||||
(domains[0][0] === "|" || domains[0][0] === "!")
|
||||
);
|
||||
_(domains).each(function (domain) {
|
||||
if (_.isString(domain)) {
|
||||
// wrap raw strings in domain
|
||||
domain = { __ref: 'domain', __debug: domain };
|
||||
}
|
||||
var domain_array_to_combine;
|
||||
switch(domain.__ref) {
|
||||
case 'domain':
|
||||
evaluation_context.context = evaluation_context;
|
||||
result_domain.push.apply(
|
||||
result_domain, py.eval(domain.__debug, wrap_context(evaluation_context)));
|
||||
domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context));
|
||||
break;
|
||||
case 'compound_domain':
|
||||
var eval_context = eval_contexts([domain.__eval_context]);
|
||||
result_domain.push.apply(
|
||||
result_domain, eval_domains(
|
||||
domain.__domains, _.extend(
|
||||
{}, evaluation_context, eval_context)));
|
||||
domain_array_to_combine = eval_domains(
|
||||
domain.__domains, _.extend({}, evaluation_context, eval_context)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
result_domain.push.apply(result_domain, domain);
|
||||
domain_array_to_combine = domain;
|
||||
}
|
||||
if (need_normalization) {
|
||||
domain_array_to_combine = get_normalized_domain(domain_array_to_combine);
|
||||
}
|
||||
result_domain.push.apply(result_domain, domain_array_to_combine);
|
||||
});
|
||||
return result_domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized copy of the given domain array. Normalization is
|
||||
* is making the implicit "&" at the start of the domain explicit, e.g.
|
||||
* [A, B, C] would become ["&", "&", A, B, C].
|
||||
*
|
||||
* @param {Array} domain_array
|
||||
* @returns {Array} normalized copy of the given array
|
||||
*/
|
||||
function get_normalized_domain(domain_array) {
|
||||
var expected = 1; // Holds the number of expected domain expressions
|
||||
_.each(domain_array, function (item) {
|
||||
if (item === "&" || item === "|") {
|
||||
expected++;
|
||||
} else if (item !== "!") {
|
||||
expected--;
|
||||
}
|
||||
});
|
||||
var new_explicit_ands = _.times(-expected, _.constant("&"));
|
||||
return new_explicit_ands.concat(domain_array);
|
||||
}
|
||||
|
||||
function eval_groupbys (contexts, evaluation_context) {
|
||||
evaluation_context = _.extend(pycontext(), evaluation_context || {});
|
||||
var result_group = [];
|
||||
|
||||
@@ -536,7 +536,7 @@ odoo.define_section('eval.edc.nonliterals', ['web.pyeval', 'web.session'], funct
|
||||
var result = pyeval.sync_eval_domains_and_contexts({
|
||||
domains: [
|
||||
[['type', '=', 'contract']],
|
||||
{ "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["state", "=", "pending"]]],
|
||||
{ "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["type", "=", "contract"], ["state", "=", "pending"]]],
|
||||
"__eval_context": null,
|
||||
"__ref": "compound_domain"
|
||||
},
|
||||
@@ -552,7 +552,8 @@ odoo.define_section('eval.edc.nonliterals', ['web.pyeval', 'web.session'], funct
|
||||
assert.deepEqual(result.domain, [
|
||||
["type", "=", "contract"],
|
||||
"|", ["state", "in", ["open", "draft"]],
|
||||
["state", "=", "pending"],
|
||||
"&", ["type", "=", "contract"],
|
||||
["state", "=", "pending"],
|
||||
"|",
|
||||
"&", ["date", "!=", false],
|
||||
["date", "<=", today],
|
||||
|
||||
@@ -334,7 +334,7 @@ var CalendarView = View.extend({
|
||||
self.proxy('update_record')(event._id, data);
|
||||
},
|
||||
eventRender: function (event, element, view) {
|
||||
element.find('.fc-event-title').html(event.title);
|
||||
element.find('.fc-event-title').html(event.title + event.attendee_avatars);
|
||||
},
|
||||
eventAfterRender: function (event, element, view) {
|
||||
if ((view.name !== 'month') && (((event.end-event.start)/60000)<=30)) {
|
||||
@@ -630,7 +630,6 @@ var CalendarView = View.extend({
|
||||
if (attendee_other.length>2) {
|
||||
the_title_avatar += '<span class="o_attendee_head" title="' + attendee_other.slice(0, -2) + '">+</span>';
|
||||
}
|
||||
the_title = the_title_avatar + the_title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,6 +641,7 @@ var CalendarView = View.extend({
|
||||
'start': moment(date_start).toString(),
|
||||
'end': moment(date_stop).toString(),
|
||||
'title': the_title,
|
||||
'attendee_avatars': the_title_avatar,
|
||||
'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
|
||||
'id': evt.id,
|
||||
'attendees':attendees
|
||||
|
||||
+10
-3
@@ -1555,9 +1555,16 @@ class Root(object):
|
||||
httprequest.session.db = db_monodb(httprequest)
|
||||
|
||||
def setup_lang(self, httprequest):
|
||||
if not "lang" in httprequest.session.context:
|
||||
lang = httprequest.accept_languages.best or "en_US"
|
||||
lang = babel.core.LOCALE_ALIASES.get(lang, lang).replace('-', '_')
|
||||
if "lang" not in httprequest.session.context:
|
||||
alang = httprequest.accept_languages.best or "en-US"
|
||||
try:
|
||||
code, territory, _, _ = babel.core.parse_locale(alang, sep='-')
|
||||
if territory:
|
||||
lang = '%s_%s' % (code, territory)
|
||||
else:
|
||||
lang = babel.core.LOCALE_ALIASES[code]
|
||||
except (ValueError, KeyError):
|
||||
lang = 'en_US'
|
||||
httprequest.session.context["lang"] = lang
|
||||
|
||||
def get_request(self, httprequest):
|
||||
|
||||
Reference in New Issue
Block a user