diff --git a/app_common/__init__.py b/app_common/__init__.py new file mode 100644 index 00000000..7f49029b --- /dev/null +++ b/app_common/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from .hooks import pre_init_hook +from .hooks import post_init_hook +from .hooks import uninstall_hook diff --git a/app_common/__manifest__.py b/app_common/__manifest__.py new file mode 100644 index 00000000..8424722b --- /dev/null +++ b/app_common/__manifest__.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +# Created on 2023-02-02 +# author: 欧度智能,https://www.odooai.cn +# email: 300883@qq.com +# resource of odooai +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +# Odoo16在线用户手册(长期更新) +# https://www.odooai.cn/documentation/16.0/zh_CN/index.html + +# Odoo16在线开发者手册(长期更新) +# https://www.odooai.cn/documentation/16.0/zh_CN/developer.html + +# Odoo13在线用户手册(长期更新) +# https://www.odooai.cn/documentation/user/13.0/zh_CN/index.html + +# Odoo13在线开发者手册(长期更新) +# https://www.odooai.cn/documentation/13.0/index.html + +# Odoo10在线中文用户手册(长期更新) +# https://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# https://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# https://www.odooai.cn/odoo10_developer_document_offline/ + +############################################################################## +# Copyright (C) 2009-TODAY odooai.cn Ltd. https://www.odooai.cn +# Author: Ivan Deng,300883@qq.com +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# See . +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +############################################################################## + +{ + 'name': "odooAi Common Util and Tools", + 'version': '24.08.19', + 'author': 'odooai.cn', + 'category': 'Base', + 'website': 'https://www.odooai.cn', + 'live_test_url': 'https://demo.odooapp.cn', + 'license': 'LGPL-3', + 'sequence': 2, + 'price': 0.00, + 'currency': 'EUR', + 'images': ['static/description/banner.png'], + 'summary': ''' + Core for common use for odooai.cn apps. + 基础核心,必须没有要被依赖字段及视图等,实现auto_install + ''', + 'description': ''' + need to setup odoo.conf, add follow: + server_wide_modules = web,app_common + 1. Quick import data from excel with .py code + 2. Quick m2o default value + 3. Filter for useless field + 4. UTC local timezone convert + 5. Get browser ua, user-agent + 6. Image to local, image url to local, media to local attachment + 7. Log cron job + 8. Boost for less no use mail + 9. Customize .rng file + 10. Misc like get distance between two points + 11. Multi-language Support. Multi-Company Support. + 12. Support Odoo 17,16,15,14,13,12, Enterprise and Community and odoo.sh Edition. + 13. Full Open Source. + ========== + 1. + 2. + 3. 多语言支持 + 4. 多公司支持 + 5. Odoo 16, 企业版,社区版,多版本支持 + ''', + 'depends': [ + 'mail', + 'web', + ], + 'data': [ + 'views/ir_cron_views.xml', + # 'report/.xml', + ], + 'qweb': [ + 'static/src/xml/*.xml', + ], + 'demo': [], + # 'pre_init_hook': 'pre_init_hook', + # 'post_init_hook': 'post_init_hook', + # 'uninstall_hook': 'uninstall_hook', + # 可以不需要,因为直接放 common中了 + # 'external_dependencies': {'python': ['pyyaml', 'ua-parser', 'user-agents']}, + 'installable': True, + 'application': True, + 'auto_install': True, +} diff --git a/app_common/controllers/__init__.py b/app_common/controllers/__init__.py new file mode 100644 index 00000000..6920e202 --- /dev/null +++ b/app_common/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import main \ No newline at end of file diff --git a/app_common/controllers/main.py b/app_common/controllers/main.py new file mode 100644 index 00000000..2e7e21f4 --- /dev/null +++ b/app_common/controllers/main.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import base64 +from io import BytesIO +import requests +from math import radians, cos, sin, asin, sqrt + +from ..lib.user_agents import parse +from ..models.base import get_ua_type + + +from odoo import api, http, SUPERUSER_ID, _ +from odoo import http, exceptions +from odoo.http import request + +import logging +_logger = logging.getLogger(__name__) + +class AppController(http.Controller): + + def get_image_from_url(self, url): + if not url: + return None + try: + response = requests.get(url) # 将这个图片保存在内存 + except Exception as e: + return None + # 返回这个图片的base64编码 + return base64.b64encode(BytesIO(response.content).read()) + + @http.route(['/my/ua', '/wxa/ua', '/web/ua', '/web/ua/show'], auth='public', methods=['GET']) + def app_ua_show(self): + # https://github.com/selwin/python-user-agents + ua_string = request.httprequest.headers.get('User-Agent') + user_agent = parse(ua_string) + ua_type = get_ua_type() + ustr = "Request UA:
%s
Parse UA:
%s
UA Type:
%s
" % (ua_string, str(user_agent), ua_type) + return request.make_response(ustr, [('Content-Type', 'text/html')]) + + def get_ua_type(self): + return get_ua_type() + +def haversine(lon1, lat1, lon2, lat2): + # 计算地图上两点的距离 + # in:经度1,纬度1,经度2,纬度2 (十进制度数) + # out: 距离(米) + """ + Calculate the great circle distance between two points + on the earth (specified in decimal degrees) + """ + # 将十进制度数转化为弧度 + lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) + + # haversine公式 + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 + c = 2 * asin(sqrt(a)) + r = 6371 # 地球平均半径,单位为公里 + return c * r * 1000 diff --git a/app_common/hooks.py b/app_common/hooks.py new file mode 100644 index 00000000..bd41b59e --- /dev/null +++ b/app_common/hooks.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Created on 2018-10-12 +# author: 欧度智能,https://www.odooai.cn +# email: 300883@qq.com +# resource of odooai +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +# Odoo在线中文用户手册(长期更新) +# https://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# https://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# https://www.odooai.cn/odoo10_developer_document_offline/ +# description: + +from odoo import api, SUPERUSER_ID, _ + + +def pre_init_hook(env): + pass + # cr.execute("") + +def post_init_hook(env): + pass + # cr.execute("") + +def uninstall_hook(env): + pass + # cr.execute("") + diff --git a/app_common/i18n/zh_CN.po b/app_common/i18n/zh_CN.po new file mode 100644 index 00000000..9552e39e --- /dev/null +++ b/app_common/i18n/zh_CN.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-08 14:28+0000\n" +"PO-Revision-Date: 2018-01-08 14:28+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: app_common +#: model:ir.model.fields,field_description:app_common.field_ir_cron__trigger_user_id +msgid "Last Trigger User" +msgstr "手动运行用户" + +#. module: app_common +#: model:ir.model,name:app_common.model_ir_cron +msgid "Scheduled Actions" +msgstr "安排的动作" + +#. module: app_common +#: model:ir.model,name:app_common.model_ir_ui_view +msgid "View" +msgstr "查看" diff --git a/app_common/lib/ua_parser/__init__.py b/app_common/lib/ua_parser/__init__.py new file mode 100644 index 00000000..ca04bbe6 --- /dev/null +++ b/app_common/lib/ua_parser/__init__.py @@ -0,0 +1 @@ +VERSION = (0, 10, 0) diff --git a/app_common/lib/ua_parser/_regexes.py b/app_common/lib/ua_parser/_regexes.py new file mode 100644 index 00000000..2e626e16 --- /dev/null +++ b/app_common/lib/ua_parser/_regexes.py @@ -0,0 +1,7406 @@ +# -*- coding: utf-8 -*- +############################################ +# NOTICE: This file is autogenerated from # +# regexes.yaml. Do not edit by hand, # +# instead, re-run `setup.py build_regexes` # +############################################ + +from __future__ import absolute_import, unicode_literals +from .user_agent_parser import ( + UserAgentParser, DeviceParser, OSParser, +) + +__all__ = ( + 'USER_AGENT_PARSERS', 'DEVICE_PARSERS', 'OS_PARSERS', +) + +USER_AGENT_PARSERS = [ + UserAgentParser( + '^(Luminary)[Stage]+/(\\d+) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '(ESPN)[%20| ]+Radio/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '(Antenna)/(\\d+) CFNetwork', + 'AntennaPod', + None, + None, + ), + UserAgentParser( + '(TopPodcasts)Pro/(\\d+) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '(MusicDownloader)Lite/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '^(.*)-iPad\\/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '^(.*)-iPhone/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '^(.*)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork', + None, + None, + None, + ), + UserAgentParser( + '^(Luminary)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(espn\\.go)', + 'ESPN', + None, + None, + ), + UserAgentParser( + '(espnradio\\.com)', + 'ESPN', + None, + None, + ), + UserAgentParser( + 'ESPN APP$', + 'ESPN', + None, + None, + ), + UserAgentParser( + '(audioboom\\.com)', + 'AudioBoom', + None, + None, + ), + UserAgentParser( + ' (Rivo) RHYTHM', + None, + None, + None, + ), + UserAgentParser( + '(CFNetwork)(?:/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)', + 'CFNetwork', + None, + None, + ), + UserAgentParser( + '(Pingdom\\.com_bot_version_)(\\d+)\\.(\\d+)', + 'PingdomBot', + None, + None, + ), + UserAgentParser( + '(PingdomTMS)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'PingdomBot', + None, + None, + ), + UserAgentParser( + ' (PTST)/(\\d+)(?:\\.(\\d+)|)$', + 'WebPageTest.org bot', + None, + None, + ), + UserAgentParser( + 'X11; (Datanyze); Linux', + None, + None, + None, + ), + UserAgentParser( + '(NewRelicPinger)/(\\d+)\\.(\\d+)', + 'NewRelicPingerBot', + None, + None, + ), + UserAgentParser( + '(Tableau)/(\\d+)\\.(\\d+)', + 'Tableau', + None, + None, + ), + UserAgentParser( + 'AppleWebKit/\\d+\\.\\d+.* Safari.* (CreativeCloud)/(\\d+)\\.(\\d+).(\\d+)', + 'Adobe CreativeCloud', + None, + None, + ), + UserAgentParser( + '(Salesforce)(?:.)\\/(\\d+)\\.(\\d?)', + None, + None, + None, + ), + UserAgentParser( + '(\\(StatusCake\\))', + 'StatusCakeBot', + None, + None, + ), + UserAgentParser( + '(facebookexternalhit)/(\\d+)\\.(\\d+)', + 'FacebookBot', + None, + None, + ), + UserAgentParser( + 'Google.*/\\+/web/snippet', + 'GooglePlusBot', + None, + None, + ), + UserAgentParser( + 'via ggpht\\.com GoogleImageProxy', + 'GmailImageProxy', + None, + None, + ), + UserAgentParser( + 'YahooMailProxy; https://help\\.yahoo\\.com/kb/yahoo-mail-proxy-SLN28749\\.html', + 'YahooMailProxy', + None, + None, + ), + UserAgentParser( + '(Twitterbot)/(\\d+)\\.(\\d+)', + 'Twitterbot', + None, + None, + ), + UserAgentParser( + '/((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \\-](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '\\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(MSIE) (\\d+)\\.(\\d+)([a-z]\\d|[a-z]|);.* MSIECrawler', + 'MSIECrawler', + None, + None, + ), + UserAgentParser( + '(DAVdroid)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Google-HTTP-Java-Client|Apache-HttpClient|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest|axios|unirest-(?:java|python|ruby|nodejs|php|net))(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + None, + None, + None, + ), + UserAgentParser( + '(Pinterest(?:bot|))/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)[;\\s(]+\\+https://www.pinterest.com/bot.html', + 'Pinterestbot', + None, + None, + ), + UserAgentParser( + '(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\\/\\$BotVersion|123metaspider-Bot|1470\\.net crawler|50\\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\\b\\w{0,30}favicon\\w{0,30}\\b|\\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\\(S\\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\\.almaden\\.ibm\\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\\.ze\\.bz|ZooShot|ZyBorg)(?:[ /]v?(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)|)', + None, + None, + None, + ), + UserAgentParser( + '\\b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?|dotnet-(?:\\d{1,2}|core)))|s3fs)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '\\[(FBAN/MessengerForiOS|FB_IAB/MESSENGER);FBAV/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)', + 'Facebook Messenger', + None, + None, + ), + UserAgentParser( + '\\[FB.*;(FBAV)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + 'Facebook', + None, + None, + ), + UserAgentParser( + '\\[FB.*;', + 'Facebook', + None, + None, + ), + UserAgentParser( + '(?:\\/[A-Za-z0-9\\.]+|) {0,5}([A-Za-z0-9 \\-_\\!\\[\\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)', + None, + None, + None, + ), + UserAgentParser( + '((?:[A-Za-z][A-Za-z0-9 -]{0,50}|)[^C][^Uu][Bb]ot)\\b(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + None, + None, + None, + ), + UserAgentParser( + '((?:[A-z0-9]{1,50}|[A-z\\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + None, + None, + None, + ), + UserAgentParser( + '(HbbTV)/(\\d+)\\.(\\d+)\\.(\\d+) \\(', + None, + None, + None, + ), + UserAgentParser( + '(Chimera|SeaMonkey|Camino|Waterfox)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*|)', + None, + None, + None, + ), + UserAgentParser( + '(SailfishBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Sailfish Browser', + None, + None, + ), + UserAgentParser( + '\\[(Pinterest)/[^\\]]+\\]', + None, + None, + None, + ), + UserAgentParser( + '(Pinterest)(?: for Android(?: Tablet|)|)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + 'Mozilla.*Mobile.*(Instagram).(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'Mozilla.*Mobile.*(Flipboard).(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'Mozilla.*Mobile.*(Flipboard-Briefing).(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'Mozilla.*Mobile.*(Onefootball)\\/Android.(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Snapchat)\\/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Twitter for (?:iPhone|iPad)|TwitterAndroid)(?:\\/(\\d+)\\.(\\d+)|)', + 'Twitter', + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+) Basilisk/(\\d+)', + 'Basilisk', + None, + None, + ), + UserAgentParser( + '(PaleMoon)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Pale Moon', + None, + None, + ), + UserAgentParser( + '(Fennec)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)', + 'Firefox Mobile', + None, + None, + ), + UserAgentParser( + '(Fennec)/(\\d+)\\.(\\d+)(pre)', + 'Firefox Mobile', + None, + None, + ), + UserAgentParser( + '(Fennec)/(\\d+)\\.(\\d+)', + 'Firefox Mobile', + None, + None, + ), + UserAgentParser( + '(?:Mobile|Tablet);.*(Firefox)/(\\d+)\\.(\\d+)', + 'Firefox Mobile', + None, + None, + ), + UserAgentParser( + '(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|))', + 'Firefox ($1)', + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)', + 'Firefox Alpha', + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)', + 'Firefox Beta', + None, + None, + ), + UserAgentParser( + '(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)', + 'Firefox Alpha', + None, + None, + ), + UserAgentParser( + '(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)', + 'Firefox Beta', + None, + None, + ), + UserAgentParser( + '(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)', + 'Firefox ($1)', + None, + None, + ), + UserAgentParser( + '(Firefox).*Tablet browser (\\d+)\\.(\\d+)\\.(\\d+)', + 'MicroB', + None, + None, + ), + UserAgentParser( + '(MozillaDeveloperPreview)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)', + None, + None, + None, + ), + UserAgentParser( + '(FxiOS)/(\\d+)\\.(\\d+)(\\.(\\d+)|)(\\.(\\d+)|)', + 'Firefox iOS', + None, + None, + ), + UserAgentParser( + '(Flock)/(\\d+)\\.(\\d+)(b\\d+?)', + None, + None, + None, + ), + UserAgentParser( + '(RockMelt)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Navigator)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Netscape', + None, + None, + ), + UserAgentParser( + '(Navigator)/(\\d+)\\.(\\d+)([ab]\\d+)', + 'Netscape', + None, + None, + ), + UserAgentParser( + '(Netscape6)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)', + 'Netscape', + None, + None, + ), + UserAgentParser( + '(MyIBrow)/(\\d+)\\.(\\d+)', + 'My Internet Browser', + None, + None, + ), + UserAgentParser( + '(UC? ?Browser|UCWEB|U3)[ /]?(\\d+)\\.(\\d+)\\.(\\d+)', + 'UC Browser', + None, + None, + ), + UserAgentParser( + '(Opera Tablet).*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Opera Mini)(?:/att|)/?(\\d+|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Opera)/.+Opera Mobi.+Version/(\\d+)\\.(\\d+)', + 'Opera Mobile', + None, + None, + ), + UserAgentParser( + '(Opera)/(\\d+)\\.(\\d+).+Opera Mobi', + 'Opera Mobile', + None, + None, + ), + UserAgentParser( + 'Opera Mobi.+(Opera)(?:/|\\s+)(\\d+)\\.(\\d+)', + 'Opera Mobile', + None, + None, + ), + UserAgentParser( + 'Opera Mobi', + 'Opera Mobile', + None, + None, + ), + UserAgentParser( + '(Opera)/9.80.*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(?:Mobile Safari).*(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Opera Mobile', + None, + None, + ), + UserAgentParser( + '(?:Chrome).*(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Opera', + None, + None, + ), + UserAgentParser( + '(Coast)/(\\d+).(\\d+).(\\d+)', + 'Opera Coast', + None, + None, + ), + UserAgentParser( + '(OPiOS)/(\\d+).(\\d+).(\\d+)', + 'Opera Mini', + None, + None, + ), + UserAgentParser( + 'Chrome/.+( MMS)/(\\d+).(\\d+).(\\d+)', + 'Opera Neon', + None, + None, + ), + UserAgentParser( + '(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'webOS Browser', + None, + None, + ), + UserAgentParser( + '(luakit)', + 'LuaKit', + None, + None, + ), + UserAgentParser( + '(Snowshoe)/(\\d+)\\.(\\d+).(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'Gecko/\\d+ (Lightning)/(\\d+)\\.(\\d+)\\.?((?:[ab]?\\d+[a-z]*)|(?:\\d*))', + None, + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|)) \\(Swiftfox\\)', + 'Swiftfox', + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|) \\(Swiftfox\\)', + 'Swiftfox', + None, + None, + ), + UserAgentParser( + '(rekonq)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Safari', + 'Rekonq', + None, + None, + ), + UserAgentParser( + 'rekonq', + 'Rekonq', + None, + None, + ), + UserAgentParser( + '(conkeror|Conkeror)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Conkeror', + None, + None, + ), + UserAgentParser( + '(konqueror)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Konqueror', + None, + None, + ), + UserAgentParser( + '(WeTab)-Browser', + None, + None, + None, + ), + UserAgentParser( + '(Comodo_Dragon)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Comodo Dragon', + None, + None, + ), + UserAgentParser( + '(Symphony) (\\d+).(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'PLAYSTATION 3.+WebKit', + 'NetFront NX', + None, + None, + ), + UserAgentParser( + 'PLAYSTATION 3', + 'NetFront', + None, + None, + ), + UserAgentParser( + '(PlayStation Portable)', + 'NetFront', + None, + None, + ), + UserAgentParser( + '(PlayStation Vita)', + 'NetFront NX', + None, + None, + ), + UserAgentParser( + 'AppleWebKit.+ (NX)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'NetFront NX', + None, + None, + ), + UserAgentParser( + '(Nintendo 3DS)', + 'NetFront NX', + None, + None, + ), + UserAgentParser( + '(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|)', + 'Amazon Silk', + None, + None, + ), + UserAgentParser( + '(Puffin)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + 'Windows Phone .*(Edge)/(\\d+)\\.(\\d+)', + 'Edge Mobile', + None, + None, + ), + UserAgentParser( + '(EdgA)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Edge Mobile', + None, + None, + ), + UserAgentParser( + '(EdgiOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Edge Mobile', + None, + None, + ), + UserAgentParser( + '(SamsungBrowser)/(\\d+)\\.(\\d+)', + 'Samsung Internet', + None, + None, + ), + UserAgentParser( + '(SznProhlizec)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Seznam prohlížeč', + None, + None, + ), + UserAgentParser( + '(coc_coc_browser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Coc Coc', + None, + None, + ), + UserAgentParser( + '(baidubrowser)[/\\s](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + 'Baidu Browser', + None, + None, + ), + UserAgentParser( + '(FlyFlow)/(\\d+)\\.(\\d+)', + 'Baidu Explorer', + None, + None, + ), + UserAgentParser( + '(MxBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Maxthon', + None, + None, + ), + UserAgentParser( + '(Crosswalk)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Line)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'LINE', + None, + None, + ), + UserAgentParser( + '(MiuiBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'MiuiBrowser', + None, + None, + ), + UserAgentParser( + '(Mint Browser)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Mint Browser', + None, + None, + ), + UserAgentParser( + '(TopBuzz)/(\\d+).(\\d+).(\\d+)', + 'TopBuzz', + None, + None, + ), + UserAgentParser( + 'Mozilla.+Android.+(GSA)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Google', + None, + None, + ), + UserAgentParser( + '(MQQBrowser/Mini)(?:(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + 'QQ Browser Mini', + None, + None, + ), + UserAgentParser( + '(MQQBrowser)(?:/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + 'QQ Browser Mobile', + None, + None, + ), + UserAgentParser( + '(QQBrowser)(?:/(\\d+)(?:\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)|)', + 'QQ Browser', + None, + None, + ), + UserAgentParser( + 'Version/.+(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Mobile WebView', + None, + None, + ), + UserAgentParser( + '; wv\\).+(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Mobile WebView', + None, + None, + ), + UserAgentParser( + '(CrMo)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Mobile', + None, + None, + ), + UserAgentParser( + '(CriOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Mobile iOS', + None, + None, + ), + UserAgentParser( + '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)', + 'Chrome Mobile', + None, + None, + ), + UserAgentParser( + ' Mobile .*(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Mobile', + None, + None, + ), + UserAgentParser( + '(chromeframe)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Chrome Frame', + None, + None, + ), + UserAgentParser( + '(SLP Browser)/(\\d+)\\.(\\d+)', + 'Tizen Browser', + None, + None, + ), + UserAgentParser( + '(SE 2\\.X) MetaSr (\\d+)\\.(\\d+)', + 'Sogou Explorer', + None, + None, + ), + UserAgentParser( + '(Rackspace Monitoring)/(\\d+)\\.(\\d+)', + 'RackspaceBot', + None, + None, + ), + UserAgentParser( + '(PyAMF)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(YaBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Yandex Browser', + None, + None, + ), + UserAgentParser( + '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+).* MRCHROME', + 'Mail.ru Chromium Browser', + None, + None, + ), + UserAgentParser( + '(AOL) (\\d+)\\.(\\d+); AOLBuild (\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(PodCruncher|Downcast)[ /]?(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + ' (BoxNotes)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)', + 'Whale', + None, + None, + ), + UserAgentParser( + '(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Whale', + None, + None, + ), + UserAgentParser( + '(1Password)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Ghost)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Slack_SSB)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Slack Desktop Client', + None, + None, + ), + UserAgentParser( + '(HipChat)/?(\\d+|)', + 'HipChat Desktop Client', + None, + None, + ), + UserAgentParser( + '\\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + 'Microsoft Office Outlook 12\\.\\d+\\.\\d+|MSOffice 12', + 'Outlook', + '2007', + None, + ), + UserAgentParser( + 'Microsoft Outlook 14\\.\\d+\\.\\d+|MSOffice 14', + 'Outlook', + '2010', + None, + ), + UserAgentParser( + 'Microsoft Outlook 15\\.\\d+\\.\\d+', + 'Outlook', + '2013', + None, + ), + UserAgentParser( + 'Microsoft Outlook (?:Mail )?16\\.\\d+\\.\\d+|MSOffice 16', + 'Outlook', + '2016', + None, + ), + UserAgentParser( + 'Microsoft Office (Word) 2014', + None, + None, + None, + ), + UserAgentParser( + 'Outlook-Express\\/7\\.0.*', + 'Windows Live Mail', + None, + None, + ), + UserAgentParser( + '(Airmail) (\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Thunderbird)/(\\d+)\\.(\\d+)(?:\\.(\\d+(?:pre|))|)', + 'Thunderbird', + None, + None, + ), + UserAgentParser( + '(Postbox)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Postbox', + None, + None, + ), + UserAgentParser( + '(Barca(?:Pro)?)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Barca', + None, + None, + ), + UserAgentParser( + '(Lotus-Notes)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Lotus Notes', + None, + None, + ), + UserAgentParser( + 'Superhuman', + 'Superhuman', + None, + None, + ), + UserAgentParser( + '(Vivaldi)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Edge?)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + 'Edge', + None, + None, + ), + UserAgentParser( + '(brave)/(\\d+)\\.(\\d+)\\.(\\d+) Chrome', + 'Brave', + None, + None, + ), + UserAgentParser( + '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)[\\d.]* Iron[^/]', + 'Iron', + None, + None, + ), + UserAgentParser( + '\\b(Dolphin)(?: |HDCN/|/INT\\-)(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(HeadlessChrome)(?:/(\\d+)\\.(\\d+)\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Evolution)/(\\d+)\\.(\\d+)\\.(\\d+\\.\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(RCM CardDAV plugin)/(\\d+)\\.(\\d+)\\.(\\d+(?:-dev|))', + None, + None, + None, + ), + UserAgentParser( + '(bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Chromium|Chrome)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(IEMobile)[ /](\\d+)\\.(\\d+)', + 'IE Mobile', + None, + None, + ), + UserAgentParser( + '(BacaBerita App)\\/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(bPod|Pocket Casts|Player FM)$', + None, + None, + None, + ), + UserAgentParser( + '^(AlexaMediaPlayer|VLC)/(\\d+)\\.(\\d+)\\.([^.\\s]+)', + None, + None, + None, + ), + UserAgentParser( + '^(AntennaPod|WMPlayer|Zune|Podkicker|Radio|ExoPlayerDemo|Overcast|PocketTunes|NSPlayer|okhttp|DoggCatcher|QuickNews|QuickTime|Peapod|Podcasts|GoldenPod|VLC|Spotify|Miro|MediaGo|Juice|iPodder|gPodder|Banshee)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '^(Peapod|Liferea)/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)', + None, + None, + None, + ), + UserAgentParser( + '^(bPod|Player FM) BMID/(\\S+)', + None, + None, + None, + ), + UserAgentParser( + '^(Podcast ?Addict)/v(\\d+) ', + None, + None, + None, + ), + UserAgentParser( + '^(Podcast ?Addict) ', + 'PodcastAddict', + None, + None, + ), + UserAgentParser( + '(Replay) AV', + None, + None, + None, + ), + UserAgentParser( + '(VOX) Music Player', + None, + None, + None, + ), + UserAgentParser( + '(CITA) RSS Aggregator/(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Pocket Casts)$', + None, + None, + None, + ), + UserAgentParser( + '(Player FM)$', + None, + None, + None, + ), + UserAgentParser( + '(LG Player|Doppler|FancyMusic|MediaMonkey|Clementine) (\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)', + None, + None, + None, + ), + UserAgentParser( + '(philpodder)/(\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)', + None, + None, + None, + ), + UserAgentParser( + '(Player FM|Pocket Casts|DoggCatcher|Spotify|MediaMonkey|MediaGo|BashPodder)', + None, + None, + None, + ), + UserAgentParser( + '(QuickTime)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Kinoma)(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Fancy) Cloud Music (\\d+)\\.(\\d+)', + 'FancyMusic', + None, + None, + ), + UserAgentParser( + 'EspnDownloadManager', + 'ESPN', + None, + None, + ), + UserAgentParser( + '(ESPN) Radio (\\d+)\\.(\\d+)(?:\\.(\\d+)|) ?(?:rv:(\\d+)|) ', + None, + None, + None, + ), + UserAgentParser( + '(podracer|jPodder) v ?(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(ZDM)/(\\d+)\\.(\\d+)[; ]?', + None, + None, + None, + ), + UserAgentParser( + '(Zune|BeyondPod) (\\d+)(?:\\.(\\d+)|)[\\);]', + None, + None, + None, + ), + UserAgentParser( + '(WMPlayer)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(Lavf)', + 'WMPlayer', + None, + None, + ), + UserAgentParser( + '^(RSSRadio)[ /]?(\\d+|)', + None, + None, + None, + ), + UserAgentParser( + '(RSS_Radio) (\\d+)\\.(\\d+)', + 'RSSRadio', + None, + None, + ), + UserAgentParser( + '(Podkicker) \\S+/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Podkicker', + None, + None, + ), + UserAgentParser( + '^(HTC) Streaming Player \\S+ / \\S+ / \\S+ / (\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '^(Stitcher)/iOS', + None, + None, + None, + ), + UserAgentParser( + '^(Stitcher)/Android', + None, + None, + None, + ), + UserAgentParser( + '^(VLC) .*version (\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + ' (VLC) for', + None, + None, + None, + ), + UserAgentParser( + '(vlc)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'VLC', + None, + None, + ), + UserAgentParser( + '^(foobar)\\S+/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)', + None, + None, + None, + ), + UserAgentParser( + '^(Clementine)\\S+ ([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)', + None, + None, + None, + ), + UserAgentParser( + '(amarok)/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)', + 'Amarok', + None, + None, + ), + UserAgentParser( + '(Custom)-Feed Reader', + None, + None, + None, + ), + UserAgentParser( + '(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(iCab|Lunascape|Opera|Android|Jasmine|Polaris|Microsoft SkyDriveSync|The Bat!) (\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(Kindle)/(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Android) Donut', + None, + '1', + '2', + ), + UserAgentParser( + '(Android) Eclair', + None, + '2', + '1', + ), + UserAgentParser( + '(Android) Froyo', + None, + '2', + '2', + ), + UserAgentParser( + '(Android) Gingerbread', + None, + '2', + '3', + ), + UserAgentParser( + '(Android) Honeycomb', + None, + '3', + None, + ), + UserAgentParser( + '(MSIE) (\\d+)\\.(\\d+).*XBLWP7', + 'IE Large Screen', + None, + None, + ), + UserAgentParser( + '(Nextcloud)', + None, + None, + None, + ), + UserAgentParser( + '(mirall)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(ownCloud-android)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Owncloud', + None, + None, + ), + UserAgentParser( + '(OC)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) \\(Skype for Business\\)', + 'Skype', + None, + None, + ), + UserAgentParser( + '(Obigo)InternetBrowser', + None, + None, + None, + ), + UserAgentParser( + '(Obigo)\\-Browser', + None, + None, + None, + ), + UserAgentParser( + '(Obigo|OBIGO)[^\\d]*(\\d+)(?:.(\\d+)|)', + 'Obigo', + None, + None, + ), + UserAgentParser( + '(MAXTHON|Maxthon) (\\d+)\\.(\\d+)', + 'Maxthon', + None, + None, + ), + UserAgentParser( + '(Maxthon|MyIE2|Uzbl|Shiira)', + None, + '0', + None, + ), + UserAgentParser( + '(BrowseX) \\((\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(NCSA_Mosaic)/(\\d+)\\.(\\d+)', + 'NCSA Mosaic', + None, + None, + ), + UserAgentParser( + '(POLARIS)/(\\d+)\\.(\\d+)', + 'Polaris', + None, + None, + ), + UserAgentParser( + '(Embider)/(\\d+)\\.(\\d+)', + 'Polaris', + None, + None, + ), + UserAgentParser( + '(BonEcho)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)', + 'Bon Echo', + None, + None, + ), + UserAgentParser( + '(TopBuzz) com.alex.NewsMaster/(\\d+).(\\d+).(\\d+)', + 'TopBuzz', + None, + None, + ), + UserAgentParser( + '(TopBuzz) com.mobilesrepublic.newsrepublic/(\\d+).(\\d+).(\\d+)', + 'TopBuzz', + None, + None, + ), + UserAgentParser( + '(TopBuzz) com.topbuzz.videoen/(\\d+).(\\d+).(\\d+)', + 'TopBuzz', + None, + None, + ), + UserAgentParser( + '(iPod|iPhone|iPad).+GSA/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Mobile', + 'Google', + None, + None, + ), + UserAgentParser( + '(iPod|iPhone|iPad).+Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).*[ +]Safari', + 'Mobile Safari', + None, + None, + ), + UserAgentParser( + '(iPod|iPod touch|iPhone|iPad);.*CPU.*OS[ +](\\d+)_(\\d+)(?:_(\\d+)|).* AppleNews\\/\\d+\\.\\d+\\.\\d+?', + 'Mobile Safari UI/WKWebView', + None, + None, + ), + UserAgentParser( + '(iPod|iPhone|iPad).+Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Mobile Safari UI/WKWebView', + None, + None, + ), + UserAgentParser( + '(iPod|iPod touch|iPhone|iPad).* Safari', + 'Mobile Safari', + None, + None, + ), + UserAgentParser( + '(iPod|iPod touch|iPhone|iPad)', + 'Mobile Safari UI/WKWebView', + None, + None, + ), + UserAgentParser( + '(Watch)(\\d+),(\\d+)', + 'Apple $1 App', + None, + None, + ), + UserAgentParser( + '(Outlook-iOS)/\\d+\\.\\d+\\.prod\\.iphone \\((\\d+)\\.(\\d+)\\.(\\d+)\\)', + None, + None, + None, + ), + UserAgentParser( + '(AvantGo) (\\d+).(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(OneBrowser)/(\\d+).(\\d+)', + 'ONE Browser', + None, + None, + ), + UserAgentParser( + '(Avant)', + None, + '1', + None, + ), + UserAgentParser( + '(QtCarBrowser)', + None, + '1', + None, + ), + UserAgentParser( + '^(iBrowser/Mini)(\\d+).(\\d+)', + 'iBrowser Mini', + None, + None, + ), + UserAgentParser( + '^(iBrowser|iRAPP)/(\\d+).(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(Nokia)', + 'Nokia Services (WAP) Browser', + None, + None, + ), + UserAgentParser( + '(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)\\.(\\d+)', + 'Nokia Browser', + None, + None, + ), + UserAgentParser( + '(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)', + 'Nokia Browser', + None, + None, + ), + UserAgentParser( + '(NokiaBrowser)/(\\d+)\\.(\\d+)', + 'Nokia Browser', + None, + None, + ), + UserAgentParser( + '(BrowserNG)/(\\d+)\\.(\\d+).(\\d+)', + 'Nokia Browser', + None, + None, + ), + UserAgentParser( + '(Series60)/5\\.0', + 'Nokia Browser', + '7', + '0', + ), + UserAgentParser( + '(Series60)/(\\d+)\\.(\\d+)', + 'Nokia OSS Browser', + None, + None, + ), + UserAgentParser( + '(S40OviBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)', + 'Ovi Browser', + None, + None, + ), + UserAgentParser( + '(Nokia)[EN]?(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(PlayBook).+RIM Tablet OS (\\d+)\\.(\\d+)\\.(\\d+)', + 'BlackBerry WebKit', + None, + None, + ), + UserAgentParser( + '(Black[bB]erry|BB10).+Version/(\\d+)\\.(\\d+)\\.(\\d+)', + 'BlackBerry WebKit', + None, + None, + ), + UserAgentParser( + '(Black[bB]erry)\\s?(\\d+)', + 'BlackBerry', + None, + None, + ), + UserAgentParser( + '(OmniWeb)/v(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Blazer)/(\\d+)\\.(\\d+)', + 'Palm Blazer', + None, + None, + ), + UserAgentParser( + '(Pre)/(\\d+)\\.(\\d+)', + 'Palm Pre', + None, + None, + ), + UserAgentParser( + '(ELinks)/(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(ELinks) \\((\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Links) \\((\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(QtWeb) Internet Browser/(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(PhantomJS)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(AppleWebKit)/(\\d+)(?:\\.(\\d+)|)\\+ .* Safari', + 'WebKit Nightly', + None, + None, + ), + UserAgentParser( + '(Version)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).*Safari/', + 'Safari', + None, + None, + ), + UserAgentParser( + '(Safari)/\\d+', + None, + None, + None, + ), + UserAgentParser( + '(OLPC)/Update(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(OLPC)/Update()\\.(\\d+)', + None, + '0', + None, + ), + UserAgentParser( + '(SEMC\\-Browser)/(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Teleca)', + 'Teleca Browser', + None, + None, + ), + UserAgentParser( + '(Phantom)/V(\\d+)\\.(\\d+)', + 'Phantom Browser', + None, + None, + ), + UserAgentParser( + '(Trident)/(7|8)\\.(0)', + 'IE', + '11', + None, + ), + UserAgentParser( + '(Trident)/(6)\\.(0)', + 'IE', + '10', + None, + ), + UserAgentParser( + '(Trident)/(5)\\.(0)', + 'IE', + '9', + None, + ), + UserAgentParser( + '(Trident)/(4)\\.(0)', + 'IE', + '8', + None, + ), + UserAgentParser( + '(Espial)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '(AppleWebKit)/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Apple Mail', + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Firefox)/(\\d+)\\.(\\d+)(pre|[ab]\\d+[a-z]*|)', + None, + None, + None, + ), + UserAgentParser( + '([MS]?IE) (\\d+)\\.(\\d+)', + 'IE', + None, + None, + ), + UserAgentParser( + '(python-requests)/(\\d+)\\.(\\d+)', + 'Python Requests', + None, + None, + ), + UserAgentParser( + '\\b(Windows-Update-Agent|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|aria2|Axel|OpenBSD ftp|lftp|jupdate|insomnia|fetch libfetch|akka-http|got)(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)', + None, + None, + None, + ), + UserAgentParser( + '(Python/3\\.\\d{1,3} aiohttp)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Python/3\\.\\d{1,3} aiohttp)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Java)[/ ]?\\d+\\.(\\d+)\\.(\\d+)[_-]*([a-zA-Z0-9]+|)', + None, + None, + None, + ), + UserAgentParser( + '^(Cyberduck)/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.\\d+|)', + None, + None, + None, + ), + UserAgentParser( + '^(S3 Browser) (\\d+)-(\\d+)-(\\d+)(?:\\s*http://s3browser\\.com|)', + None, + None, + None, + ), + UserAgentParser( + '(S3Gof3r)', + None, + None, + None, + ), + UserAgentParser( + '\\b(ibm-cos-sdk-(?:core|java|js|python))/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + None, + None, + None, + ), + UserAgentParser( + '^(rusoto)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(rclone)/v(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(Roku)/DVP-(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '(Kurio)\\/(\\d+)\\.(\\d+)\\.(\\d+)', + 'Kurio App', + None, + None, + ), + UserAgentParser( + '^(Box(?: Sync)?)/(\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + ), + UserAgentParser( + '^(ViaFree|Viafree)-(?:tvOS-)?[A-Z]{2}/(\\d+)\\.(\\d+)\\.(\\d+)', + 'ViaFree', + None, + None, + ), +] + +DEVICE_PARSERS = [ + DeviceParser( + '(?:(?:iPhone|Windows CE|Windows Phone|Android).*(?:(?:Bot|Yeti)-Mobile|YRSpider|BingPreview|bots?/\\d|(?:bot|spider)\\.html)|AdsBot-Google-Mobile.*iPhone)', + 'i', + 'Spider', + 'Spider', + 'Smartphone', + ), + DeviceParser( + '(?:DoCoMo|\\bMOT\\b|\\bLG\\b|Nokia|Samsung|SonyEricsson).*(?:(?:Bot|Yeti)-Mobile|bots?/\\d|(?:bot|crawler)\\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker)', + 'i', + 'Spider', + 'Spider', + 'Feature Phone', + ), + DeviceParser( + ' PTST/\\d+(?:\\.)?\\d+$', + None, + 'Spider', + 'Spider', + None, + ), + DeviceParser( + 'X11; Datanyze; Linux', + None, + 'Spider', + 'Spider', + None, + ), + DeviceParser( + '\\bSmartWatch {0,2}\\( {0,2}([^;]+) {0,2}; {0,2}([^;]+) {0,2};', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + 'Android Application[^\\-]+ - (Sony) ?(Ericsson|) (.+) \\w+ - ', + None, + '$1 $2', + '$1$2', + '$3', + ), + DeviceParser( + 'Android Application[^\\-]+ - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\\-](.+) \\w+ - ', + 'i', + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + 'Android Application[^\\-]+ - ([^ ]+) (.+) \\w+ - ', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *([BLRQ]C\\d{4}[A-Z]+?)(?: Build|\\) AppleWebKit)', + None, + '3Q $1', + '3Q', + '$1', + ), + DeviceParser( + '; *(?:3Q_)([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '3Q $1', + '3Q', + '$1', + ), + DeviceParser( + 'Android [34].*; *(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: Lite| 3G|)|A701|B1-A71|A1-\\d{3}|B1-\\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider SL101|DA22[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Acer', + '$1', + ), + DeviceParser( + '; *Acer Iconia Tab ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Acer', + '$1', + ), + DeviceParser( + '; *(Z1[1235]0|E320[^/]*|S500|S510|Liquid[^;/]*|Iconia A\\d+)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Acer', + '$1', + ), + DeviceParser( + '; *(Acer |ACER )([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Acer', + '$2', + ), + DeviceParser( + '; *(Advent |)(Vega(?:Bean|Comb|)).*?(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Advent', + '$2', + ), + DeviceParser( + '; *(Ainol |)((?:NOVO|[Nn]ovo)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Ainol', + '$2', + ), + DeviceParser( + '; *AIRIS[ _\\-]?([^/;\\)]+) *(?:;|\\)|Build)', + 'i', + '$1', + 'Airis', + '$1', + ), + DeviceParser( + '; *(OnePAD[^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Airis', + '$1', + ), + DeviceParser( + '; *Airpad[ \\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Airpad $1', + 'Airpad', + '$1', + ), + DeviceParser( + '; *(one ?touch) (EVO7|T10|T20)(?: Build|\\) AppleWebKit)', + None, + 'Alcatel One Touch $2', + 'Alcatel', + 'One Touch $2', + ), + DeviceParser( + '; *(?:alcatel[ _]|)(?:(?:one[ _]?touch[ _])|ot[ \\-])([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Alcatel One Touch $1', + 'Alcatel', + 'One Touch $1', + ), + DeviceParser( + '; *(TCL)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(Vodafone Smart II|Optimus_Madrid)(?: Build|\\) AppleWebKit)', + None, + 'Alcatel $1', + 'Alcatel', + '$1', + ), + DeviceParser( + '; *BASE_Lutea_3(?: Build|\\) AppleWebKit)', + None, + 'Alcatel One Touch 998', + 'Alcatel', + 'One Touch 998', + ), + DeviceParser( + '; *BASE_Varia(?: Build|\\) AppleWebKit)', + None, + 'Alcatel One Touch 918D', + 'Alcatel', + 'One Touch 918D', + ), + DeviceParser( + '; *((?:FINE|Fine)\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Allfine', + '$1', + ), + DeviceParser( + '; *(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).*?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Allview', + '$2', + ), + DeviceParser( + '; *(ALLVIEW[ _]?|Allview[ _]?|)(AX1_Shine|AX2_Frenzy)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Allview', + '$2', + ), + DeviceParser( + '; *(ALLVIEW[ _]?|Allview[ _]?)([^;/]*?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Allview', + '$2', + ), + DeviceParser( + '; *(A13-MID)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Allwinner', + '$1', + ), + DeviceParser( + '; *(Allwinner)[ _\\-]?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Allwinner', + '$1', + ), + DeviceParser( + '; *(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Amaway', + '$1', + ), + DeviceParser( + '; *(?:AMOI|Amoi)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Amoi $1', + 'Amoi', + '$1', + ), + DeviceParser( + '^(?:AMOI|Amoi)[ _]([^;/]+?) Linux', + None, + 'Amoi $1', + 'Amoi', + '$1', + ), + DeviceParser( + '; *(MW(?:0[789]|10)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Aoc', + '$1', + ), + DeviceParser( + '; *(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T|)|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D)(?: Build|\\) AppleWebKit)', + None, + 'Aoson $1', + 'Aoson', + '$1', + ), + DeviceParser( + '; *Aoson ([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Aoson $1', + 'Aoson', + '$1', + ), + DeviceParser( + '; *[Aa]panda[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Apanda $1', + 'Apanda', + '$1', + ), + DeviceParser( + '; *(?:ARCHOS|Archos) ?(GAMEPAD.*?)(?: Build|\\) AppleWebKit)', + None, + 'Archos $1', + 'Archos', + '$1', + ), + DeviceParser( + 'ARCHOS; GOGI; ([^;]+);', + None, + 'Archos $1', + 'Archos', + '$1', + ), + DeviceParser( + '(?:ARCHOS|Archos)[ _]?(.*?)(?: Build|[;/\\(\\)\\-]|$)', + None, + 'Archos $1', + 'Archos', + '$1', + ), + DeviceParser( + '; *(AN(?:7|8|9|10|13)[A-Z0-9]{1,4})(?: Build|\\) AppleWebKit)', + None, + 'Archos $1', + 'Archos', + '$1', + ), + DeviceParser( + '; *(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9)(?: Build|\\) AppleWebKit)', + None, + 'Archos $1', + 'Archos', + '$1', + ), + DeviceParser( + '; *(PAD-FMD[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Arival', + '$1', + ), + DeviceParser( + '; *(BioniQ) ?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Arival', + '$1 $2', + ), + DeviceParser( + '; *(AN\\d[^;/]+|ARCHM\\d+)(?: Build|\\) AppleWebKit)', + None, + 'Arnova $1', + 'Arnova', + '$1', + ), + DeviceParser( + '; *(?:ARNOVA|Arnova) ?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Arnova $1', + 'Arnova', + '$1', + ), + DeviceParser( + '; *(?:ASSISTANT |)(AP)-?([1789]\\d{2}[A-Z]{0,2}|80104)(?: Build|\\) AppleWebKit)', + None, + 'Assistant $1-$2', + 'Assistant', + '$1-$2', + ), + DeviceParser( + '; *(ME17\\d[^;/]*|ME3\\d{2}[^;/]+|K00[A-Z]|Nexus 10|Nexus 7(?: 2013|)|PadFone[^;/]*|Transformer[^;/]*|TF\\d{3}[^;/]*|eeepc)(?: Build|\\) AppleWebKit)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + '; *ASUS[ _]*([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + '; *Garmin-Asus ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Garmin-Asus $1', + 'Garmin-Asus', + '$1', + ), + DeviceParser( + '; *(Garminfone)(?: Build|\\) AppleWebKit)', + None, + 'Garmin $1', + 'Garmin-Asus', + '$1', + ), + DeviceParser( + '; (@TAB-[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Attab', + '$1', + ), + DeviceParser( + '; *(T-(?:07|[^0]\\d)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Audiosonic', + '$1', + ), + DeviceParser( + '; *(?:Axioo[ _\\-]([^;/]+?)|(picopad)[ _\\-]([^;/]+?))(?: Build|\\) AppleWebKit)', + 'i', + 'Axioo $1$2 $3', + 'Axioo', + '$1$2 $3', + ), + DeviceParser( + '; *(V(?:100|700|800)[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Azend', + '$1', + ), + DeviceParser( + '; *(IBAK\\-[^;/]*)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Bak', + '$1', + ), + DeviceParser( + '; *(HY5001|HY6501|X12|X21|I5)(?: Build|\\) AppleWebKit)', + None, + 'Bedove $1', + 'Bedove', + '$1', + ), + DeviceParser( + '; *(JC-[^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'Benss $1', + 'Benss', + '$1', + ), + DeviceParser( + '; *(BB) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Blackberry', + '$2', + ), + DeviceParser( + '; *(BlackBird)[ _](I8.*?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(BlackBird)[ _](.*?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *([0-9]+BP[EM][^;/]*|Endeavour[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Blaupunkt $1', + 'Blaupunkt', + '$1', + ), + DeviceParser( + '; *((?:BLU|Blu)[ _\\-])([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Blu', + '$2', + ), + DeviceParser( + '; *(?:BMOBILE )?(Blu|BLU|DASH [^;/]+|VIVO 4\\.3|TANK 4\\.5)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Blu', + '$1', + ), + DeviceParser( + '; *(TOUCH\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Blusens', + '$1', + ), + DeviceParser( + '; *(AX5\\d+)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Bmobile', + '$1', + ), + DeviceParser( + '; *([Bb]q) ([^;/]+?);?(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'bq', + '$2', + ), + DeviceParser( + '; *(Maxwell [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'bq', + '$1', + ), + DeviceParser( + '; *((?:B-Tab|B-TAB) ?\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Braun', + '$1', + ), + DeviceParser( + '; *(Broncho) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *CAPTIVA ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Captiva $1', + 'Captiva', + '$1', + ), + DeviceParser( + '; *(C771|CAL21|IS11CA)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Casio', + '$1', + ), + DeviceParser( + '; *(?:Cat|CAT) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Cat $1', + 'Cat', + '$1', + ), + DeviceParser( + '; *(?:Cat)(Nova.*?)(?: Build|\\) AppleWebKit)', + None, + 'Cat $1', + 'Cat', + '$1', + ), + DeviceParser( + '; *(INM8002KP|ADM8000KP_[AB])(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Cat', + 'Tablet PHOENIX 8.1J0', + ), + DeviceParser( + '; *(?:[Cc]elkon[ _\\*]|CELKON[ _\\*])([^;/\\)]+) ?(?:Build|;|\\))', + None, + '$1', + 'Celkon', + '$1', + ), + DeviceParser( + 'Build/(?:[Cc]elkon)+_?([^;/_\\)]+)', + None, + '$1', + 'Celkon', + '$1', + ), + DeviceParser( + '; *(CT)-?(\\d+)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Celkon', + '$1$2', + ), + DeviceParser( + '; *(A19|A19Q|A105|A107[^;/\\)]*) ?(?:Build|;|\\))', + None, + '$1', + 'Celkon', + '$1', + ), + DeviceParser( + '; *(TPC[0-9]{4,5})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'ChangJia', + '$1', + ), + DeviceParser( + '; *(Cloudfone)[ _](Excite)([^ ][^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2 $3', + 'Cloudfone', + '$1 $2 $3', + ), + DeviceParser( + '; *(Excite|ICE)[ _](\\d+[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Cloudfone $1 $2', + 'Cloudfone', + 'Cloudfone $1 $2', + ), + DeviceParser( + '; *(Cloudfone|CloudPad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Cloudfone', + '$1 $2', + ), + DeviceParser( + '; *((?:Aquila|Clanga|Rapax)[^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Cmx', + '$1', + ), + DeviceParser( + '; *(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\\(3G\\)-4G| GB 8K| 3G| 8K| GB)? *(?:Build|[;\\)])', + None, + 'CobyKyros $1$2', + 'CobyKyros', + '$1$2', + ), + DeviceParser( + '; *([^;/]*)Coolpad[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Coolpad', + '$1$2', + ), + DeviceParser( + '; *(CUBE[ _])?([KU][0-9]+ ?GT.*?|A5300)(?: Build|\\) AppleWebKit)', + 'i', + '$1$2', + 'Cube', + '$2', + ), + DeviceParser( + '; *CUBOT ([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Cubot', + '$1', + ), + DeviceParser( + '; *(BOBBY)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Cubot', + '$1', + ), + DeviceParser( + '; *(Dslide [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Danew', + '$1', + ), + DeviceParser( + '; *(XCD)[ _]?(28|35)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1$2', + 'Dell', + '$1$2', + ), + DeviceParser( + '; *(001DL)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + 'Streak', + ), + DeviceParser( + '; *(?:Dell|DELL) (Streak)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + 'Streak', + ), + DeviceParser( + '; *(101DL|GS01|Streak Pro[^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + 'Streak Pro', + ), + DeviceParser( + '; *([Ss]treak ?7)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + 'Streak 7', + ), + DeviceParser( + '; *(Mini-3iX)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + '; *(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.*?|Streak[ _]Pro)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + '; *Dell[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + '; *Dell ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + '; *(TA[CD]-\\d+[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Denver', + '$1', + ), + DeviceParser( + '; *(iP[789]\\d{2}(?:-3G)?|IP10\\d{2}(?:-8GB)?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Dex', + '$1', + ), + DeviceParser( + '; *(AirTab)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'DNS', + '$1 $2', + ), + DeviceParser( + '; *(F\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Fujitsu', + '$1', + ), + DeviceParser( + '; *(HT-03A)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'HTC', + 'Magic', + ), + DeviceParser( + '; *(HT\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(L\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'LG', + '$1', + ), + DeviceParser( + '; *(N\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Nec', + '$1', + ), + DeviceParser( + '; *(P\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Panasonic', + '$1', + ), + DeviceParser( + '; *(SC\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *(SH\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sharp', + '$1', + ), + DeviceParser( + '; *(SO\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'SonyEricsson', + '$1', + ), + DeviceParser( + '; *(T\\-0[12][^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Toshiba', + '$1', + ), + DeviceParser( + '; *(DOOV)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'DOOV', + '$2', + ), + DeviceParser( + '; *(Enot|ENOT)[ -]?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Enot', + '$2', + ), + DeviceParser( + '; *[^;/]+ Build/(?:CROSS|Cross)+[ _\\-]([^\\)]+)', + None, + 'CROSS $1', + 'Evercoss', + 'Cross $1', + ), + DeviceParser( + '; *(CROSS|Cross)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Evercoss', + 'Cross $2', + ), + DeviceParser( + '; *Explay[_ ](.+?)(?:[\\)]| Build)', + None, + '$1', + 'Explay', + '$1', + ), + DeviceParser( + '; *(IQ.*?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Fly', + '$1', + ), + DeviceParser( + '; *(Fly|FLY)[ _](IQ[^;]+?|F[34]\\d+[^;]*?);?(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Fly', + '$2', + ), + DeviceParser( + '; *(M532|Q572|FJL21)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Fujitsu', + '$1', + ), + DeviceParser( + '; *(G1)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Galapad', + '$1', + ), + DeviceParser( + '; *(Geeksphone) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(G[^F]?FIVE) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Gfive', + '$2', + ), + DeviceParser( + '; *(Gionee)[ _\\-]([^;/]+?)(?:/[^;/]+|)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'Gionee', + '$2', + ), + DeviceParser( + '; *(GN\\d+[A-Z]?|INFINITY_PASSION|Ctrl_V1)(?: Build|\\) AppleWebKit)', + None, + 'Gionee $1', + 'Gionee', + '$1', + ), + DeviceParser( + '; *(E3) Build/JOP40D', + None, + 'Gionee $1', + 'Gionee', + '$1', + ), + DeviceParser( + '\\sGIONEE[-\\s_](\\w*)', + 'i', + 'Gionee $1', + 'Gionee', + '$1', + ), + DeviceParser( + '; *((?:FONE|QUANTUM|INSIGNIA) \\d+[^;/]*|PLAYTAB)(?: Build|\\) AppleWebKit)', + None, + 'GoClever $1', + 'GoClever', + '$1', + ), + DeviceParser( + '; *GOCLEVER ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'GoClever $1', + 'GoClever', + '$1', + ), + DeviceParser( + '; *(Glass \\d+)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Google', + '$1', + ), + DeviceParser( + '; *(Pixel.*?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Google', + '$1', + ), + DeviceParser( + '; *(GSmart)[ -]([^/]+)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Gigabyte', + '$1 $2', + ), + DeviceParser( + '; *(imx5[13]_[^/]+)(?: Build|\\) AppleWebKit)', + None, + 'Freescale $1', + 'Freescale', + '$1', + ), + DeviceParser( + '; *Haier[ _\\-]([^/]+)(?: Build|\\) AppleWebKit)', + None, + 'Haier $1', + 'Haier', + '$1', + ), + DeviceParser( + '; *(PAD1016)(?: Build|\\) AppleWebKit)', + None, + 'Haipad $1', + 'Haipad', + '$1', + ), + DeviceParser( + '; *(M701|M7|M8|M9)(?: Build|\\) AppleWebKit)', + None, + 'Haipad $1', + 'Haipad', + '$1', + ), + DeviceParser( + '; *(SN\\d+T[^;\\)/]*)(?: Build|[;\\)])', + None, + 'Hannspree $1', + 'Hannspree', + '$1', + ), + DeviceParser( + 'Build/HCL ME Tablet ([^;\\)]+)[\\);]', + None, + 'HCLme $1', + 'HCLme', + '$1', + ), + DeviceParser( + '; *([^;\\/]+) Build/HCL', + None, + 'HCLme $1', + 'HCLme', + '$1', + ), + DeviceParser( + '; *(MID-?\\d{4}C[EM])(?: Build|\\) AppleWebKit)', + None, + 'Hena $1', + 'Hena', + '$1', + ), + DeviceParser( + '; *(EG\\d{2,}|HS-[^;/]+|MIRA[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Hisense $1', + 'Hisense', + '$1', + ), + DeviceParser( + '; *(andromax[^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Hisense $1', + 'Hisense', + '$1', + ), + DeviceParser( + '; *(?:AMAZE[ _](S\\d+)|(S\\d+)[ _]AMAZE)(?: Build|\\) AppleWebKit)', + None, + 'AMAZE $1$2', + 'hitech', + 'AMAZE $1$2', + ), + DeviceParser( + '; *(PlayBook)(?: Build|\\) AppleWebKit)', + None, + 'HP $1', + 'HP', + '$1', + ), + DeviceParser( + '; *HP ([^/]+)(?: Build|\\) AppleWebKit)', + None, + 'HP $1', + 'HP', + '$1', + ), + DeviceParser( + '; *([^/]+_tenderloin)(?: Build|\\) AppleWebKit)', + None, + 'HP TouchPad', + 'HP', + 'TouchPad', + ), + DeviceParser( + '; *(HUAWEI |Huawei-|)([UY][^;/]+) Build/(?:Huawei|HUAWEI)([UY][^\\);]+)\\)', + None, + '$1$2', + 'Huawei', + '$2', + ), + DeviceParser( + '; *([^;/]+) Build[/ ]Huawei(MT1-U06|[A-Z]+\\d+[^\\);]+)\\)', + None, + '$1', + 'Huawei', + '$2', + ), + DeviceParser( + '; *(S7|M860) Build', + None, + '$1', + 'Huawei', + '$1', + ), + DeviceParser( + '; *((?:HUAWEI|Huawei)[ \\-]?)(MediaPad) Build', + None, + '$1$2', + 'Huawei', + '$2', + ), + DeviceParser( + '; *((?:HUAWEI[ _]?|Huawei[ _]|)Ascend[ _])([^;/]+) Build', + None, + '$1$2', + 'Huawei', + '$2', + ), + DeviceParser( + '; *((?:HUAWEI|Huawei)[ _\\-]?)((?:G700-|MT-)[^;/]+) Build', + None, + '$1$2', + 'Huawei', + '$2', + ), + DeviceParser( + '; *((?:HUAWEI|Huawei)[ _\\-]?)([^;/]+) Build', + None, + '$1$2', + 'Huawei', + '$2', + ), + DeviceParser( + '; *(MediaPad[^;]+|SpringBoard) Build/Huawei', + None, + '$1', + 'Huawei', + '$1', + ), + DeviceParser( + '; *([^;]+) Build/(?:Huawei|HUAWEI)', + None, + '$1', + 'Huawei', + '$1', + ), + DeviceParser( + '; *([Uu])([89]\\d{3}) Build', + None, + '$1$2', + 'Huawei', + 'U$2', + ), + DeviceParser( + '; *(?:Ideos |IDEOS )(S7) Build', + None, + 'Huawei Ideos$1', + 'Huawei', + 'Ideos$1', + ), + DeviceParser( + '; *(?:Ideos |IDEOS )([^;/]+\\s*|\\s*)Build', + None, + 'Huawei Ideos$1', + 'Huawei', + 'Ideos$1', + ), + DeviceParser( + '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P|ATH-.+?) Build[/ ]', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + '; *((?:[A-Z]{3})\\-L[A-Za0-9]{2})[\\)]', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + '; *HTC[ _]([^;]+); Windows Phone', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(?:HTC[ _/])+([^ _/]+)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))', + None, + 'HTC $1 $2', + 'HTC', + '$1 $2', + ), + DeviceParser( + '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))', + None, + 'HTC $1 $2 $3', + 'HTC', + '$1 $2 $3', + ), + DeviceParser( + '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))', + None, + 'HTC $1 $2 $3 $4', + 'HTC', + '$1 $2 $3 $4', + ), + DeviceParser( + '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/;]+)(?: *Build|[;\\)]| - )', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)(?: *Build|[;\\)]| - )', + None, + 'HTC $1 $2', + 'HTC', + '$1 $2', + ), + DeviceParser( + '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)|)(?: *Build|[;\\)]| - )', + None, + 'HTC $1 $2 $3', + 'HTC', + '$1 $2 $3', + ), + DeviceParser( + '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ /;]+)|)|)|)(?: *Build|[;\\)]| - )', + None, + 'HTC $1 $2 $3 $4', + 'HTC', + '$1 $2 $3 $4', + ), + DeviceParser( + 'HTC Streaming Player [^\\/]*/[^\\/]*/ htc_([^/]+) /', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '(?:[;,] *|^)(?:htccn_chs-|)HTC[ _-]?([^;]+?)(?: *Build|clay|Android|-?Mozilla| Opera| Profile| UNTRUSTED|[;/\\(\\)]|$)', + 'i', + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\\+?)\\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: Build|[/;\\(\\)])', + 'i', + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ _-](.+?)(?:[/;\\)]|Build|MIUI|1\\.0)', + 'i', + 'HTC $1 $2', + 'HTC', + '$1 $2', + ), + DeviceParser( + '; *HYUNDAI (T\\d[^/]*)(?: Build|\\) AppleWebKit)', + None, + 'Hyundai $1', + 'Hyundai', + '$1', + ), + DeviceParser( + '; *HYUNDAI ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Hyundai $1', + 'Hyundai', + '$1', + ), + DeviceParser( + '; *(X700|Hold X|MB-6900)(?: Build|\\) AppleWebKit)', + None, + 'Hyundai $1', + 'Hyundai', + '$1', + ), + DeviceParser( + '; *(?:iBall[ _\\-]|)(Andi)[ _]?(\\d[^;/]*)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'iBall', + '$1 $2', + ), + DeviceParser( + '; *(IBall)(?:[ _]([^;/]+?)|)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'iBall', + '$2', + ), + DeviceParser( + '; *(NT-\\d+[^ ;/]*|Net[Tt]AB [^;/]+|Mercury [A-Z]+|iconBIT)(?: S/N:[^;/]+|)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'IconBIT', + '$1', + ), + DeviceParser( + '; *(IMO)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'IMO', + '$2', + ), + DeviceParser( + '; *i-?mobile[ _]([^/]+)(?: Build|\\) AppleWebKit)', + 'i', + 'i-mobile $1', + 'imobile', + '$1', + ), + DeviceParser( + '; *(i-(?:style|note)[^/]*)(?: Build|\\) AppleWebKit)', + 'i', + 'i-mobile $1', + 'imobile', + '$1', + ), + DeviceParser( + '; *(ImPAD) ?(\\d+(?:.)*?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Impression', + '$1 $2', + ), + DeviceParser( + '; *(Infinix)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Infinix', + '$2', + ), + DeviceParser( + '; *(Informer)[ \\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Informer', + '$2', + ), + DeviceParser( + '; *(TAB) ?([78][12]4)(?: Build|\\) AppleWebKit)', + None, + 'Intenso $1', + 'Intenso', + '$1 $2', + ), + DeviceParser( + '; *(?:Intex[ _]|)(AQUA|Aqua)([ _\\.\\-])([^;/]+?) *(?:Build|;)', + None, + '$1$2$3', + 'Intex', + '$1 $3', + ), + DeviceParser( + '; *(?:INTEX|Intex)(?:[_ ]([^\\ _;/]+))(?:[_ ]([^\\ _;/]+)|) *(?:Build|;)', + None, + '$1 $2', + 'Intex', + '$1 $2', + ), + DeviceParser( + '; *([iI]Buddy)[ _]?(Connect)(?:_|\\?_| |)([^;/]*) *(?:Build|;)', + None, + '$1 $2 $3', + 'Intex', + 'iBuddy $2 $3', + ), + DeviceParser( + '; *(I-Buddy)[ _]([^;/]+?) *(?:Build|;)', + None, + '$1 $2', + 'Intex', + 'iBuddy $2', + ), + DeviceParser( + '; *(iOCEAN) ([^/]+)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'iOCEAN', + '$2', + ), + DeviceParser( + '; *(TP\\d+(?:\\.\\d+|)\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'ionik $1', + 'ionik', + '$1', + ), + DeviceParser( + '; *(M702pro)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Iru', + '$1', + ), + DeviceParser( + '; *(DE88Plus|MD70)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Ivio', + '$1', + ), + DeviceParser( + '; *IVIO[_\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Ivio', + '$1', + ), + DeviceParser( + '; *(TPC-\\d+|JAY-TECH)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Jaytech', + '$1', + ), + DeviceParser( + '; *(JY-[^;/]+|G[234]S?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Jiayu', + '$1', + ), + DeviceParser( + '; *(JXD)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'JXD', + '$2', + ), + DeviceParser( + '; *Karbonn[ _]?([^;/]+) *(?:Build|;)', + 'i', + '$1', + 'Karbonn', + '$1', + ), + DeviceParser( + '; *([^;]+) Build/Karbonn', + None, + '$1', + 'Karbonn', + '$1', + ), + DeviceParser( + '; *(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\\d) +Build', + None, + '$1', + 'Karbonn', + '$1', + ), + DeviceParser( + '; *(IS01|IS03|IS05|IS\\d{2}SH)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sharp', + '$1', + ), + DeviceParser( + '; *(IS04)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Regza', + '$1', + ), + DeviceParser( + '; *(IS06|IS\\d{2}PT)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Pantech', + '$1', + ), + DeviceParser( + '; *(IS11S)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'SonyEricsson', + 'Xperia Acro', + ), + DeviceParser( + '; *(IS11CA)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Casio', + 'GzOne $1', + ), + DeviceParser( + '; *(IS11LG)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'LG', + 'Optimus X', + ), + DeviceParser( + '; *(IS11N)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Medias', + '$1', + ), + DeviceParser( + '; *(IS11PT)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Pantech', + 'MIRACH', + ), + DeviceParser( + '; *(IS12F)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Fujitsu', + 'Arrows ES', + ), + DeviceParser( + '; *(IS12M)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Motorola', + 'XT909', + ), + DeviceParser( + '; *(IS12S)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'SonyEricsson', + 'Xperia Acro HD', + ), + DeviceParser( + '; *(ISW11F)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Fujitsu', + 'Arrowz Z', + ), + DeviceParser( + '; *(ISW11HT)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'HTC', + 'EVO', + ), + DeviceParser( + '; *(ISW11K)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Kyocera', + 'DIGNO', + ), + DeviceParser( + '; *(ISW11M)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Motorola', + 'Photon', + ), + DeviceParser( + '; *(ISW11SC)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Samsung', + 'GALAXY S II WiMAX', + ), + DeviceParser( + '; *(ISW12HT)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'HTC', + 'EVO 3D', + ), + DeviceParser( + '; *(ISW13HT)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'HTC', + 'J', + ), + DeviceParser( + '; *(ISW?[0-9]{2}[A-Z]{0,2})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'KDDI', + '$1', + ), + DeviceParser( + '; *(INFOBAR [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'KDDI', + '$1', + ), + DeviceParser( + '; *(JOYPAD|Joypad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Kingcom', + '$1 $2', + ), + DeviceParser( + '; *(Vox|VOX|Arc|K080)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Kobo', + '$1', + ), + DeviceParser( + '\\b(Kobo Touch)\\b', + None, + '$1', + 'Kobo', + '$1', + ), + DeviceParser( + '; *(K-Touch)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'Ktouch', + '$2', + ), + DeviceParser( + '; *((?:EV|KM)-S\\d+[A-Z]?)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'KTtech', + '$1', + ), + DeviceParser( + '; *(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\\d{2})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Kyocera', + '$1', + ), + DeviceParser( + '; *(?:LAVA[ _]|)IRIS[ _\\-]?([^/;\\)]+) *(?:;|\\)|Build)', + 'i', + 'Iris $1', + 'Lava', + 'Iris $1', + ), + DeviceParser( + '; *LAVA[ _]([^;/]+) Build', + None, + '$1', + 'Lava', + '$1', + ), + DeviceParser( + '; *(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]+))_?(?: Build|\\) AppleWebKit)', + None, + 'Lemon $1$2', + 'Lemon', + '$1$2', + ), + DeviceParser( + '; *(TAB-1012)(?: Build|\\) AppleWebKit)', + None, + 'Lenco $1', + 'Lenco', + '$1', + ), + DeviceParser( + '; Lenco ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Lenco $1', + 'Lenco', + '$1', + ), + DeviceParser( + '; *(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build', + None, + '$1', + 'Lenovo', + '$1', + ), + DeviceParser( + '; *(Idea[Tp]ab)[ _]([^;/]+);? Build', + None, + 'Lenovo $1 $2', + 'Lenovo', + '$1 $2', + ), + DeviceParser( + '; *(Idea(?:Tab|pad)) ?([^;/]+) Build', + None, + 'Lenovo $1 $2', + 'Lenovo', + '$1 $2', + ), + DeviceParser( + '; *(ThinkPad) ?(Tablet) Build/', + None, + 'Lenovo $1 $2', + 'Lenovo', + '$1 $2', + ), + DeviceParser( + '; *(?:LNV-|)(?:=?[Ll]enovo[ _\\-]?|LENOVO[ _])(.+?)(?:Build|[;/\\)])', + None, + 'Lenovo $1', + 'Lenovo', + '$1', + ), + DeviceParser( + '[;,] (?:Vodafone |)(SmartTab) ?(II) ?(\\d+) Build/', + None, + 'Lenovo $1 $2 $3', + 'Lenovo', + '$1 $2 $3', + ), + DeviceParser( + '; *(?:Ideapad |)K1 Build/', + None, + 'Lenovo Ideapad K1', + 'Lenovo', + 'Ideapad K1', + ), + DeviceParser( + '; *(3GC101|3GW10[01]|A390) Build/', + None, + '$1', + 'Lenovo', + '$1', + ), + DeviceParser( + '\\b(?:Lenovo|LENOVO)+[ _\\-]?([^,;:/ ]+)', + None, + 'Lenovo $1', + 'Lenovo', + '$1', + ), + DeviceParser( + '; *(MFC\\d+)[A-Z]{2}([^;,/]*),?(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Lexibook', + '$1$2', + ), + DeviceParser( + '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)', + None, + '$1', + 'LG', + '$1', + ), + DeviceParser( + '[;:] *(L-\\d+[A-Z]|LGL\\d+[A-Z]?)(?:/V\\d+|) *(?:Build|[;\\)])', + None, + '$1', + 'LG', + '$1', + ), + DeviceParser( + '; *(LG-)([A-Z]{1,2}\\d{2,}[^,;/\\)\\(]*?)(?:Build| V\\d+|[,;/\\)\\(]|$)', + None, + '$1$2', + 'LG', + '$2', + ), + DeviceParser( + '; *(LG[ \\-]|LG)([^;/]+)[;/]? Build', + None, + '$1$2', + 'LG', + '$2', + ), + DeviceParser( + '^(LG)-([^;/]+)/ Mozilla/.*; Android', + None, + '$1 $2', + 'LG', + '$2', + ), + DeviceParser( + '(Web0S); Linux/(SmartTV)', + None, + 'LG $1 $2', + 'LG', + '$1 $2', + ), + DeviceParser( + '; *((?:SMB|smb)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Malata', + '$1', + ), + DeviceParser( + '; *(?:Malata|MALATA) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Malata', + '$1', + ), + DeviceParser( + '; *(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9])(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Manta', + '$1', + ), + DeviceParser( + '; *(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Match', + '$1', + ), + DeviceParser( + '; *(GenxDroid7|MSD7.*?|AX\\d.*?|Tab 701|Tab 722)(?: Build|\\) AppleWebKit)', + None, + 'Maxx $1', + 'Maxx', + '$1', + ), + DeviceParser( + '; *(M-PP[^;/]+|PhonePad ?\\d{2,}[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Mediacom $1', + 'Mediacom', + '$1', + ), + DeviceParser( + '; *(M-MP[^;/]+|SmartPad ?\\d{2,}[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Mediacom $1', + 'Mediacom', + '$1', + ), + DeviceParser( + '; *(?:MD_|)LIFETAB[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Medion Lifetab $1', + 'Medion', + 'Lifetab $1', + ), + DeviceParser( + '; *MEDION ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Medion $1', + 'Medion', + '$1', + ), + DeviceParser( + '; *(M030|M031|M035|M040|M065|m9)(?: Build|\\) AppleWebKit)', + None, + 'Meizu $1', + 'Meizu', + '$1', + ), + DeviceParser( + '; *(?:meizu_|MEIZU )(.+?) *(?:Build|[;\\)])', + None, + 'Meizu $1', + 'Meizu', + '$1', + ), + DeviceParser( + '; *(?:Micromax[ _](A111|A240)|(A111|A240)) Build', + 'i', + 'Micromax $1$2', + 'Micromax', + '$1$2', + ), + DeviceParser( + '; *Micromax[ _](A\\d{2,3}[^;/]*) Build', + 'i', + 'Micromax $1', + 'Micromax', + '$1', + ), + DeviceParser( + '; *(A\\d{2}|A[12]\\d{2}|A90S|A110Q) Build', + 'i', + 'Micromax $1', + 'Micromax', + '$1', + ), + DeviceParser( + '; *Micromax[ _](P\\d{3}[^;/]*) Build', + 'i', + 'Micromax $1', + 'Micromax', + '$1', + ), + DeviceParser( + '; *(P\\d{3}|P\\d{3}\\(Funbook\\)) Build', + 'i', + 'Micromax $1', + 'Micromax', + '$1', + ), + DeviceParser( + '; *(MITO)[ _\\-]?([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'Mito', + '$2', + ), + DeviceParser( + '; *(Cynus)[ _](F5|T\\d|.+?) *(?:Build|[;/\\)])', + 'i', + '$1 $2', + 'Mobistel', + '$1 $2', + ), + DeviceParser( + '; *(MODECOM |)(FreeTab) ?([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1$2 $3', + 'Modecom', + '$2 $3', + ), + DeviceParser( + '; *(MODECOM )([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'Modecom', + '$2', + ), + DeviceParser( + '; *(MZ\\d{3}\\+?|MZ\\d{3} 4G|Xoom|XOOM[^;/]*) Build', + None, + 'Motorola $1', + 'Motorola', + '$1', + ), + DeviceParser( + '; *(Milestone )(XT[^;/]*) Build', + None, + 'Motorola $1$2', + 'Motorola', + '$2', + ), + DeviceParser( + '; *(Motoroi ?x|Droid X|DROIDX) Build', + 'i', + 'Motorola $1', + 'Motorola', + 'DROID X', + ), + DeviceParser( + '; *(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) Build', + None, + 'Motorola $1', + 'Motorola', + '$1', + ), + DeviceParser( + '; *(A555|A85[34][^;/]*|A95[356]|ME[58]\\d{2}\\+?|ME600|ME632|ME722|MB\\d{3}\\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\\d{3,4}[A-Z\\+]*|CL[iI]Q|CL[iI]Q XT) Build', + None, + '$1', + 'Motorola', + '$1', + ), + DeviceParser( + '; *(Motorola MOT-|Motorola[ _\\-]|MOT\\-?)([^;/]+) Build', + None, + '$1$2', + 'Motorola', + '$2', + ), + DeviceParser( + '; *(Moto[_ ]?|MOT\\-)([^;/]+) Build', + None, + '$1$2', + 'Motorola', + '$2', + ), + DeviceParser( + '; *((?:MP[DQ]C|MPG\\d{1,4}|MP\\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Mpman', + '$1', + ), + DeviceParser( + '; *(?:MSI[ _]|)(Primo\\d+|Enjoy[ _\\-][^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'Msi', + '$1', + ), + DeviceParser( + '; *Multilaser[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Multilaser', + '$1', + ), + DeviceParser( + '; *(My)[_]?(Pad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2 $3', + 'MyPhone', + '$1$2 $3', + ), + DeviceParser( + '; *(My)\\|?(Phone)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2 $3', + 'MyPhone', + '$3', + ), + DeviceParser( + '; *(A\\d+)[ _](Duo|)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'MyPhone', + '$1 $2', + ), + DeviceParser( + '; *(myTab[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Mytab', + '$1', + ), + DeviceParser( + '; *(NABI2?-)([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Nabi', + '$2', + ), + DeviceParser( + '; *(N-\\d+[CDE])(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Nec', + '$1', + ), + DeviceParser( + '; ?(NEC-)(.*?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Nec', + '$2', + ), + DeviceParser( + '; *(LT-NA7)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Nec', + 'Lifetouch Note', + ), + DeviceParser( + '; *(NXM\\d+[A-Za-z0-9_]*|Next\\d[A-Za-z0-9_ \\-]*|NEXT\\d[A-Za-z0-9_ \\-]*|Nextbook [A-Za-z0-9_ ]*|DATAM803HC|M805)(?: Build|[\\);])', + None, + '$1', + 'Nextbook', + '$1', + ), + DeviceParser( + '; *(Nokia)([ _\\-]*)([^;/]*) Build', + 'i', + '$1$2$3', + 'Nokia', + '$3', + ), + DeviceParser( + '; *(Nook ?|Barnes & Noble Nook |BN )([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Nook', + '$2', + ), + DeviceParser( + '; *(NOOK |)(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Nook', + '$2', + ), + DeviceParser( + '; Build/(Nook)', + None, + '$1', + 'Nook', + 'Tablet', + ), + DeviceParser( + '; *(OP110|OliPad[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Olivetti $1', + 'Olivetti', + '$1', + ), + DeviceParser( + '; *OMEGA[ _\\-](MID[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Omega $1', + 'Omega', + '$1', + ), + DeviceParser( + '^(MID7500|MID\\d+) Mozilla/5\\.0 \\(iPad;', + None, + 'Omega $1', + 'Omega', + '$1', + ), + DeviceParser( + '; *((?:CIUS|cius)[^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'Openpeak $1', + 'Openpeak', + '$1', + ), + DeviceParser( + '; *(Find ?(?:5|7a)|R8[012]\\d{1,2}|T703\\d?|U70\\d{1,2}T?|X90\\d{1,2})(?: Build|\\) AppleWebKit)', + None, + 'Oppo $1', + 'Oppo', + '$1', + ), + DeviceParser( + '; *OPPO ?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Oppo $1', + 'Oppo', + '$1', + ), + DeviceParser( + '; *(?:Odys\\-|ODYS\\-|ODYS )([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Odys $1', + 'Odys', + '$1', + ), + DeviceParser( + '; *(SELECT) ?(7)(?: Build|\\) AppleWebKit)', + None, + 'Odys $1 $2', + 'Odys', + '$1 $2', + ), + DeviceParser( + '; *(PEDI)_(PLUS)_(W)(?: Build|\\) AppleWebKit)', + None, + 'Odys $1 $2 $3', + 'Odys', + '$1 $2 $3', + ), + DeviceParser( + '; *(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio ?\\d+ ?[Pp]ro|XENO10|XPRESS PRO)(?: Build|\\) AppleWebKit)', + None, + 'Odys $1', + 'Odys', + '$1', + ), + DeviceParser( + '; (ONE [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)', + None, + 'OnePlus $1', + 'OnePlus', + '$1', + ), + DeviceParser( + '; (ONEPLUS [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)', + None, + 'OnePlus $1', + 'OnePlus', + '$1', + ), + DeviceParser( + '; *(TP-\\d+)(?: Build|\\) AppleWebKit)', + None, + 'Orion $1', + 'Orion', + '$1', + ), + DeviceParser( + '; *(G100W?)(?: Build|\\) AppleWebKit)', + None, + 'PackardBell $1', + 'PackardBell', + '$1', + ), + DeviceParser( + '; *(Panasonic)[_ ]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(FZ-A1B|JT-B1)(?: Build|\\) AppleWebKit)', + None, + 'Panasonic $1', + 'Panasonic', + '$1', + ), + DeviceParser( + '; *(dL1|DL1)(?: Build|\\) AppleWebKit)', + None, + 'Panasonic $1', + 'Panasonic', + '$1', + ), + DeviceParser( + '; *(SKY[ _]|)(IM\\-[AT]\\d{3}[^;/]+).* Build/', + None, + 'Pantech $1$2', + 'Pantech', + '$1$2', + ), + DeviceParser( + '; *((?:ADR8995|ADR910L|ADR930L|ADR930VW|PTL21|P8000)(?: 4G|)) Build/', + None, + '$1', + 'Pantech', + '$1', + ), + DeviceParser( + '; *Pantech([^;/]+).* Build/', + None, + 'Pantech $1', + 'Pantech', + '$1', + ), + DeviceParser( + '; *(papyre)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1 $2', + 'Papyre', + '$2', + ), + DeviceParser( + '; *(?:Touchlet )?(X10\\.[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Pearl $1', + 'Pearl', + '$1', + ), + DeviceParser( + '; PHICOMM (i800)(?: Build|\\) AppleWebKit)', + None, + 'Phicomm $1', + 'Phicomm', + '$1', + ), + DeviceParser( + '; PHICOMM ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Phicomm $1', + 'Phicomm', + '$1', + ), + DeviceParser( + '; *(FWS\\d{3}[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Phicomm $1', + 'Phicomm', + '$1', + ), + DeviceParser( + '; *(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Philips', + '$1', + ), + DeviceParser( + '; *(?:Philips|PHILIPS)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Philips $1', + 'Philips', + '$1', + ), + DeviceParser( + 'Android 4\\..*; *(M[12356789]|U[12368]|S[123])\\ ?(pro)?(?: Build|\\) AppleWebKit)', + None, + 'Pipo $1$2', + 'Pipo', + '$1$2', + ), + DeviceParser( + '; *(MOMO[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Ployer', + '$1', + ), + DeviceParser( + '; *(?:Polaroid[ _]|)((?:MIDC\\d{3,}|PMID\\d{2,}|PTAB\\d{3,})[^;/]*?)(\\/[^;/]*|)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Polaroid', + '$1', + ), + DeviceParser( + '; *(?:Polaroid )(Tablet)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Polaroid', + '$1', + ), + DeviceParser( + '; *(POMP)[ _\\-](.+?) *(?:Build|[;/\\)])', + None, + '$1 $2', + 'Pomp', + '$2', + ), + DeviceParser( + '; *(TB07STA|TB10STA|TB07FTA|TB10FTA)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Positivo', + '$1', + ), + DeviceParser( + '; *(?:Positivo |)((?:YPY|Ypy)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Positivo', + '$1', + ), + DeviceParser( + '; *(MOB-[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'POV', + '$1', + ), + DeviceParser( + '; *POV[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'POV $1', + 'POV', + '$1', + ), + DeviceParser( + '; *((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\\-]|TAB-P)[^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'POV $1', + 'POV', + '$1', + ), + DeviceParser( + '; *(?:Prestigio |)((?:PAP|PMP)\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Prestigio $1', + 'Prestigio', + '$1', + ), + DeviceParser( + '; *(PLT[0-9]{4}.*?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Proscan', + '$1', + ), + DeviceParser( + '; *(A2|A5|A8|A900)_?(Classic|)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Qmobile', + '$1 $2', + ), + DeviceParser( + '; *(Q[Mm]obile)_([^_]+)_([^_]+?)(?: Build|\\) AppleWebKit)', + None, + 'Qmobile $2 $3', + 'Qmobile', + '$2 $3', + ), + DeviceParser( + '; *(Q\\-?[Mm]obile)[_ ](A[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Qmobile $2', + 'Qmobile', + '$2', + ), + DeviceParser( + '; *(Q\\-Smart)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Qmobilevn', + '$2', + ), + DeviceParser( + '; *(Q\\-?[Mm]obile)[ _\\-](S[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Qmobilevn', + '$2', + ), + DeviceParser( + '; *(TA1013)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Quanta', + '$1', + ), + DeviceParser( + '; (RCT\\w+)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'RCA', + '$1', + ), + DeviceParser( + '; RCA (\\w+)(?: Build|\\) AppleWebKit)', + None, + 'RCA $1', + 'RCA', + '$1', + ), + DeviceParser( + '; *(RK\\d+),?(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Rockchip', + '$1', + ), + DeviceParser( + ' Build/(RK\\d+)', + None, + '$1', + 'Rockchip', + '$1', + ), + DeviceParser( + '; *(SAMSUNG |Samsung |)((?:Galaxy (?:Note II|S\\d)|GT-I9082|GT-I9205|GT-N7\\d{3}|SM-N9005)[^;/]*)\\/?[^;/]* Build/', + None, + 'Samsung $1$2', + 'Samsung', + '$2', + ), + DeviceParser( + '; *(Google |)(Nexus [Ss](?: 4G|)) Build/', + None, + 'Samsung $1$2', + 'Samsung', + '$2', + ), + DeviceParser( + '; *(SAMSUNG |Samsung )([^\\/]*)\\/[^ ]* Build/', + None, + 'Samsung $2', + 'Samsung', + '$2', + ), + DeviceParser( + '; *(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus 4G|)) Build/', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *(SAMSUNG[ _\\-]|)(?:SAMSUNG[ _\\-])([^;/]+) Build', + None, + 'Samsung $2', + 'Samsung', + '$2', + ), + DeviceParser( + '; *(SAMSUNG-|)(GT\\-[BINPS]\\d{4}[^\\/]*)(\\/[^ ]*) Build', + None, + 'Samsung $1$2$3', + 'Samsung', + '$2', + ), + DeviceParser( + '(?:; *|^)((?:GT\\-[BIiNPS]\\d{4}|I9\\d{2}0[A-Za-z\\+]?\\b)[^;/\\)]*?)(?:Build|Linux|MIUI|[;/\\)])', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; (SAMSUNG-)([A-Za-z0-9\\-]+).* Build/', + None, + 'Samsung $1$2', + 'Samsung', + '$2', + ), + DeviceParser( + '; *((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9 ]+)(/?[^ ]*|) Build', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *((?:SC)\\-[A-Za-z0-9 ]+)(/?[^ ]*|)\\)', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + ' ((?:SCH)\\-[A-Za-z0-9 ]+)(/?[^ ]*|) Build', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *(Behold ?(?:2|II)|YP\\-G[^;/]+|EK-GC100|SCL21|I9300) Build', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9]{5,6})[\\)]', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '; *(SH\\-?\\d\\d[^;/]+|SBM\\d[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sharp', + '$1', + ), + DeviceParser( + '; *(SHARP[ -])([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Sharp', + '$2', + ), + DeviceParser( + '; *(SPX[_\\-]\\d[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Simvalley', + '$1', + ), + DeviceParser( + '; *(SX7\\-PEARL\\.GmbH)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Simvalley', + '$1', + ), + DeviceParser( + '; *(SP[T]?\\-\\d{2}[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Simvalley', + '$1', + ), + DeviceParser( + '; *(SK\\-.*?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'SKtelesys', + '$1', + ), + DeviceParser( + '; *(?:SKYTEX|SX)-([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Skytex', + '$1', + ), + DeviceParser( + '; *(IMAGINE [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Skytex', + '$1', + ), + DeviceParser( + '; *(SmartQ) ?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(WF7C|WF10C|SBT[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Smartbitt', + '$1', + ), + DeviceParser( + '; *(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build', + None, + '$1', + 'Sharp', + '$1', + ), + DeviceParser( + '; *(003P|101P|101P11C|102P) Build', + None, + '$1', + 'Panasonic', + '$1', + ), + DeviceParser( + '; *(00\\dZ) Build/', + None, + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; HTC(X06HT) Build', + None, + '$1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(001HT|X06HT) Build', + None, + '$1', + 'HTC', + '$1', + ), + DeviceParser( + '; *(201M) Build', + None, + '$1', + 'Motorola', + 'XT902', + ), + DeviceParser( + '; *(ST\\d{4}.*)Build/ST', + None, + 'Trekstor $1', + 'Trekstor', + '$1', + ), + DeviceParser( + '; *(ST\\d{4}.*?)(?: Build|\\) AppleWebKit)', + None, + 'Trekstor $1', + 'Trekstor', + '$1', + ), + DeviceParser( + '; *(Sony ?Ericsson ?)([^;/]+) Build', + None, + '$1$2', + 'SonyEricsson', + '$2', + ), + DeviceParser( + '; *((?:SK|ST|E|X|LT|MK|MT|WT)\\d{2}[a-z0-9]*(?:-o|)|R800i|U20i) Build', + None, + '$1', + 'SonyEricsson', + '$1', + ), + DeviceParser( + '; *(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\\d+)[^;/]*) Build', + 'i', + '$1', + 'SonyEricsson', + '$1', + ), + DeviceParser( + '; Sony (Tablet[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Sony $1', + 'Sony', + '$1', + ), + DeviceParser( + '; Sony ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Sony $1', + 'Sony', + '$1', + ), + DeviceParser( + '; *(Sony)([A-Za-z0-9\\-]+)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(Xperia [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sony', + '$1', + ), + DeviceParser( + '; *(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\\d{3}|D6[56]\\d{2})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sony', + '$1', + ), + DeviceParser( + '; *(SGP\\d{3}|SGPT\\d{2})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sony', + '$1', + ), + DeviceParser( + '; *(NW-Z1000Series)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Sony', + '$1', + ), + DeviceParser( + 'PLAYSTATION 3', + None, + 'PlayStation 3', + 'Sony', + 'PlayStation 3', + ), + DeviceParser( + '(PlayStation (?:Portable|Vita|\\d+))', + None, + '$1', + 'Sony', + '$1', + ), + DeviceParser( + '; *((?:CSL_Spice|Spice|SPICE|CSL)[ _\\-]?|)([Mm][Ii])([ _\\-]|)(\\d{3}[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1$2$3$4', + 'Spice', + 'Mi$4', + ), + DeviceParser( + '; *(Sprint )(.+?) *(?:Build|[;/])', + None, + '$1$2', + 'Sprint', + '$2', + ), + DeviceParser( + '\\b(Sprint)[: ]([^;,/ ]+)', + None, + '$1$2', + 'Sprint', + '$2', + ), + DeviceParser( + '; *(TAGI[ ]?)(MID) ?([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2$3', + 'Tagi', + '$2$3', + ), + DeviceParser( + '; *(Oyster500|Opal 800)(?: Build|\\) AppleWebKit)', + None, + 'Tecmobile $1', + 'Tecmobile', + '$1', + ), + DeviceParser( + '; *(TECNO[ _])([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Tecno', + '$2', + ), + DeviceParser( + '; *Android for (Telechips|Techvision) ([^ ]+) ', + 'i', + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(T-Hub2)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Telstra', + '$1', + ), + DeviceParser( + '; *(PAD) ?(100[12])(?: Build|\\) AppleWebKit)', + None, + 'Terra $1$2', + 'Terra', + '$1$2', + ), + DeviceParser( + '; *(T[BM]-\\d{3}[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Texet', + '$1', + ), + DeviceParser( + '; *(tolino [^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Thalia', + '$1', + ), + DeviceParser( + '; *Build/.* (TOLINO_BROWSER)', + None, + '$1', + 'Thalia', + 'Tolino Shine', + ), + DeviceParser( + '; *(?:CJ[ -])?(ThL|THL)[ -]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Thl', + '$2', + ), + DeviceParser( + '; *(T100|T200|T5|W100|W200|W8s)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Thl', + '$1', + ), + DeviceParser( + '; *(T-Mobile[ _]G2[ _]Touch) Build', + None, + '$1', + 'HTC', + 'Hero', + ), + DeviceParser( + '; *(T-Mobile[ _]G2) Build', + None, + '$1', + 'HTC', + 'Desire Z', + ), + DeviceParser( + '; *(T-Mobile myTouch Q) Build', + None, + '$1', + 'Huawei', + 'U8730', + ), + DeviceParser( + '; *(T-Mobile myTouch) Build', + None, + '$1', + 'Huawei', + 'U8680', + ), + DeviceParser( + '; *(T-Mobile_Espresso) Build', + None, + '$1', + 'HTC', + 'Espresso', + ), + DeviceParser( + '; *(T-Mobile G1) Build', + None, + '$1', + 'HTC', + 'Dream', + ), + DeviceParser( + '\\b(T-Mobile ?|)(myTouch)[ _]?([34]G)[ _]?([^\\/]*) (?:Mozilla|Build)', + None, + '$1$2 $3 $4', + 'HTC', + '$2 $3 $4', + ), + DeviceParser( + '\\b(T-Mobile)_([^_]+)_(.*) Build', + None, + '$1 $2 $3', + 'Tmobile', + '$2 $3', + ), + DeviceParser( + '\\b(T-Mobile)[_ ]?(.*?)Build', + None, + '$1 $2', + 'Tmobile', + '$2', + ), + DeviceParser( + ' (ATP[0-9]{4})(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Tomtec', + '$1', + ), + DeviceParser( + ' *(TOOKY)[ _\\-]([^;/]+?) ?(?:Build|;)', + 'i', + '$1 $2', + 'Tooky', + '$2', + ), + DeviceParser( + '\\b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A)', + None, + '$1', + 'Toshiba', + 'Folio 100', + ), + DeviceParser( + '; *([Ff]olio ?100)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Toshiba', + 'Folio 100', + ), + DeviceParser( + '; *(AT[0-9]{2,3}(?:\\-A|LE\\-A|PE\\-A|SE|a|)|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]+|THRiVE|Thrive)(?: Build|\\) AppleWebKit)', + None, + 'Toshiba $1', + 'Toshiba', + '$1', + ), + DeviceParser( + '; *(TM-MID\\d+[^;/]+|TOUCHMATE|MID-750)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Touchmate', + '$1', + ), + DeviceParser( + '; *(TM-SM\\d+[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Touchmate', + '$1', + ), + DeviceParser( + '; *(A10 [Bb]asic2?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Treq', + '$1', + ), + DeviceParser( + '; *(TREQ[ _\\-])([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + '$1$2', + 'Treq', + '$2', + ), + DeviceParser( + '; *(X-?5|X-?3)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Umeox', + '$1', + ), + DeviceParser( + '; *(A502\\+?|A936|A603|X1|X2)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Umeox', + '$1', + ), + DeviceParser( + '(TOUCH(?:TAB|PAD).+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Versus $1', + 'Versus', + '$1', + ), + DeviceParser( + '(VERTU) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'Vertu', + '$2', + ), + DeviceParser( + '; *(Videocon)[ _\\-]([^;/]+?) *(?:Build|;)', + None, + '$1 $2', + 'Videocon', + '$2', + ), + DeviceParser( + ' (VT\\d{2}[A-Za-z]*)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Videocon', + '$1', + ), + DeviceParser( + '; *((?:ViewPad|ViewPhone|VSD)[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Viewsonic', + '$1', + ), + DeviceParser( + '; *(ViewSonic-)([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'Viewsonic', + '$2', + ), + DeviceParser( + '; *(GTablet.*?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Viewsonic', + '$1', + ), + DeviceParser( + '; *([Vv]ivo)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'vivo', + '$2', + ), + DeviceParser( + '(Vodafone) (.*?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(?:Walton[ _\\-]|)(Primo[ _\\-][^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Walton $1', + 'Walton', + '$1', + ), + DeviceParser( + '; *(?:WIKO[ \\-]|)(CINK\\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK [^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Wiko $1', + 'Wiko', + '$1', + ), + DeviceParser( + '; *WellcoM-([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Wellcom $1', + 'Wellcom', + '$1', + ), + DeviceParser( + '(?:(WeTab)-Browser|; (wetab) Build)', + None, + '$1', + 'WeTab', + 'WeTab', + ), + DeviceParser( + '; *(AT-AS[^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Wolfgang $1', + 'Wolfgang', + '$1', + ), + DeviceParser( + '; *(?:Woxter|Wxt) ([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'Woxter $1', + 'Woxter', + '$1', + ), + DeviceParser( + '; *(?:Xenta |Luna |)(TAB[234][0-9]{2}|TAB0[78]-\\d{3}|TAB0?9-\\d{3}|TAB1[03]-\\d{3}|SMP\\d{2}-\\d{3})(?: Build|\\) AppleWebKit)', + None, + 'Yarvik $1', + 'Yarvik', + '$1', + ), + DeviceParser( + '; *([A-Z]{2,4})(M\\d{3,}[A-Z]{2})([^;\\)\\/]*)(?: Build|[;\\)])', + None, + 'Yifang $1$2$3', + 'Yifang', + '$2', + ), + DeviceParser( + '; *((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/]*) (Build|MIUI)/', + None, + 'XiaoMi $1', + 'XiaoMi', + '$1', + ), + DeviceParser( + '; *((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/\\)]*)', + None, + 'XiaoMi $1', + 'XiaoMi', + '$1', + ), + DeviceParser( + '; *(MIX) (Build|MIUI)/', + None, + 'XiaoMi $1', + 'XiaoMi', + '$1', + ), + DeviceParser( + '; *((MIX) ([^;/]*)) (Build|MIUI)/', + None, + 'XiaoMi $1', + 'XiaoMi', + '$1', + ), + DeviceParser( + '; *XOLO[ _]([^;/]*tab.*)(?: Build|\\) AppleWebKit)', + 'i', + 'Xolo $1', + 'Xolo', + '$1', + ), + DeviceParser( + '; *XOLO[ _]([^;/]+?)(?: Build|\\) AppleWebKit)', + 'i', + 'Xolo $1', + 'Xolo', + '$1', + ), + DeviceParser( + '; *(q\\d0{2,3}[a-z]?)(?: Build|\\) AppleWebKit)', + 'i', + 'Xolo $1', + 'Xolo', + '$1', + ), + DeviceParser( + '; *(PAD ?[79]\\d+[^;/]*|TelePAD\\d+[^;/])(?: Build|\\) AppleWebKit)', + None, + 'Xoro $1', + 'Xoro', + '$1', + ), + DeviceParser( + '; *(?:(?:ZOPO|Zopo)[ _]([^;/]+?)|(ZP ?(?:\\d{2}[^;/]+|C2))|(C[2379]))(?: Build|\\) AppleWebKit)', + None, + '$1$2$3', + 'Zopo', + '$1$2$3', + ), + DeviceParser( + '; *(ZiiLABS) (Zii[^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'ZiiLabs', + '$2', + ), + DeviceParser( + '; *(Zii)_([^;/]*)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'ZiiLabs', + '$2', + ), + DeviceParser( + '; *(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]*?|Era|Memo[^;]*?)|JOE|(?:Kis|KIS)\\b[^;]*?|Libra|Light [^;]*?|N8[056][01]|N850L|N8000|N9[15]\\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]*?|RacerII|RACERII|San Francisco[^;]*?|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; *([A-Z]\\d+)_USA_[^;]*(?: Build|\\) AppleWebKit)', + None, + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; *(SmartTab\\d+)[^;]*(?: Build|\\) AppleWebKit)', + None, + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; *(?:Blade|BLADE|ZTE-BLADE)([^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'ZTE Blade$1', + 'ZTE', + 'Blade$1', + ), + DeviceParser( + '; *(?:Skate|SKATE|ZTE-SKATE)([^;/]*)(?: Build|\\) AppleWebKit)', + None, + 'ZTE Skate$1', + 'ZTE', + 'Skate$1', + ), + DeviceParser( + '; *(Orange |Optimus )(Monte Carlo|San Francisco)(?: Build|\\) AppleWebKit)', + None, + '$1$2', + 'ZTE', + '$1$2', + ), + DeviceParser( + '; *(?:ZXY-ZTE_|ZTE\\-U |ZTE[\\- _]|ZTE-C[_ ])([^;/]+?)(?: Build|\\) AppleWebKit)', + None, + 'ZTE $1', + 'ZTE', + '$1', + ), + DeviceParser( + '; (BASE) (lutea|Lutea 2|Tab[^;]*?)(?: Build|\\) AppleWebKit)', + None, + '$1 $2', + 'ZTE', + '$1 $2', + ), + DeviceParser( + '; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link)(?: Build|\\) AppleWebKit)', + 'i', + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; *(vp9plus)\\)', + None, + '$1', + 'ZTE', + '$1', + ), + DeviceParser( + '; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900)(?: Build|\\) AppleWebKit)', + None, + '$1', + 'Zync', + '$1', + ), + DeviceParser( + '; ?(KFOT|Kindle Fire) Build\\b', + None, + 'Kindle Fire', + 'Amazon', + 'Kindle Fire', + ), + DeviceParser( + '; ?(KFOTE|Amazon Kindle Fire2) Build\\b', + None, + 'Kindle Fire 2', + 'Amazon', + 'Kindle Fire 2', + ), + DeviceParser( + '; ?(KFTT) Build\\b', + None, + 'Kindle Fire HD', + 'Amazon', + 'Kindle Fire HD 7"', + ), + DeviceParser( + '; ?(KFJWI) Build\\b', + None, + 'Kindle Fire HD 8.9" WiFi', + 'Amazon', + 'Kindle Fire HD 8.9" WiFi', + ), + DeviceParser( + '; ?(KFJWA) Build\\b', + None, + 'Kindle Fire HD 8.9" 4G', + 'Amazon', + 'Kindle Fire HD 8.9" 4G', + ), + DeviceParser( + '; ?(KFSOWI) Build\\b', + None, + 'Kindle Fire HD 7" WiFi', + 'Amazon', + 'Kindle Fire HD 7" WiFi', + ), + DeviceParser( + '; ?(KFTHWI) Build\\b', + None, + 'Kindle Fire HDX 7" WiFi', + 'Amazon', + 'Kindle Fire HDX 7" WiFi', + ), + DeviceParser( + '; ?(KFTHWA) Build\\b', + None, + 'Kindle Fire HDX 7" 4G', + 'Amazon', + 'Kindle Fire HDX 7" 4G', + ), + DeviceParser( + '; ?(KFAPWI) Build\\b', + None, + 'Kindle Fire HDX 8.9" WiFi', + 'Amazon', + 'Kindle Fire HDX 8.9" WiFi', + ), + DeviceParser( + '; ?(KFAPWA) Build\\b', + None, + 'Kindle Fire HDX 8.9" 4G', + 'Amazon', + 'Kindle Fire HDX 8.9" 4G', + ), + DeviceParser( + '; ?Amazon ([^;/]+) Build\\b', + None, + '$1', + 'Amazon', + '$1', + ), + DeviceParser( + '; ?(Kindle) Build\\b', + None, + 'Kindle', + 'Amazon', + 'Kindle', + ), + DeviceParser( + '; ?(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|) Build\\b', + None, + 'Kindle Fire', + 'Amazon', + 'Kindle Fire$2', + ), + DeviceParser( + ' (Kindle)/(\\d+\\.\\d+)', + None, + 'Kindle', + 'Amazon', + '$1 $2', + ), + DeviceParser( + ' (Silk|Kindle)/(\\d+)\\.', + None, + 'Kindle', + 'Amazon', + 'Kindle', + ), + DeviceParser( + '(sprd)\\-([^/]+)/', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '; *(H\\d{2}00\\+?) Build', + None, + '$1', + 'Hero', + '$1', + ), + DeviceParser( + '; *(iphone|iPhone5) Build/', + None, + 'Xianghe $1', + 'Xianghe', + '$1', + ), + DeviceParser( + '; *(e\\d{4}[a-z]?_?v\\d+|v89_[^;/]+)[^;/]+ Build/', + None, + 'Xianghe $1', + 'Xianghe', + '$1', + ), + DeviceParser( + '\\bUSCC[_\\-]?([^ ;/\\)]+)', + None, + '$1', + 'Cellular', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:ALCATEL)[^;]*; *([^;,\\)]+)', + None, + 'Alcatel $1', + 'Alcatel', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:ASUS|Asus)[^;]*; *([^;,\\)]+)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:DELL|Dell)[^;]*; *([^;,\\)]+)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:HTC|Htc|HTC_blocked[^;]*)[^;]*; *(?:HTC|)([^;,\\)]+)', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:HUAWEI)[^;]*; *(?:HUAWEI |)([^;,\\)]+)', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:LG|Lg)[^;]*; *(?:LG[ \\-]|)([^;,\\)]+)', + None, + 'LG $1', + 'LG', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:rv:11; |)(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)(\\d{3,10}[^;\\)]*)', + None, + 'Lumia $1', + 'Nokia', + 'Lumia $1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]*; *(RM-\\d{3,})', + None, + 'Nokia $1', + 'Nokia', + '$1', + ), + DeviceParser( + '(?:Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)]|WPDesktop;) ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)([^;\\)]+)', + None, + 'Nokia $1', + 'Nokia', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:Microsoft(?: Corporation|))[^;]*; *([^;,\\)]+)', + None, + 'Microsoft $1', + 'Microsoft', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:SAMSUNG)[^;]*; *(?:SAMSUNG |)([^;,\\.\\)]+)', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]*; *([^;,\\)]+)', + None, + 'Toshiba $1', + 'Toshiba', + '$1', + ), + DeviceParser( + 'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)([^;]+); *([^;,\\)]+)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '(?:^|; )SAMSUNG\\-([A-Za-z0-9\\-]+).* Bada/', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '\\(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]+?)(?:/[^;]+|); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/', + None, + 'Alcatel $1 $2 $3', + 'Alcatel', + 'One Touch $3', + ), + DeviceParser( + '\\(Mobile; (?:ZTE([^;]+)|(OpenC)); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/', + None, + 'ZTE $1$2', + 'ZTE', + '$1$2', + ), + DeviceParser( + '\\(Mobile; ALCATEL([A-Za-z0-9\\-]+); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KaiOS/', + None, + 'Alcatel $1', + 'Alcatel', + '$1', + ), + DeviceParser( + '\\(Mobile; LYF\\/([A-Za-z0-9\\-]+)\\/.+;.+rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KAIOS/', + None, + 'LYF $1', + 'LYF', + '$1', + ), + DeviceParser( + '\\(Mobile; Nokia_([A-Za-z0-9\\-]+)_.+; rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KAIOS/', + None, + 'Nokia $1', + 'Nokia', + '$1', + ), + DeviceParser( + 'Nokia(N[0-9]+)([A-Za-z_\\-][A-Za-z0-9_\\-]*)', + None, + 'Nokia $1', + 'Nokia', + '$1$2', + ), + DeviceParser( + '(?:NOKIA|Nokia)(?:\\-| *)(?:([A-Za-z0-9]+)\\-[0-9a-f]{32}|([A-Za-z0-9\\-]+)(?:UCBrowser)|([A-Za-z0-9\\-]+))', + None, + 'Nokia $1$2$3', + 'Nokia', + '$1$2$3', + ), + DeviceParser( + 'Lumia ([A-Za-z0-9\\-]+)', + None, + 'Lumia $1', + 'Nokia', + 'Lumia $1', + ), + DeviceParser( + '\\(Symbian; U; S60 V5; [A-z]{2}\\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]+?)\\)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '\\(Symbian(?:/3|); U; ([^;]+);', + None, + 'Nokia $1', + 'Nokia', + '$1', + ), + DeviceParser( + 'BB10; ([A-Za-z0-9\\- ]+)\\)', + None, + 'BlackBerry $1', + 'BlackBerry', + '$1', + ), + DeviceParser( + 'Play[Bb]ook.+RIM Tablet OS', + None, + 'BlackBerry Playbook', + 'BlackBerry', + 'Playbook', + ), + DeviceParser( + 'Black[Bb]erry ([0-9]+);', + None, + 'BlackBerry $1', + 'BlackBerry', + '$1', + ), + DeviceParser( + 'Black[Bb]erry([0-9]+)', + None, + 'BlackBerry $1', + 'BlackBerry', + '$1', + ), + DeviceParser( + 'Black[Bb]erry;', + None, + 'BlackBerry', + 'BlackBerry', + None, + ), + DeviceParser( + '(Pre|Pixi)/\\d+\\.\\d+', + None, + 'Palm $1', + 'Palm', + '$1', + ), + DeviceParser( + 'Palm([0-9]+)', + None, + 'Palm $1', + 'Palm', + '$1', + ), + DeviceParser( + 'Treo([A-Za-z0-9]+)', + None, + 'Palm Treo $1', + 'Palm', + 'Treo $1', + ), + DeviceParser( + 'webOS.*(P160U(?:NA|))/(\\d+).(\\d+)', + None, + 'HP Veer', + 'HP', + 'Veer', + ), + DeviceParser( + '(Touch[Pp]ad)/\\d+\\.\\d+', + None, + 'HP TouchPad', + 'HP', + 'TouchPad', + ), + DeviceParser( + 'HPiPAQ([A-Za-z0-9]+)/\\d+.\\d+', + None, + 'HP iPAQ $1', + 'HP', + 'iPAQ $1', + ), + DeviceParser( + 'PDA; (PalmOS)/sony/model ([a-z]+)/Revision', + None, + '$1', + 'Sony', + '$1 $2', + ), + DeviceParser( + '(Apple\\s?TV)', + None, + 'AppleTV', + 'Apple', + 'AppleTV', + ), + DeviceParser( + '(QtCarBrowser)', + None, + 'Tesla Model S', + 'Tesla', + 'Model S', + ), + DeviceParser( + '(iPhone|iPad|iPod)(\\d+,\\d+)', + None, + '$1', + 'Apple', + '$1$2', + ), + DeviceParser( + '(iPad)(?:;| Simulator;)', + None, + '$1', + 'Apple', + '$1', + ), + DeviceParser( + '(iPod)(?:;| touch;| Simulator;)', + None, + '$1', + 'Apple', + '$1', + ), + DeviceParser( + '(iPhone)(?:;| Simulator;)', + None, + '$1', + 'Apple', + '$1', + ), + DeviceParser( + '(Watch)(\\d+,\\d+)', + None, + 'Apple $1', + 'Apple', + '$1$2', + ), + DeviceParser( + '(Apple Watch)(?:;| Simulator;)', + None, + '$1', + 'Apple', + '$1', + ), + DeviceParser( + '(HomePod)(?:;| Simulator;)', + None, + '$1', + 'Apple', + '$1', + ), + DeviceParser( + 'iPhone', + None, + 'iPhone', + 'Apple', + 'iPhone', + ), + DeviceParser( + 'CFNetwork/.* Darwin/\\d.*\\(((?:Mac|iMac|PowerMac|PowerBook)[^\\d]*)(\\d+)(?:,|%2C)(\\d+)', + None, + '$1$2,$3', + 'Apple', + '$1$2,$3', + ), + DeviceParser( + 'CFNetwork/.* Darwin/\\d+\\.\\d+\\.\\d+ \\(x86_64\\)', + None, + 'Mac', + 'Apple', + 'Mac', + ), + DeviceParser( + 'CFNetwork/.* Darwin/\\d', + None, + 'iOS-Device', + 'Apple', + 'iOS-Device', + ), + DeviceParser( + 'Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone', + None, + 'iPhone', + 'Apple', + 'iPhone', + ), + DeviceParser( + 'acer_([A-Za-z0-9]+)_', + None, + 'Acer $1', + 'Acer', + '$1', + ), + DeviceParser( + '(?:ALCATEL|Alcatel)-([A-Za-z0-9\\-]+)', + None, + 'Alcatel $1', + 'Alcatel', + '$1', + ), + DeviceParser( + '(?:Amoi|AMOI)\\-([A-Za-z0-9]+)', + None, + 'Amoi $1', + 'Amoi', + '$1', + ), + DeviceParser( + '(?:; |\\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + '(?:asus.*?ASUS|Asus|ASUS|asus)[\\- ;]*((?:Transformer (?:Pad|Prime) |Transformer |Padfone |Nexus[ _]|)[A-Za-z0-9]+)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + '(?:ASUS)_([A-Za-z0-9\\-]+)', + None, + 'Asus $1', + 'Asus', + '$1', + ), + DeviceParser( + '\\bBIRD[ \\-\\.]([A-Za-z0-9]+)', + None, + 'Bird $1', + 'Bird', + '$1', + ), + DeviceParser( + '\\bDell ([A-Za-z0-9]+)', + None, + 'Dell $1', + 'Dell', + '$1', + ), + DeviceParser( + 'DoCoMo/2\\.0 ([A-Za-z0-9]+)', + None, + 'DoCoMo $1', + 'DoCoMo', + '$1', + ), + DeviceParser( + '([A-Za-z0-9]+)_W;FOMA', + None, + 'DoCoMo $1', + 'DoCoMo', + '$1', + ), + DeviceParser( + '([A-Za-z0-9]+);FOMA', + None, + 'DoCoMo $1', + 'DoCoMo', + '$1', + ), + DeviceParser( + '\\b(?:HTC/|HTC/[a-z0-9]+/|)HTC[ _\\-;]? *(.*?)(?:-?Mozilla|fingerPrint|[;/\\(\\)]|$)', + None, + 'HTC $1', + 'HTC', + '$1', + ), + DeviceParser( + 'Huawei([A-Za-z0-9]+)', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + 'HUAWEI-([A-Za-z0-9]+)', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + 'HUAWEI ([A-Za-z0-9\\-]+)', + None, + 'Huawei $1', + 'Huawei', + '$1', + ), + DeviceParser( + 'vodafone([A-Za-z0-9]+)', + None, + 'Huawei Vodafone $1', + 'Huawei', + 'Vodafone $1', + ), + DeviceParser( + 'i\\-mate ([A-Za-z0-9]+)', + None, + 'i-mate $1', + 'i-mate', + '$1', + ), + DeviceParser( + 'Kyocera\\-([A-Za-z0-9]+)', + None, + 'Kyocera $1', + 'Kyocera', + '$1', + ), + DeviceParser( + 'KWC\\-([A-Za-z0-9]+)', + None, + 'Kyocera $1', + 'Kyocera', + '$1', + ), + DeviceParser( + 'Lenovo[_\\-]([A-Za-z0-9]+)', + None, + 'Lenovo $1', + 'Lenovo', + '$1', + ), + DeviceParser( + '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\( ?;(LG)E ?;([^;]{0,30})', + None, + '$1', + '$2', + '$3', + ), + DeviceParser( + '(HbbTV)/1\\.1\\.1.*CE-HTML/1\\.\\d;(Vendor/|)(THOM[^;]*?)[;\\s].{0,30}(LF[^;]+);?', + None, + '$1', + 'Thomson', + '$4', + ), + DeviceParser( + '(HbbTV)(?:/1\\.1\\.1|) ?(?: \\(;;;;;\\)|); *CE-HTML(?:/1\\.\\d|); *([^ ]+) ([^;]+);', + None, + '$1', + '$2', + '$3', + ), + DeviceParser( + '(HbbTV)/1\\.1\\.1 \\(;;;;;\\) Maple_2011', + None, + '$1', + 'Samsung', + None, + ), + DeviceParser( + '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\([^;]{0,30}; ?(?:CUS:([^;]*)|([^;]+)) ?; ?([^;]{0,30})', + None, + '$1', + '$2$3', + '$4', + ), + DeviceParser( + '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+', + None, + '$1', + None, + None, + ), + DeviceParser( + 'LGE; (?:Media\\/|)([^;]*);[^;]*;[^;]*;?\\); "?LG NetCast(\\.TV|\\.Media|)-\\d+', + None, + 'NetCast$2', + 'LG', + '$1', + ), + DeviceParser( + 'InettvBrowser/[0-9]+\\.[0-9A-Z]+ \\([^;]*;(Sony)([^;]*);[^;]*;[^\\)]*\\)', + None, + 'Inettv', + '$1', + '$2', + ), + DeviceParser( + 'InettvBrowser/[0-9]+\\.[0-9A-Z]+ \\([^;]*;([^;]*);[^;]*;[^\\)]*\\)', + None, + 'Inettv', + 'Generic_Inettv', + '$1', + ), + DeviceParser( + '(?:InettvBrowser|TSBNetTV|NETTV|HBBTV)', + None, + 'Inettv', + 'Generic_Inettv', + None, + ), + DeviceParser( + 'Series60/\\d\\.\\d (LG)[\\-]?([A-Za-z0-9 \\-]+)', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + '\\b(?:LGE[ \\-]LG\\-(?:AX|)|LGE |LGE?-LG|LGE?[ \\-]|LG[ /\\-]|lg[\\-])([A-Za-z0-9]+)\\b', + None, + 'LG $1', + 'LG', + '$1', + ), + DeviceParser( + '(?:^LG[\\-]?|^LGE[\\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*)', + None, + 'LG $1', + 'LG', + '$1', + ), + DeviceParser( + '^LG([0-9]+[A-Za-z]*)', + None, + 'LG $1', + 'LG', + '$1', + ), + DeviceParser( + '(KIN\\.[^ ]+) (\\d+)\\.(\\d+)', + None, + 'Microsoft $1', + 'Microsoft', + '$1', + ), + DeviceParser( + '(?:MSIE|XBMC).*\\b(Xbox)\\b', + None, + '$1', + 'Microsoft', + '$1', + ), + DeviceParser( + '; ARM; Trident/6\\.0; Touch[\\);]', + None, + 'Microsoft Surface RT', + 'Microsoft', + 'Surface RT', + ), + DeviceParser( + 'Motorola\\-([A-Za-z0-9]+)', + None, + 'Motorola $1', + 'Motorola', + '$1', + ), + DeviceParser( + 'MOTO\\-([A-Za-z0-9]+)', + None, + 'Motorola $1', + 'Motorola', + '$1', + ), + DeviceParser( + 'MOT\\-([A-z0-9][A-z0-9\\-]*)', + None, + 'Motorola $1', + 'Motorola', + '$1', + ), + DeviceParser( + 'Nintendo WiiU', + None, + 'Nintendo Wii U', + 'Nintendo', + 'Wii U', + ), + DeviceParser( + 'Nintendo (DS|3DS|DSi|Wii);', + None, + 'Nintendo $1', + 'Nintendo', + '$1', + ), + DeviceParser( + '(?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\\-]+)', + None, + 'Pantech $1', + 'Pantech', + '$1', + ), + DeviceParser( + 'Philips([A-Za-z0-9]+)', + None, + 'Philips $1', + 'Philips', + '$1', + ), + DeviceParser( + 'Philips ([A-Za-z0-9]+)', + None, + 'Philips $1', + 'Philips', + '$1', + ), + DeviceParser( + '(SMART-TV); .* Tizen ', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + 'SymbianOS/9\\.\\d.* Samsung[/\\-]([A-Za-z0-9 \\-]+)', + None, + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '(Samsung)(SGH)(i[0-9]+)', + None, + '$1 $2$3', + '$1', + '$2-$3', + ), + DeviceParser( + 'SAMSUNG-ANDROID-MMS/([^;/]+)', + None, + '$1', + 'Samsung', + '$1', + ), + DeviceParser( + 'SAMSUNG(?:; |[ -/])([A-Za-z0-9\\-]+)', + 'i', + 'Samsung $1', + 'Samsung', + '$1', + ), + DeviceParser( + '(Dreamcast)', + None, + 'Sega $1', + 'Sega', + '$1', + ), + DeviceParser( + '^SIE-([A-Za-z0-9]+)', + None, + 'Siemens $1', + 'Siemens', + '$1', + ), + DeviceParser( + 'Softbank/[12]\\.0/([A-Za-z0-9]+)', + None, + 'Softbank $1', + 'Softbank', + '$1', + ), + DeviceParser( + 'SonyEricsson ?([A-Za-z0-9\\-]+)', + None, + 'Ericsson $1', + 'SonyEricsson', + '$1', + ), + DeviceParser( + 'Android [^;]+; ([^ ]+) (Sony)/', + None, + '$2 $1', + '$2', + '$1', + ), + DeviceParser( + '(Sony)(?:BDP\\/|\\/|)([^ /;\\)]+)[ /;\\)]', + None, + '$1 $2', + '$1', + '$2', + ), + DeviceParser( + 'Puffin/[\\d\\.]+IT', + None, + 'iPad', + 'Apple', + 'iPad', + ), + DeviceParser( + 'Puffin/[\\d\\.]+IP', + None, + 'iPhone', + 'Apple', + 'iPhone', + ), + DeviceParser( + 'Puffin/[\\d\\.]+AT', + None, + 'Generic Tablet', + 'Generic', + 'Tablet', + ), + DeviceParser( + 'Puffin/[\\d\\.]+AP', + None, + 'Generic Smartphone', + 'Generic', + 'Smartphone', + ), + DeviceParser( + 'Android[\\- ][\\d]+\\.[\\d]+; [A-Za-z]{2}\\-[A-Za-z]{0,2}; WOWMobile (.+)( Build[/ ]|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android[\\- ][\\d]+\\.[\\d]+\\-update1; [A-Za-z]{2}\\-[A-Za-z]{0,2} *; *(.+?)( Build[/ ]|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[A-Za-z]{2}[_\\-][A-Za-z]{0,2}\\-? *; *(.+?)( Build[/ ]|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[A-Za-z]{0,2}\\- *; *(.+?)( Build[/ ]|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[a-z]{0,2}[_\\-]?[A-Za-z]{0,2};?( Build[/ ]|\\))', + None, + 'Generic Smartphone', + 'Generic', + 'Smartphone', + ), + DeviceParser( + 'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *\\-?[A-Za-z]{2}; *(.+?)( Build[/ ]|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\) AppleWebKit).+? Mobile Safari', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + 'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\) AppleWebKit).+? Safari', + None, + None, + 'Generic_Android_Tablet', + '$1', + ), + DeviceParser( + 'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\))', + None, + None, + 'Generic_Android', + '$1', + ), + DeviceParser( + '(GoogleTV)', + None, + None, + 'Generic_Inettv', + '$1', + ), + DeviceParser( + '(WebTV)/\\d+.\\d+', + None, + None, + 'Generic_Inettv', + '$1', + ), + DeviceParser( + '^(Roku)/DVP-\\d+\\.\\d+', + None, + None, + 'Generic_Inettv', + '$1', + ), + DeviceParser( + '(Android 3\\.\\d|Opera Tablet|Tablet; .+Firefox/|Android.*(?:Tab|Pad))', + 'i', + 'Generic Tablet', + 'Generic', + 'Tablet', + ), + DeviceParser( + '(Symbian|\\bS60(Version|V\\d)|\\bS60\\b|\\((Series 60|Windows Mobile|Palm OS|Bada); Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .+Firefox/|iPhone OS|Android|MobileSafari|Windows *Phone|\\(webOS/|PalmOS)', + None, + 'Generic Smartphone', + 'Generic', + 'Smartphone', + ), + DeviceParser( + '(hiptop|avantgo|plucker|xiino|blazer|elaine)', + 'i', + 'Generic Smartphone', + 'Generic', + 'Smartphone', + ), + DeviceParser( + '(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.*/\\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify|Yeti|OgScrper)', + 'i', + 'Spider', + 'Spider', + 'Desktop', + ), + DeviceParser( + '^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\\-|airn|alav|asus|attw|au\\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\\-n|bw\\-u|beck|benq|bilb|blac|c55/|cdm\\-|chtm|capi|comp|cond|dall|dbte|dc\\-s|dica|ds\\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\\-|fly_|g\\-mo|g1 u|g560|gf\\-5|grun|gene|go.w|good|grad|hcit|hd\\-m|hd\\-p|hd\\-t|hei\\-|hp i|hpip|hs\\-c|htc |htc\\-|htca|htcg)', + 'i', + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + '^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\\-20|i\\-go|i\\-ma|i\\-mobile|i230|iac|iac\\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt |kwc\\-|klon|lexi|lg g|lg\\-a|lg\\-b|lg\\-c|lg\\-d|lg\\-f|lg\\-g|lg\\-k|lg\\-l|lg\\-m|lg\\-o|lg\\-p|lg\\-s|lg\\-t|lg\\-u|lg\\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\\-|lge/|leno|m1\\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\\-|nem\\-|newg|neon)', + 'i', + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + '^(netf|noki|nzph|o2 x|o2\\-x|opwv|owg1|opti|oran|ot\\-s|p800|pand|pg\\-1|pg\\-2|pg\\-3|pg\\-6|pg\\-8|pg\\-c|pg13|phil|pn\\-2|pt\\-g|palm|pana|pire|pock|pose|psio|qa\\-a|qc\\-2|qc\\-3|qc\\-5|qc\\-7|qc07|qc12|qc21|qc32|qc60|qci\\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\\-|scp\\-|sdk/|se47|sec\\-|sec0|sec1|semc|sgh\\-|shar|sie\\-|sk\\-0|sl45|slid|smb3|smt5|sp01|sph\\-|spv |spv\\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\\-mo|t218|t250|t600|t610|t618|tcl\\-|tdg\\-|telm|tim\\-|ts70|tsm\\-|tsm3|tsm5|tx\\-9|tagt)', + 'i', + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + '^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c |w3c\\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\\-|your|zte\\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\\-|webc|whit|wmlb|xda\\-|xda_)', + 'i', + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + '^(Ice)$', + None, + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + '(wap[\\-\\ ]browser|maui|netfront|obigo|teleca|up\\.browser|midp|Opera Mini)', + 'i', + 'Generic Feature Phone', + 'Generic', + 'Feature Phone', + ), + DeviceParser( + 'Mac OS', + None, + 'Mac', + 'Apple', + 'Mac', + ), +] + +OS_PARSERS = [ + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 4.0', + None, + '2013', + None, + None, + None, + ), + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 3.0', + None, + '2012', + None, + None, + None, + ), + OSParser( + 'HbbTV/1.1.1 \\(;;;;;\\) Maple_2011', + 'Samsung', + '2011', + None, + None, + None, + ), + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.*FXPDEUC', + None, + None, + 'UE40F7000', + None, + None, + ), + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.*MST12DEUC', + None, + None, + 'UE32F4500', + None, + None, + ), + OSParser( + 'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/4', + None, + '2013', + None, + None, + None, + ), + OSParser( + 'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/3', + None, + '2012', + None, + None, + None, + ), + OSParser( + 'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/2', + None, + '2011', + None, + None, + None, + ), + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+.*(firetv)-firefox-plugin (\\d+).(\\d+).(\\d+)', + 'FireHbbTV', + None, + None, + None, + None, + ), + OSParser( + 'HbbTV/\\d+\\.\\d+\\.\\d+ \\(.*; ?([a-zA-Z]+) ?;.*(201[1-9]).*\\)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Windows Phone) (?:OS[ /])?(\\d+)\\.(\\d+)', + None, + None, + None, + None, + None, + ), + OSParser( + '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|).*Outlook-iOS-Android', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(Android)[ \\-/](\\d+)(?:\\.(\\d+)|)(?:[.\\-]([a-z0-9]+)|)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Android) Donut', + None, + '1', + '2', + None, + None, + ), + OSParser( + '(Android) Eclair', + None, + '2', + '1', + None, + None, + ), + OSParser( + '(Android) Froyo', + None, + '2', + '2', + None, + None, + ), + OSParser( + '(Android) Gingerbread', + None, + '2', + '3', + None, + None, + ), + OSParser( + '(Android) Honeycomb', + None, + '3', + None, + None, + None, + ), + OSParser( + '(Android) (\\d+);', + None, + None, + None, + None, + None, + ), + OSParser( + '^UCWEB.*; (Adr) (\\d+)\\.(\\d+)(?:[.\\-]([a-z0-9]+)|);', + 'Android', + None, + None, + None, + None, + ), + OSParser( + '^UCWEB.*; (iPad|iPh|iPd) OS (\\d+)_(\\d+)(?:_(\\d+)|);', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '^UCWEB.*; (wds) (\\d+)\\.(\\d+)(?:\\.(\\d+)|);', + 'Windows Phone', + None, + None, + None, + None, + ), + OSParser( + '^(JUC).*; ?U; ?(?:Android|)(\\d+)\\.(\\d+)(?:[\\.\\-]([a-z0-9]+)|)', + 'Android', + None, + None, + None, + None, + ), + OSParser( + '(android)\\s(?:mobile\\/)(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)', + 'Android', + None, + None, + None, + None, + ), + OSParser( + '(Silk-Accelerated=[a-z]{4,5})', + 'Android', + None, + None, + None, + None, + ), + OSParser( + '(x86_64|aarch64)\\ (\\d+)\\.(\\d+)\\.(\\d+).*Chrome.*(?:CitrixChromeApp)$', + 'Chrome OS', + None, + None, + None, + None, + ), + OSParser( + '(XBLWP7)', + 'Windows Phone', + None, + None, + None, + None, + ), + OSParser( + '(Windows ?Mobile)', + 'Windows Mobile', + None, + None, + None, + None, + ), + OSParser( + '(Windows 10)', + 'Windows', + '10', + None, + None, + None, + ), + OSParser( + '(Windows (?:NT 5\\.2|NT 5\\.1))', + 'Windows', + 'XP', + None, + None, + None, + ), + OSParser( + '(Windows NT 6\\.1)', + 'Windows', + '7', + None, + None, + None, + ), + OSParser( + '(Windows NT 6\\.0)', + 'Windows', + 'Vista', + None, + None, + None, + ), + OSParser( + '(Win 9x 4\\.90)', + 'Windows', + 'ME', + None, + None, + None, + ), + OSParser( + '(Windows NT 6\\.2; ARM;)', + 'Windows', + 'RT', + None, + None, + None, + ), + OSParser( + '(Windows NT 6\\.2)', + 'Windows', + '8', + None, + None, + None, + ), + OSParser( + '(Windows NT 6\\.3; ARM;)', + 'Windows', + 'RT 8', + '1', + None, + None, + ), + OSParser( + '(Windows NT 6\\.3)', + 'Windows', + '8', + '1', + None, + None, + ), + OSParser( + '(Windows NT 6\\.4)', + 'Windows', + '10', + None, + None, + None, + ), + OSParser( + '(Windows NT 10\\.0)', + 'Windows', + '10', + None, + None, + None, + ), + OSParser( + '(Windows NT 5\\.0)', + 'Windows', + '2000', + None, + None, + None, + ), + OSParser( + '(WinNT4.0)', + 'Windows', + 'NT 4.0', + None, + None, + None, + ), + OSParser( + '(Windows ?CE)', + 'Windows', + 'CE', + None, + None, + None, + ), + OSParser( + 'Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE)', + 'Windows', + '$1', + None, + None, + None, + ), + OSParser( + 'Win16', + 'Windows', + '3.1', + None, + None, + None, + ), + OSParser( + 'Win32', + 'Windows', + '95', + None, + None, + None, + ), + OSParser( + '^Box.*Windows/([\\d.]+);', + 'Windows', + '$1', + None, + None, + None, + ), + OSParser( + '(Tizen)[/ ](\\d+)\\.(\\d+)', + None, + None, + None, + None, + None, + ), + OSParser( + '((?:Mac[ +]?|; )OS[ +]X)[\\s+/](?:(\\d+)[_.](\\d+)(?:[_.](\\d+)|)|Mach-O)', + 'Mac OS X', + None, + None, + None, + None, + ), + OSParser( + '\\w+\\s+Mac OS X\\s+\\w+\\s+(\\d+).(\\d+).(\\d+).*', + 'Mac OS X', + '$1', + '$2', + '$3', + None, + ), + OSParser( + ' (Dar)(win)/(9).(\\d+).*\\((?:i386|x86_64|Power Macintosh)\\)', + 'Mac OS X', + '10', + '5', + None, + None, + ), + OSParser( + ' (Dar)(win)/(10).(\\d+).*\\((?:i386|x86_64)\\)', + 'Mac OS X', + '10', + '6', + None, + None, + ), + OSParser( + ' (Dar)(win)/(11).(\\d+).*\\((?:i386|x86_64)\\)', + 'Mac OS X', + '10', + '7', + None, + None, + ), + OSParser( + ' (Dar)(win)/(12).(\\d+).*\\((?:i386|x86_64)\\)', + 'Mac OS X', + '10', + '8', + None, + None, + ), + OSParser( + ' (Dar)(win)/(13).(\\d+).*\\((?:i386|x86_64)\\)', + 'Mac OS X', + '10', + '9', + None, + None, + ), + OSParser( + 'Mac_PowerPC', + 'Mac OS', + None, + None, + None, + None, + ), + OSParser( + '(?:PPC|Intel) (Mac OS X)', + None, + None, + None, + None, + None, + ), + OSParser( + '^Box.*;(Darwin)/(10)\\.(1\\d)(?:\\.(\\d+)|)', + 'Mac OS X', + None, + None, + None, + None, + ), + OSParser( + '(Apple\\s?TV)(?:/(\\d+)\\.(\\d+)|)', + 'ATV OS X', + None, + None, + None, + None, + ), + OSParser( + '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|)', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(iPhone|iPad|iPod); Opera', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(iPhone|iPad|iPod).*Mac OS X.*Version/(\\d+)\\.(\\d+)', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(5)48\\.0\\.3.* Darwin/11\\.0\\.0', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(5)48\\.(0)\\.4.* Darwin/(1)1\\.0\\.0', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(5)48\\.(1)\\.4', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(4)85\\.1(3)\\.9', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(6)09\\.(1)\\.4', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/(6)(0)9', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/6(7)2\\.(1)\\.13', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/6(7)2\\.(1)\\.(1)4', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CF)(Network)/6(7)(2)\\.1\\.15', + 'iOS', + '7', + '1', + None, + None, + ), + OSParser( + '(CFNetwork)/6(7)2\\.(0)\\.(?:2|8)', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(CFNetwork)/709\\.1', + 'iOS', + '8', + '0.b5', + None, + None, + ), + OSParser( + '(CF)(Network)/711\\.(\\d)', + 'iOS', + '8', + None, + None, + None, + ), + OSParser( + '(CF)(Network)/(720)\\.(\\d)', + 'Mac OS X', + '10', + '10', + None, + None, + ), + OSParser( + '(CF)(Network)/(760)\\.(\\d)', + 'Mac OS X', + '10', + '11', + None, + None, + ), + OSParser( + 'CFNetwork/7.* Darwin/15\\.4\\.\\d+', + 'iOS', + '9', + '3', + '1', + None, + ), + OSParser( + 'CFNetwork/7.* Darwin/15\\.5\\.\\d+', + 'iOS', + '9', + '3', + '2', + None, + ), + OSParser( + 'CFNetwork/7.* Darwin/15\\.6\\.\\d+', + 'iOS', + '9', + '3', + '5', + None, + ), + OSParser( + '(CF)(Network)/758\\.(\\d)', + 'iOS', + '9', + None, + None, + None, + ), + OSParser( + 'CFNetwork/808\\.3 Darwin/16\\.3\\.\\d+', + 'iOS', + '10', + '2', + '1', + None, + ), + OSParser( + '(CF)(Network)/808\\.(\\d)', + 'iOS', + '10', + None, + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/17\\.\\d+.*\\(x86_64\\)', + 'Mac OS X', + '10', + '13', + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/16\\.\\d+.*\\(x86_64\\)', + 'Mac OS X', + '10', + '12', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/15\\.\\d+.*\\(x86_64\\)', + 'Mac OS X', + '10', + '11', + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/(9)\\.\\d+', + 'iOS', + '1', + None, + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/(10)\\.\\d+', + 'iOS', + '4', + None, + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/(11)\\.\\d+', + 'iOS', + '5', + None, + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/(13)\\.\\d+', + 'iOS', + '6', + None, + None, + None, + ), + OSParser( + 'CFNetwork/6.* Darwin/(14)\\.\\d+', + 'iOS', + '7', + None, + None, + None, + ), + OSParser( + 'CFNetwork/7.* Darwin/(14)\\.\\d+', + 'iOS', + '8', + '0', + None, + None, + ), + OSParser( + 'CFNetwork/7.* Darwin/(15)\\.\\d+', + 'iOS', + '9', + '0', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/16\\.5\\.\\d+', + 'iOS', + '10', + '3', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/16\\.6\\.\\d+', + 'iOS', + '10', + '3', + '2', + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/16\\.7\\.\\d+', + 'iOS', + '10', + '3', + '3', + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/(16)\\.\\d+', + 'iOS', + '10', + None, + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/17\\.0\\.\\d+', + 'iOS', + '11', + '0', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/17\\.2\\.\\d+', + 'iOS', + '11', + '1', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/17\\.3\\.\\d+', + 'iOS', + '11', + '2', + None, + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/17\\.4\\.\\d+', + 'iOS', + '11', + '2', + '6', + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/17\\.5\\.\\d+', + 'iOS', + '11', + '3', + None, + None, + ), + OSParser( + 'CFNetwork/9.* Darwin/17\\.6\\.\\d+', + 'iOS', + '11', + '4', + None, + None, + ), + OSParser( + 'CFNetwork/9.* Darwin/17\\.7\\.\\d+', + 'iOS', + '11', + '4', + '1', + None, + ), + OSParser( + 'CFNetwork/8.* Darwin/(17)\\.\\d+', + 'iOS', + '11', + None, + None, + None, + ), + OSParser( + 'CFNetwork/9.* Darwin/18\\.0\\.\\d+', + 'iOS', + '12', + '0', + None, + None, + ), + OSParser( + 'CFNetwork/9.* Darwin/(18)\\.\\d+', + 'iOS', + '12', + None, + None, + None, + ), + OSParser( + 'CFNetwork/.* Darwin/', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '\\b(iOS[ /]|iOS; |iPhone(?:/| v|[ _]OS[/,]|; | OS : |\\d,\\d/|\\d,\\d; )|iPad/)(\\d{1,2})[_\\.](\\d{1,2})(?:[_\\.](\\d+)|)', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '\\((iOS);', + None, + None, + None, + None, + None, + ), + OSParser( + '(watchOS)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'WatchOS', + None, + None, + None, + None, + ), + OSParser( + 'Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone', + None, + None, + None, + None, + None, + ), + OSParser( + '(iPod|iPhone|iPad)', + 'iOS', + None, + None, + None, + None, + ), + OSParser( + '(tvOS)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'tvOS', + None, + None, + None, + None, + ), + OSParser( + '(CrOS) [a-z0-9_]+ (\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'Chrome OS', + None, + None, + None, + None, + ), + OSParser( + '([Dd]ebian)', + 'Debian', + None, + None, + None, + None, + ), + OSParser( + '(Linux Mint)(?:/(\\d+)|)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Mandriva)(?: Linux|)/(?:[\\d.-]+m[a-z]{2}(\\d+).(\\d)|)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Symbian[Oo][Ss])[/ ](\\d+)\\.(\\d+)', + 'Symbian OS', + None, + None, + None, + None, + ), + OSParser( + '(Symbian/3).+NokiaBrowser/7\\.3', + 'Symbian^3 Anna', + None, + None, + None, + None, + ), + OSParser( + '(Symbian/3).+NokiaBrowser/7\\.4', + 'Symbian^3 Belle', + None, + None, + None, + None, + ), + OSParser( + '(Symbian/3)', + 'Symbian^3', + None, + None, + None, + None, + ), + OSParser( + '\\b(Series 60|SymbOS|S60Version|S60V\\d|S60\\b)', + 'Symbian OS', + None, + None, + None, + None, + ), + OSParser( + '(MeeGo)', + None, + None, + None, + None, + None, + ), + OSParser( + 'Symbian [Oo][Ss]', + 'Symbian OS', + None, + None, + None, + None, + ), + OSParser( + 'Series40;', + 'Nokia Series 40', + None, + None, + None, + None, + ), + OSParser( + 'Series30Plus;', + 'Nokia Series 30 Plus', + None, + None, + None, + None, + ), + OSParser( + '(BB10);.+Version/(\\d+)\\.(\\d+)\\.(\\d+)', + 'BlackBerry OS', + None, + None, + None, + None, + ), + OSParser( + '(Black[Bb]erry)[0-9a-z]+/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'BlackBerry OS', + None, + None, + None, + None, + ), + OSParser( + '(Black[Bb]erry).+Version/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'BlackBerry OS', + None, + None, + None, + None, + ), + OSParser( + '(RIM Tablet OS) (\\d+)\\.(\\d+)\\.(\\d+)', + 'BlackBerry Tablet OS', + None, + None, + None, + None, + ), + OSParser( + '(Play[Bb]ook)', + 'BlackBerry Tablet OS', + None, + None, + None, + None, + ), + OSParser( + '(Black[Bb]erry)', + 'BlackBerry OS', + None, + None, + None, + None, + ), + OSParser( + '(K[Aa][Ii]OS)\\/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'KaiOS', + None, + None, + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/18.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '1', + '0', + '1', + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/18.1 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '1', + '1', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/26.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '1', + '2', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/28.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '1', + '3', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/30.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '1', + '4', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/32.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '2', + '0', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Gecko/34.0 Firefox/\\d+\\.\\d+', + 'Firefox OS', + '2', + '1', + None, + None, + ), + OSParser( + '\\((?:Mobile|Tablet);.+Firefox/\\d+\\.\\d+', + 'Firefox OS', + None, + None, + None, + None, + ), + OSParser( + '(BREW)[ /](\\d+)\\.(\\d+)\\.(\\d+)', + None, + None, + None, + None, + None, + ), + OSParser( + '(BREW);', + None, + None, + None, + None, + None, + ), + OSParser( + '(Brew MP|BMP)[ /](\\d+)\\.(\\d+)\\.(\\d+)', + 'Brew MP', + None, + None, + None, + None, + ), + OSParser( + 'BMP;', + 'Brew MP', + None, + None, + None, + None, + ), + OSParser( + '(GoogleTV)(?: (\\d+)\\.(\\d+)(?:\\.(\\d+)|)|/[\\da-z]+)', + None, + None, + None, + None, + None, + ), + OSParser( + '(WebTV)/(\\d+).(\\d+)', + None, + None, + None, + None, + None, + ), + OSParser( + '(CrKey)(?:[/](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)', + 'Chromecast', + None, + None, + None, + None, + ), + OSParser( + '(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)', + 'webOS', + None, + None, + None, + None, + ), + OSParser( + '(VRE);', + None, + None, + None, + None, + None, + ), + OSParser( + '(Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Sailfish|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\\b)BSD)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Linux)[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|).*gentoo', + 'Gentoo', + None, + None, + None, + None, + ), + OSParser( + '\\((Bada);', + None, + None, + None, + None, + None, + ), + OSParser( + '(Windows|Android|WeTab|Maemo|Web0S)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|(?:Free|Open|Net|\\b)BSD)', + None, + None, + None, + None, + None, + ), + OSParser( + '(Linux)(?:[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)', + None, + None, + None, + None, + None, + ), + OSParser( + 'SunOS', + 'Solaris', + None, + None, + None, + None, + ), + OSParser( + '\\(linux-gnu\\)', + 'Linux', + None, + None, + None, + None, + ), + OSParser( + '\\(x86_64-redhat-linux-gnu\\)', + 'Red Hat', + None, + None, + None, + None, + ), + OSParser( + '\\((freebsd)(\\d+)\\.(\\d+)\\)', + 'FreeBSD', + None, + None, + None, + None, + ), + OSParser( + 'linux', + 'Linux', + None, + None, + None, + None, + ), + OSParser( + '^(Roku)/DVP-(\\d+)\\.(\\d+)', + None, + None, + None, + None, + None, + ), +] diff --git a/app_common/lib/ua_parser/user_agent_parser.py b/app_common/lib/ua_parser/user_agent_parser.py new file mode 100644 index 00000000..45f5a063 --- /dev/null +++ b/app_common/lib/ua_parser/user_agent_parser.py @@ -0,0 +1,544 @@ +# Copyright 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the 'License') +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python implementation of the UA parser.""" + +from __future__ import absolute_import + +import os +import re + +__author__ = "Lindsey Simon " + + +class UserAgentParser(object): + def __init__( + self, pattern, family_replacement=None, v1_replacement=None, v2_replacement=None + ): + """Initialize UserAgentParser. + + Args: + pattern: a regular expression string + family_replacement: a string to override the matched family (optional) + v1_replacement: a string to override the matched v1 (optional) + v2_replacement: a string to override the matched v2 (optional) + """ + self.pattern = pattern + self.user_agent_re = re.compile(self.pattern) + self.family_replacement = family_replacement + self.v1_replacement = v1_replacement + self.v2_replacement = v2_replacement + + def MatchSpans(self, user_agent_string): + match_spans = [] + match = self.user_agent_re.search(user_agent_string) + if match: + match_spans = [ + match.span(group_index) for group_index in range(1, match.lastindex + 1) + ] + return match_spans + + def Parse(self, user_agent_string): + family, v1, v2, v3 = None, None, None, None + match = self.user_agent_re.search(user_agent_string) + if match: + if self.family_replacement: + if re.search(r"\$1", self.family_replacement): + family = re.sub(r"\$1", match.group(1), self.family_replacement) + else: + family = self.family_replacement + else: + family = match.group(1) + + if self.v1_replacement: + v1 = self.v1_replacement + elif match.lastindex and match.lastindex >= 2: + v1 = match.group(2) or None + + if self.v2_replacement: + v2 = self.v2_replacement + elif match.lastindex and match.lastindex >= 3: + v2 = match.group(3) or None + + if match.lastindex and match.lastindex >= 4: + v3 = match.group(4) or None + + return family, v1, v2, v3 + + +class OSParser(object): + def __init__( + self, + pattern, + os_replacement=None, + os_v1_replacement=None, + os_v2_replacement=None, + os_v3_replacement=None, + os_v4_replacement=None, + ): + """Initialize UserAgentParser. + + Args: + pattern: a regular expression string + os_replacement: a string to override the matched os (optional) + os_v1_replacement: a string to override the matched v1 (optional) + os_v2_replacement: a string to override the matched v2 (optional) + os_v3_replacement: a string to override the matched v3 (optional) + os_v4_replacement: a string to override the matched v4 (optional) + """ + self.pattern = pattern + self.user_agent_re = re.compile(self.pattern) + self.os_replacement = os_replacement + self.os_v1_replacement = os_v1_replacement + self.os_v2_replacement = os_v2_replacement + self.os_v3_replacement = os_v3_replacement + self.os_v4_replacement = os_v4_replacement + + def MatchSpans(self, user_agent_string): + match_spans = [] + match = self.user_agent_re.search(user_agent_string) + if match: + match_spans = [ + match.span(group_index) for group_index in range(1, match.lastindex + 1) + ] + return match_spans + + def Parse(self, user_agent_string): + os, os_v1, os_v2, os_v3, os_v4 = None, None, None, None, None + match = self.user_agent_re.search(user_agent_string) + if match: + if self.os_replacement: + os = MultiReplace(self.os_replacement, match) + elif match.lastindex: + os = match.group(1) + + if self.os_v1_replacement: + os_v1 = MultiReplace(self.os_v1_replacement, match) + elif match.lastindex and match.lastindex >= 2: + os_v1 = match.group(2) + + if self.os_v2_replacement: + os_v2 = MultiReplace(self.os_v2_replacement, match) + elif match.lastindex and match.lastindex >= 3: + os_v2 = match.group(3) + + if self.os_v3_replacement: + os_v3 = MultiReplace(self.os_v3_replacement, match) + elif match.lastindex and match.lastindex >= 4: + os_v3 = match.group(4) + + if self.os_v4_replacement: + os_v4 = MultiReplace(self.os_v4_replacement, match) + elif match.lastindex and match.lastindex >= 5: + os_v4 = match.group(5) + + return os, os_v1, os_v2, os_v3, os_v4 + + +def MultiReplace(string, match): + def _repl(m): + index = int(m.group(1)) - 1 + group = match.groups() + if index < len(group): + return group[index] + return "" + + _string = re.sub(r"\$(\d)", _repl, string) + _string = re.sub(r"^\s+|\s+$", "", _string) + if _string == "": + return None + return _string + + +class DeviceParser(object): + def __init__( + self, + pattern, + regex_flag=None, + device_replacement=None, + brand_replacement=None, + model_replacement=None, + ): + """Initialize UserAgentParser. + + Args: + pattern: a regular expression string + device_replacement: a string to override the matched device (optional) + """ + self.pattern = pattern + if regex_flag == "i": + self.user_agent_re = re.compile(self.pattern, re.IGNORECASE) + else: + self.user_agent_re = re.compile(self.pattern) + self.device_replacement = device_replacement + self.brand_replacement = brand_replacement + self.model_replacement = model_replacement + + def MatchSpans(self, user_agent_string): + match_spans = [] + match = self.user_agent_re.search(user_agent_string) + if match: + match_spans = [ + match.span(group_index) for group_index in range(1, match.lastindex + 1) + ] + return match_spans + + def Parse(self, user_agent_string): + device, brand, model = None, None, None + match = self.user_agent_re.search(user_agent_string) + if match: + if self.device_replacement: + device = MultiReplace(self.device_replacement, match) + else: + device = match.group(1) + + if self.brand_replacement: + brand = MultiReplace(self.brand_replacement, match) + + if self.model_replacement: + model = MultiReplace(self.model_replacement, match) + elif len(match.groups()) > 0: + model = match.group(1) + + return device, brand, model + + +MAX_CACHE_SIZE = 20 +_parse_cache = {} + + +def Parse(user_agent_string, **jsParseBits): + """ Parse all the things + Args: + user_agent_string: the full user agent string + jsParseBits: javascript override bits + Returns: + A dictionary containing all parsed bits + """ + jsParseBits = jsParseBits or {} + key = (user_agent_string, repr(jsParseBits)) + cached = _parse_cache.get(key) + if cached is not None: + return cached + if len(_parse_cache) > MAX_CACHE_SIZE: + _parse_cache.clear() + v = { + "user_agent": ParseUserAgent(user_agent_string, **jsParseBits), + "os": ParseOS(user_agent_string, **jsParseBits), + "device": ParseDevice(user_agent_string, **jsParseBits), + "string": user_agent_string, + } + _parse_cache[key] = v + return v + + +def ParseUserAgent(user_agent_string, **jsParseBits): + """ Parses the user-agent string for user agent (browser) info. + Args: + user_agent_string: The full user-agent string. + jsParseBits: javascript override bits. + Returns: + A dictionary containing parsed bits. + """ + if ( + "js_user_agent_family" in jsParseBits + and jsParseBits["js_user_agent_family"] != "" + ): + family = jsParseBits["js_user_agent_family"] + v1 = jsParseBits.get("js_user_agent_v1") or None + v2 = jsParseBits.get("js_user_agent_v2") or None + v3 = jsParseBits.get("js_user_agent_v3") or None + else: + for uaParser in USER_AGENT_PARSERS: + family, v1, v2, v3 = uaParser.Parse(user_agent_string) + if family: + break + + # Override for Chrome Frame IFF Chrome is enabled. + if "js_user_agent_string" in jsParseBits: + js_user_agent_string = jsParseBits["js_user_agent_string"] + if ( + js_user_agent_string + and js_user_agent_string.find("Chrome/") > -1 + and user_agent_string.find("chromeframe") > -1 + ): + jsOverride = {} + jsOverride = ParseUserAgent(js_user_agent_string) + family = "Chrome Frame (%s %s)" % (family, v1) + v1 = jsOverride["major"] + v2 = jsOverride["minor"] + v3 = jsOverride["patch"] + + family = family or "Other" + return { + "family": family, + "major": v1 or None, + "minor": v2 or None, + "patch": v3 or None, + } + + +def ParseOS(user_agent_string, **jsParseBits): + """ Parses the user-agent string for operating system info + Args: + user_agent_string: The full user-agent string. + jsParseBits: javascript override bits. + Returns: + A dictionary containing parsed bits. + """ + for osParser in OS_PARSERS: + os, os_v1, os_v2, os_v3, os_v4 = osParser.Parse(user_agent_string) + if os: + break + os = os or "Other" + return { + "family": os, + "major": os_v1, + "minor": os_v2, + "patch": os_v3, + "patch_minor": os_v4, + } + + +def ParseDevice(user_agent_string): + """ Parses the user-agent string for device info. + Args: + user_agent_string: The full user-agent string. + ua_family: The parsed user agent family name. + Returns: + A dictionary containing parsed bits. + """ + for deviceParser in DEVICE_PARSERS: + device, brand, model = deviceParser.Parse(user_agent_string) + if device: + break + + if device is None: + device = "Other" + + return {"family": device, "brand": brand, "model": model} + + +def PrettyUserAgent(family, v1=None, v2=None, v3=None): + """Pretty user agent string.""" + if v3: + if v3[0].isdigit(): + return "%s %s.%s.%s" % (family, v1, v2, v3) + else: + return "%s %s.%s%s" % (family, v1, v2, v3) + elif v2: + return "%s %s.%s" % (family, v1, v2) + elif v1: + return "%s %s" % (family, v1) + return family + + +def PrettyOS(os, os_v1=None, os_v2=None, os_v3=None, os_v4=None): + """Pretty os string.""" + if os_v4: + return "%s %s.%s.%s.%s" % (os, os_v1, os_v2, os_v3, os_v4) + if os_v3: + if os_v3[0].isdigit(): + return "%s %s.%s.%s" % (os, os_v1, os_v2, os_v3) + else: + return "%s %s.%s%s" % (os, os_v1, os_v2, os_v3) + elif os_v2: + return "%s %s.%s" % (os, os_v1, os_v2) + elif os_v1: + return "%s %s" % (os, os_v1) + return os + + +def ParseWithJSOverrides( + user_agent_string, + js_user_agent_string=None, + js_user_agent_family=None, + js_user_agent_v1=None, + js_user_agent_v2=None, + js_user_agent_v3=None, +): + """ backwards compatible. use one of the other Parse methods instead! """ + + # Override via JS properties. + if js_user_agent_family is not None and js_user_agent_family != "": + family = js_user_agent_family + v1 = None + v2 = None + v3 = None + if js_user_agent_v1 is not None: + v1 = js_user_agent_v1 + if js_user_agent_v2 is not None: + v2 = js_user_agent_v2 + if js_user_agent_v3 is not None: + v3 = js_user_agent_v3 + else: + for parser in USER_AGENT_PARSERS: + family, v1, v2, v3 = parser.Parse(user_agent_string) + if family: + break + + # Override for Chrome Frame IFF Chrome is enabled. + if ( + js_user_agent_string + and js_user_agent_string.find("Chrome/") > -1 + and user_agent_string.find("chromeframe") > -1 + ): + family = "Chrome Frame (%s %s)" % (family, v1) + ua_dict = ParseUserAgent(js_user_agent_string) + v1 = ua_dict["major"] + v2 = ua_dict["minor"] + v3 = ua_dict["patch"] + + return family or "Other", v1, v2, v3 + + +def Pretty(family, v1=None, v2=None, v3=None): + """ backwards compatible. use PrettyUserAgent instead! """ + if v3: + if v3[0].isdigit(): + return "%s %s.%s.%s" % (family, v1, v2, v3) + else: + return "%s %s.%s%s" % (family, v1, v2, v3) + elif v2: + return "%s %s.%s" % (family, v1, v2) + elif v1: + return "%s %s" % (family, v1) + return family + + +def GetFilters( + user_agent_string, + js_user_agent_string=None, + js_user_agent_family=None, + js_user_agent_v1=None, + js_user_agent_v2=None, + js_user_agent_v3=None, +): + """Return the optional arguments that should be saved and used to query. + + js_user_agent_string is always returned if it is present. We really only need + it for Chrome Frame. However, I added it in the generally case to find other + cases when it is different. When the recording of js_user_agent_string was + added, we created new records for all new user agents. + + Since we only added js_document_mode for the IE 9 preview case, it did not + cause new user agent records the way js_user_agent_string did. + + js_document_mode has since been removed in favor of individual property + overrides. + + Args: + user_agent_string: The full user-agent string. + js_user_agent_string: JavaScript ua string from client-side + js_user_agent_family: This is an override for the family name to deal + with the fact that IE platform preview (for instance) cannot be + distinguished by user_agent_string, but only in javascript. + js_user_agent_v1: v1 override - see above. + js_user_agent_v2: v1 override - see above. + js_user_agent_v3: v1 override - see above. + Returns: + {js_user_agent_string: '[...]', js_family_name: '[...]', etc...} + """ + filters = {} + filterdict = { + "js_user_agent_string": js_user_agent_string, + "js_user_agent_family": js_user_agent_family, + "js_user_agent_v1": js_user_agent_v1, + "js_user_agent_v2": js_user_agent_v2, + "js_user_agent_v3": js_user_agent_v3, + } + for key, value in filterdict.items(): + if value is not None and value != "": + filters[key] = value + return filters + + +# Build the list of user agent parsers from YAML +UA_PARSER_YAML = os.environ.get("UA_PARSER_YAML") +if UA_PARSER_YAML: + # This will raise an ImportError if missing, obviously since it's no + # longer a requirement + import yaml + + try: + # Try and use libyaml bindings if available since faster + from yaml import CSafeLoader as SafeLoader + except ImportError: + from yaml import SafeLoader + + with open(UA_PARSER_YAML) as fp: + regexes = yaml.load(fp, Loader=SafeLoader) + + USER_AGENT_PARSERS = [] + for _ua_parser in regexes["user_agent_parsers"]: + _regex = _ua_parser["regex"] + + _family_replacement = _ua_parser.get("family_replacement") + _v1_replacement = _ua_parser.get("v1_replacement") + _v2_replacement = _ua_parser.get("v2_replacement") + + USER_AGENT_PARSERS.append( + UserAgentParser( + _regex, _family_replacement, _v1_replacement, _v2_replacement + ) + ) + + OS_PARSERS = [] + for _os_parser in regexes["os_parsers"]: + _regex = _os_parser["regex"] + + _os_replacement = _os_parser.get("os_replacement") + _os_v1_replacement = _os_parser.get("os_v1_replacement") + _os_v2_replacement = _os_parser.get("os_v2_replacement") + _os_v3_replacement = _os_parser.get("os_v3_replacement") + _os_v4_replacement = _os_parser.get("os_v4_replacement") + + OS_PARSERS.append( + OSParser( + _regex, + _os_replacement, + _os_v1_replacement, + _os_v2_replacement, + _os_v3_replacement, + _os_v4_replacement, + ) + ) + + DEVICE_PARSERS = [] + for _device_parser in regexes["device_parsers"]: + _regex = _device_parser["regex"] + + _regex_flag = _device_parser.get("regex_flag") + _device_replacement = _device_parser.get("device_replacement") + _brand_replacement = _device_parser.get("brand_replacement") + _model_replacement = _device_parser.get("model_replacement") + + DEVICE_PARSERS.append( + DeviceParser( + _regex, + _regex_flag, + _device_replacement, + _brand_replacement, + _model_replacement, + ) + ) + + # Clean our our temporary vars explicitly + # so they can't be reused or imported + del regexes + del yaml + del SafeLoader +else: + # Just load our pre-compiled versions + from ._regexes import USER_AGENT_PARSERS, DEVICE_PARSERS, OS_PARSERS diff --git a/app_common/lib/ua_parser/user_agent_parser_test.py b/app_common/lib/ua_parser/user_agent_parser_test.py new file mode 100644 index 00000000..c73d742b --- /dev/null +++ b/app_common/lib/ua_parser/user_agent_parser_test.py @@ -0,0 +1,290 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the 'License') +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""User Agent Parser Unit Tests. +Run: +# python -m user_agent_parser_test (runs all the tests, takes awhile) +or like: +# python -m user_agent_parser_test ParseTest.testBrowserscopeStrings +""" + + +from __future__ import unicode_literals, absolute_import + +__author__ = "slamm@google.com (Stephen Lamm)" + +import os +import re +import unittest +import yaml + +try: + # Try and use libyaml bindings if available since faster + from yaml import CSafeLoader as SafeLoader +except ImportError: + from yaml import SafeLoader + +from ua_parser import user_agent_parser + +TEST_RESOURCES_DIR = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "../uap-core" +) + + +class ParseTest(unittest.TestCase): + def testBrowserscopeStrings(self): + self.runUserAgentTestsFromYAML( + os.path.join(TEST_RESOURCES_DIR, "tests/test_ua.yaml") + ) + + def testBrowserscopeStringsOS(self): + self.runOSTestsFromYAML(os.path.join(TEST_RESOURCES_DIR, "tests/test_os.yaml")) + + def testStringsOS(self): + self.runOSTestsFromYAML( + os.path.join(TEST_RESOURCES_DIR, "test_resources/additional_os_tests.yaml") + ) + + def testStringsDevice(self): + self.runDeviceTestsFromYAML( + os.path.join(TEST_RESOURCES_DIR, "tests/test_device.yaml") + ) + + def testMozillaStrings(self): + self.runUserAgentTestsFromYAML( + os.path.join( + TEST_RESOURCES_DIR, "test_resources/firefox_user_agent_strings.yaml" + ) + ) + + # NOTE: The YAML file used here is one output by makePGTSComparisonYAML() + # below, as opposed to the pgts_browser_list-orig.yaml file. The -orig + # file is by no means perfect, but identifies many browsers that we + # classify as "Other". This test itself is mostly useful to know when + # somthing in UA parsing changes. An effort should be made to try and + # reconcile the differences between the two YAML files. + def testPGTSStrings(self): + self.runUserAgentTestsFromYAML( + os.path.join(TEST_RESOURCES_DIR, "test_resources/pgts_browser_list.yaml") + ) + + def testParseAll(self): + user_agent_string = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; fr; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5,gzip(gfe),gzip(gfe)" + expected = { + "device": {"family": "Mac", "brand": "Apple", "model": "Mac"}, + "os": { + "family": "Mac OS X", + "major": "10", + "minor": "4", + "patch": None, + "patch_minor": None, + }, + "user_agent": { + "family": "Firefox", + "major": "3", + "minor": "5", + "patch": "5", + }, + "string": user_agent_string, + } + + result = user_agent_parser.Parse(user_agent_string) + self.assertEqual( + result, + expected, + "UA: {0}\n expected<{1}> != actual<{2}>".format( + user_agent_string, expected, result + ), + ) + + # Make a YAML file for manual comparsion with pgts_browser_list-orig.yaml + def makePGTSComparisonYAML(self): + import codecs + + outfile = codecs.open("outfile.yaml", "w", "utf-8") + print >> outfile, "test_cases:" + + yamlFile = open(os.path.join(TEST_RESOURCES_DIR, "pgts_browser_list.yaml")) + yamlContents = yaml.load(yamlFile, Loader=SafeLoader) + yamlFile.close() + + for test_case in yamlContents["test_cases"]: + user_agent_string = test_case["user_agent_string"] + kwds = {} + if "js_ua" in test_case: + kwds = eval(test_case["js_ua"]) + + (family, major, minor, patch) = user_agent_parser.ParseUserAgent( + user_agent_string, **kwds + ) + + # Escape any double-quotes in the UA string + user_agent_string = re.sub(r'"', '\\"', user_agent_string) + print >> outfile, ' - user_agent_string: "' + user_agent_string + '"' + "\n" + ' family: "' + family + '"\n' + " major: " + ( + "" if (major is None) else "'" + major + "'" + ) + "\n" + " minor: " + ( + "" if (minor is None) else "'" + minor + "'" + ) + "\n" + " patch: " + ( + "" if (patch is None) else "'" + patch + "'" + ) + outfile.close() + + # Run a set of test cases from a YAML file + def runUserAgentTestsFromYAML(self, file_name): + yamlFile = open(os.path.join(TEST_RESOURCES_DIR, file_name)) + yamlContents = yaml.load(yamlFile, Loader=SafeLoader) + yamlFile.close() + + for test_case in yamlContents["test_cases"]: + # Inputs to Parse() + user_agent_string = test_case["user_agent_string"] + kwds = {} + if "js_ua" in test_case: + kwds = eval(test_case["js_ua"]) + + # The expected results + expected = { + "family": test_case["family"], + "major": test_case["major"], + "minor": test_case["minor"], + "patch": test_case["patch"], + } + + result = {} + result = user_agent_parser.ParseUserAgent(user_agent_string, **kwds) + self.assertEqual( + result, + expected, + "UA: {0}\n expected<{1}, {2}, {3}, {4}> != actual<{5}, {6}, {7}, {8}>".format( + user_agent_string, + expected["family"], + expected["major"], + expected["minor"], + expected["patch"], + result["family"], + result["major"], + result["minor"], + result["patch"], + ), + ) + + def runOSTestsFromYAML(self, file_name): + yamlFile = open(os.path.join(TEST_RESOURCES_DIR, file_name)) + yamlContents = yaml.load(yamlFile, Loader=SafeLoader) + yamlFile.close() + + for test_case in yamlContents["test_cases"]: + # Inputs to Parse() + user_agent_string = test_case["user_agent_string"] + kwds = {} + if "js_ua" in test_case: + kwds = eval(test_case["js_ua"]) + + # The expected results + expected = { + "family": test_case["family"], + "major": test_case["major"], + "minor": test_case["minor"], + "patch": test_case["patch"], + "patch_minor": test_case["patch_minor"], + } + + result = user_agent_parser.ParseOS(user_agent_string, **kwds) + self.assertEqual( + result, + expected, + "UA: {0}\n expected<{1} {2} {3} {4} {5}> != actual<{6} {7} {8} {9} {10}>".format( + user_agent_string, + expected["family"], + expected["major"], + expected["minor"], + expected["patch"], + expected["patch_minor"], + result["family"], + result["major"], + result["minor"], + result["patch"], + result["patch_minor"], + ), + ) + + def runDeviceTestsFromYAML(self, file_name): + yamlFile = open(os.path.join(TEST_RESOURCES_DIR, file_name)) + yamlContents = yaml.load(yamlFile, Loader=SafeLoader) + yamlFile.close() + + for test_case in yamlContents["test_cases"]: + # Inputs to Parse() + user_agent_string = test_case["user_agent_string"] + kwds = {} + if "js_ua" in test_case: + kwds = eval(test_case["js_ua"]) + + # The expected results + expected = { + "family": test_case["family"], + "brand": test_case["brand"], + "model": test_case["model"], + } + + result = user_agent_parser.ParseDevice(user_agent_string, **kwds) + self.assertEqual( + result, + expected, + "UA: {0}\n expected<{1} {2} {3}> != actual<{4} {5} {6}>".format( + user_agent_string, + expected["family"], + expected["brand"], + expected["model"], + result["family"], + result["brand"], + result["model"], + ), + ) + + +class GetFiltersTest(unittest.TestCase): + def testGetFiltersNoMatchesGiveEmptyDict(self): + user_agent_string = "foo" + filters = user_agent_parser.GetFilters( + user_agent_string, js_user_agent_string=None + ) + self.assertEqual({}, filters) + + def testGetFiltersJsUaPassedThrough(self): + user_agent_string = "foo" + filters = user_agent_parser.GetFilters( + user_agent_string, js_user_agent_string="bar" + ) + self.assertEqual({"js_user_agent_string": "bar"}, filters) + + def testGetFiltersJsUserAgentFamilyAndVersions(self): + user_agent_string = ( + "Mozilla/4.0 (compatible; MSIE 8.0; " + "Windows NT 5.1; Trident/4.0; GTB6; .NET CLR 2.0.50727; " + ".NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)" + ) + filters = user_agent_parser.GetFilters( + user_agent_string, js_user_agent_string="bar", js_user_agent_family="foo" + ) + self.assertEqual( + {"js_user_agent_string": "bar", "js_user_agent_family": "foo"}, filters + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/app_common/lib/user_agents/__init__.py b/app_common/lib/user_agents/__init__.py new file mode 100644 index 00000000..e47740f2 --- /dev/null +++ b/app_common/lib/user_agents/__init__.py @@ -0,0 +1,3 @@ +VERSION = (2, 2, 0) + +from .parsers import parse diff --git a/app_common/lib/user_agents/compat.py b/app_common/lib/user_agents/compat.py new file mode 100644 index 00000000..c4d1a247 --- /dev/null +++ b/app_common/lib/user_agents/compat.py @@ -0,0 +1,14 @@ +import sys + +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str + + def iteritems(d, **kw): + return iter(d.items(**kw)) +else: + string_types = basestring + + def iteritems(d, **kw): + return iter(d.iteritems(**kw)) diff --git a/app_common/lib/user_agents/parsers.py b/app_common/lib/user_agents/parsers.py new file mode 100644 index 00000000..7c309697 --- /dev/null +++ b/app_common/lib/user_agents/parsers.py @@ -0,0 +1,268 @@ +from collections import namedtuple + +from ..ua_parser import user_agent_parser +from .compat import string_types + + +MOBILE_DEVICE_FAMILIES = ( + 'iPhone', + 'iPod', + 'Generic Smartphone', + 'Generic Feature Phone', + 'PlayStation Vita', + 'iOS-Device' +) + +PC_OS_FAMILIES = ( + 'Windows 95', + 'Windows 98', + 'Solaris', +) + +MOBILE_OS_FAMILIES = ( + 'Windows Phone', + 'Windows Phone OS', # Earlier versions of ua-parser returns Windows Phone OS + 'Symbian OS', + 'Bada', + 'Windows CE', + 'Windows Mobile', + 'Maemo', +) + +MOBILE_BROWSER_FAMILIES = ( + 'IE Mobile', + 'Opera Mobile', + 'Opera Mini', + 'Chrome Mobile', + 'Chrome Mobile WebView', + 'Chrome Mobile iOS', +) + +TABLET_DEVICE_FAMILIES = ( + 'iPad', + 'BlackBerry Playbook', + 'Blackberry Playbook', # Earlier versions of ua-parser returns "Blackberry" instead of "BlackBerry" + 'Kindle', + 'Kindle Fire', + 'Kindle Fire HD', + 'Galaxy Tab', + 'Xoom', + 'Dell Streak', +) + +TOUCH_CAPABLE_OS_FAMILIES = ( + 'iOS', + 'Android', + 'Windows Phone', + 'Windows CE', + 'Windows Mobile', + 'Firefox OS', + 'MeeGo', +) + +TOUCH_CAPABLE_DEVICE_FAMILIES = ( + 'BlackBerry Playbook', + 'Blackberry Playbook', + 'Kindle Fire', +) + +EMAIL_PROGRAM_FAMILIES = set(( + 'Outlook', + 'Windows Live Mail', + 'AirMail', + 'Apple Mail', + 'Outlook', + 'Thunderbird', + 'Lightning', + 'ThunderBrowse', + 'Windows Live Mail', + 'The Bat!', + 'Lotus Notes', + 'IBM Notes', + 'Barca', + 'MailBar', + 'kmail2', + 'YahooMobileMail' +)) + +def verify_attribute(attribute): + if isinstance(attribute, string_types) and attribute.isdigit(): + return int(attribute) + + return attribute + + +def parse_version(major=None, minor=None, patch=None, patch_minor=None): + # Returns version number tuple, attributes will be integer if they're numbers + major = verify_attribute(major) + minor = verify_attribute(minor) + patch = verify_attribute(patch) + patch_minor = verify_attribute(patch_minor) + + return tuple( + filter(lambda x: x is not None, (major, minor, patch, patch_minor)) + ) + + +Browser = namedtuple('Browser', ['family', 'version', 'version_string']) + + +def parse_browser(family, major=None, minor=None, patch=None, patch_minor=None): + # Returns a browser object + version = parse_version(major, minor, patch) + version_string = '.'.join([str(v) for v in version]) + return Browser(family, version, version_string) + + +OperatingSystem = namedtuple('OperatingSystem', ['family', 'version', 'version_string']) + + +def parse_operating_system(family, major=None, minor=None, patch=None, patch_minor=None): + version = parse_version(major, minor, patch) + version_string = '.'.join([str(v) for v in version]) + return OperatingSystem(family, version, version_string) + + +Device = namedtuple('Device', ['family', 'brand', 'model']) + + +def parse_device(family, brand, model): + return Device(family, brand, model) + + +class UserAgent(object): + + def __init__(self, user_agent_string): + ua_dict = user_agent_parser.Parse(user_agent_string) + self.ua_string = user_agent_string + self.os = parse_operating_system(**ua_dict['os']) + self.browser = parse_browser(**ua_dict['user_agent']) + self.device = parse_device(**ua_dict['device']) + + def __str__(self): + return "{device} / {os} / {browser}".format( + device=self.get_device(), + os=self.get_os(), + browser=self.get_browser() + ) + + def __unicode__(self): + return unicode(str(self)) + + def _is_android_tablet(self): + # Newer Android tablets don't have "Mobile" in their user agent string, + # older ones like Galaxy Tab still have "Mobile" though they're not + if ('Mobile Safari' not in self.ua_string and + self.browser.family != "Firefox Mobile"): + return True + return False + + def _is_blackberry_touch_capable_device(self): + # A helper to determine whether a BB phone has touch capabilities + # Blackberry Bold Touch series begins with 99XX + if 'Blackberry 99' in self.device.family: + return True + if 'Blackberry 95' in self.device.family: # BB Storm devices + return True + return False + + def get_device(self): + return self.is_pc and "PC" or self.device.family + + def get_os(self): + return ("%s %s" % (self.os.family, self.os.version_string)).strip() + + def get_browser(self): + return ("%s %s" % (self.browser.family, self.browser.version_string)).strip() + + @property + def is_tablet(self): + if self.device.family in TABLET_DEVICE_FAMILIES: + return True + if (self.os.family == 'Android' and self._is_android_tablet()): + return True + if self.os.family == 'Windows' and self.os.version_string.startswith('RT'): + return True + if self.os.family == 'Firefox OS' and 'Mobile' not in self.browser.family: + return True + return False + + @property + def is_mobile(self): + # First check for mobile device and mobile browser families + if self.device.family in MOBILE_DEVICE_FAMILIES: + return True + if self.browser.family in MOBILE_BROWSER_FAMILIES: + return True + # Device is considered Mobile OS is Android and not tablet + # This is not fool proof but would have to suffice for now + if ((self.os.family == 'Android' or self.os.family == 'Firefox OS') + and not self.is_tablet): + return True + if self.os.family == 'BlackBerry OS' and self.device.family != 'Blackberry Playbook': + return True + if self.os.family in MOBILE_OS_FAMILIES: + return True + # TODO: remove after https://github.com/tobie/ua-parser/issues/126 is closed + if 'J2ME' in self.ua_string or 'MIDP' in self.ua_string: + return True + # This is here mainly to detect Google's Mobile Spider + if 'iPhone;' in self.ua_string: + return True + if 'Googlebot-Mobile' in self.ua_string: + return True + # Mobile Spiders should be identified as mobile + if self.device.family == 'Spider' and 'Mobile' in self.browser.family: + return True + # Nokia mobile + if 'NokiaBrowser' in self.ua_string and 'Mobile' in self.ua_string: + return True + return False + + @property + def is_touch_capable(self): + # TODO: detect touch capable Nokia devices + if self.os.family in TOUCH_CAPABLE_OS_FAMILIES: + return True + if self.device.family in TOUCH_CAPABLE_DEVICE_FAMILIES: + return True + if self.os.family == 'Windows': + if self.os.version_string.startswith(('RT', 'CE')): + return True + if self.os.version_string.startswith('8') and 'Touch' in self.ua_string: + return True + if 'BlackBerry' in self.os.family and self._is_blackberry_touch_capable_device(): + return True + return False + + @property + def is_pc(self): + # Returns True for "PC" devices (Windows, Mac and Linux) + if 'Windows NT' in self.ua_string or self.os.family in PC_OS_FAMILIES or \ + self.os.family == 'Windows' and self.os.version_string == 'ME': + return True + # TODO: remove after https://github.com/tobie/ua-parser/issues/127 is closed + if self.os.family == 'Mac OS X' and 'Silk' not in self.ua_string: + return True + # Maemo has 'Linux' and 'X11' in UA, but it is not for PC + if 'Maemo' in self.ua_string: + return False + if 'Chrome OS' in self.os.family: + return True + if 'Linux' in self.ua_string and 'X11' in self.ua_string: + return True + return False + + @property + def is_bot(self): + return True if self.device.family == 'Spider' else False + + @property + def is_email_client(self): + if self.browser.family in EMAIL_PROGRAM_FAMILIES: + return True + return False + + +def parse(user_agent_string): + return UserAgent(user_agent_string) diff --git a/app_common/lib/user_agents/tests.py b/app_common/lib/user_agents/tests.py new file mode 100644 index 00000000..345cf827 --- /dev/null +++ b/app_common/lib/user_agents/tests.py @@ -0,0 +1,268 @@ +import json +import os +import unittest + +from ua_parser import user_agent_parser +from . import compat +from .parsers import parse + + +iphone_ua_string = 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3' +ipad_ua_string = 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10' +galaxy_tab_ua_string = 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; SCH-I800 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' +galaxy_s3_ua_string = 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' +kindle_fire_ua_string = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true' +playbook_ua_string = 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.0.1; en-US) AppleWebKit/535.8+ (KHTML, like Gecko) Version/7.2.0.1 Safari/535.8+' +nexus_7_ua_string = 'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19' +windows_phone_ua_string = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; SGH-i917)' +blackberry_torch_ua_string = 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; zh-TW) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.448 Mobile Safari/534.8+' +blackberry_bold_ua_string = 'BlackBerry9700/5.0.0.862 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/331 UNTRUSTED/1.0 3gpp-gba' +blackberry_bold_touch_ua_string = 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9930; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0.241 Mobile Safari/534.11+' +windows_rt_ua_string = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)' +j2me_opera_ua_string = 'Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (J2ME/22.478; U; en) Presto/2.5.25 Version/10.54' +ie_ua_string = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)' +ie_touch_ua_string = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch)' +mac_safari_ua_string = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2' +windows_ie_ua_string = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' +ubuntu_firefox_ua_string = 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1' +google_bot_ua_string = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' +nokia_n97_ua_string = 'Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/12.0.024; Profile/MIDP-2.1 Configuration/CLDC-1.1; en-us) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.12344' +android_firefox_aurora_ua_string = 'Mozilla/5.0 (Android; Mobile; rv:27.0) Gecko/27.0 Firefox/27.0' +thunderbird_ua_string = 'Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.2.0 Lightning/4.0.2' +outlook_usa_string = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; Microsoft Outlook 15.0.4420)' +chromebook_ua_string = 'Mozilla/5.0 (X11; CrOS i686 0.12.433) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.77 Safari/534.30' + +iphone_ua = parse(iphone_ua_string) +ipad_ua = parse(ipad_ua_string) +galaxy_tab = parse(galaxy_tab_ua_string) +galaxy_s3_ua = parse(galaxy_s3_ua_string) +kindle_fire_ua = parse(kindle_fire_ua_string) +playbook_ua = parse(playbook_ua_string) +nexus_7_ua = parse(nexus_7_ua_string) +windows_phone_ua = parse(windows_phone_ua_string) +windows_rt_ua = parse(windows_rt_ua_string) +blackberry_torch_ua = parse(blackberry_torch_ua_string) +blackberry_bold_ua = parse(blackberry_bold_ua_string) +blackberry_bold_touch_ua = parse(blackberry_bold_touch_ua_string) +j2me_opera_ua = parse(j2me_opera_ua_string) +ie_ua = parse(ie_ua_string) +ie_touch_ua = parse(ie_touch_ua_string) +mac_safari_ua = parse(mac_safari_ua_string) +windows_ie_ua = parse(windows_ie_ua_string) +ubuntu_firefox_ua = parse(ubuntu_firefox_ua_string) +google_bot_ua = parse(google_bot_ua_string) +nokia_n97_ua = parse(nokia_n97_ua_string) +android_firefox_aurora_ua = parse(android_firefox_aurora_ua_string) +thunderbird_ua = parse(thunderbird_ua_string) +outlook_ua = parse(outlook_usa_string) +chromebook_ua = parse(chromebook_ua_string) + + +class UserAgentsTest(unittest.TestCase): + + def test_user_agent_object_assignments(self): + ua_dict = user_agent_parser.Parse(devices['iphone']['ua_string']) + iphone_ua = devices['iphone']['user_agent'] + + # Ensure browser attributes are assigned correctly + self.assertEqual(iphone_ua.browser.family, + ua_dict['user_agent']['family']) + self.assertEqual( + iphone_ua.browser.version, + (int(ua_dict['user_agent']['major']), + int(ua_dict['user_agent']['minor'])) + ) + + # Ensure os attributes are assigned correctly + self.assertEqual(iphone_ua.os.family, ua_dict['os']['family']) + self.assertEqual( + iphone_ua.os.version, + (int(ua_dict['os']['major']), int(ua_dict['os']['minor'])) + ) + + # Ensure device attributes are assigned correctly + self.assertEqual(iphone_ua.device.family, + ua_dict['device']['family']) + + def test_is_tablet_property(self): + self.assertFalse(iphone_ua.is_tablet) + self.assertFalse(galaxy_s3_ua.is_tablet) + self.assertFalse(blackberry_torch_ua.is_tablet) + self.assertFalse(blackberry_bold_ua.is_tablet) + self.assertFalse(windows_phone_ua.is_tablet) + self.assertFalse(ie_ua.is_tablet) + self.assertFalse(ie_touch_ua.is_tablet) + self.assertFalse(mac_safari_ua.is_tablet) + self.assertFalse(windows_ie_ua.is_tablet) + self.assertFalse(ubuntu_firefox_ua.is_tablet) + self.assertFalse(j2me_opera_ua.is_tablet) + self.assertFalse(google_bot_ua.is_tablet) + self.assertFalse(nokia_n97_ua.is_tablet) + self.assertTrue(windows_rt_ua.is_tablet) + self.assertTrue(ipad_ua.is_tablet) + self.assertTrue(playbook_ua.is_tablet) + self.assertTrue(kindle_fire_ua.is_tablet) + self.assertTrue(nexus_7_ua.is_tablet) + self.assertFalse(android_firefox_aurora_ua.is_tablet) + + def test_is_mobile_property(self): + self.assertTrue(iphone_ua.is_mobile) + self.assertTrue(galaxy_s3_ua.is_mobile) + self.assertTrue(blackberry_torch_ua.is_mobile) + self.assertTrue(blackberry_bold_ua.is_mobile) + self.assertTrue(windows_phone_ua.is_mobile) + self.assertTrue(j2me_opera_ua.is_mobile) + self.assertTrue(nokia_n97_ua.is_mobile) + self.assertFalse(windows_rt_ua.is_mobile) + self.assertFalse(ipad_ua.is_mobile) + self.assertFalse(playbook_ua.is_mobile) + self.assertFalse(kindle_fire_ua.is_mobile) + self.assertFalse(nexus_7_ua.is_mobile) + self.assertFalse(ie_ua.is_mobile) + self.assertFalse(ie_touch_ua.is_mobile) + self.assertFalse(mac_safari_ua.is_mobile) + self.assertFalse(windows_ie_ua.is_mobile) + self.assertFalse(ubuntu_firefox_ua.is_mobile) + self.assertFalse(google_bot_ua.is_mobile) + self.assertTrue(android_firefox_aurora_ua.is_mobile) + + def test_is_touch_property(self): + self.assertTrue(iphone_ua.is_touch_capable) + self.assertTrue(galaxy_s3_ua.is_touch_capable) + self.assertTrue(ipad_ua.is_touch_capable) + self.assertTrue(playbook_ua.is_touch_capable) + self.assertTrue(kindle_fire_ua.is_touch_capable) + self.assertTrue(nexus_7_ua.is_touch_capable) + self.assertTrue(windows_phone_ua.is_touch_capable) + self.assertTrue(ie_touch_ua.is_touch_capable) + self.assertTrue(blackberry_bold_touch_ua.is_mobile) + self.assertTrue(blackberry_torch_ua.is_mobile) + self.assertFalse(j2me_opera_ua.is_touch_capable) + self.assertFalse(ie_ua.is_touch_capable) + self.assertFalse(blackberry_bold_ua.is_touch_capable) + self.assertFalse(mac_safari_ua.is_touch_capable) + self.assertFalse(windows_ie_ua.is_touch_capable) + self.assertFalse(ubuntu_firefox_ua.is_touch_capable) + self.assertFalse(google_bot_ua.is_touch_capable) + self.assertFalse(nokia_n97_ua.is_touch_capable) + self.assertTrue(android_firefox_aurora_ua.is_touch_capable) + + def test_is_pc(self): + self.assertFalse(iphone_ua.is_pc) + self.assertFalse(galaxy_s3_ua.is_pc) + self.assertFalse(ipad_ua.is_pc) + self.assertFalse(playbook_ua.is_pc) + self.assertFalse(kindle_fire_ua.is_pc) + self.assertFalse(nexus_7_ua.is_pc) + self.assertFalse(windows_phone_ua.is_pc) + self.assertFalse(blackberry_bold_touch_ua.is_pc) + self.assertFalse(blackberry_torch_ua.is_pc) + self.assertFalse(blackberry_bold_ua.is_pc) + self.assertFalse(j2me_opera_ua.is_pc) + self.assertFalse(google_bot_ua.is_pc) + self.assertFalse(nokia_n97_ua.is_pc) + self.assertTrue(mac_safari_ua.is_pc) + self.assertTrue(windows_ie_ua.is_pc) + self.assertTrue(ubuntu_firefox_ua.is_pc) + self.assertTrue(ie_touch_ua.is_pc) + self.assertTrue(ie_ua.is_pc) + self.assertFalse(android_firefox_aurora_ua.is_pc) + self.assertTrue(chromebook_ua.is_pc) + + def test_is_bot(self): + self.assertTrue(google_bot_ua.is_bot) + self.assertFalse(iphone_ua.is_bot) + self.assertFalse(galaxy_s3_ua.is_bot) + self.assertFalse(ipad_ua.is_bot) + self.assertFalse(playbook_ua.is_bot) + self.assertFalse(kindle_fire_ua.is_bot) + self.assertFalse(nexus_7_ua.is_bot) + self.assertFalse(windows_phone_ua.is_bot) + self.assertFalse(blackberry_bold_touch_ua.is_bot) + self.assertFalse(blackberry_torch_ua.is_bot) + self.assertFalse(blackberry_bold_ua.is_bot) + self.assertFalse(j2me_opera_ua.is_bot) + self.assertFalse(mac_safari_ua.is_bot) + self.assertFalse(windows_ie_ua.is_bot) + self.assertFalse(ubuntu_firefox_ua.is_bot) + self.assertFalse(ie_touch_ua.is_bot) + self.assertFalse(ie_ua.is_bot) + self.assertFalse(nokia_n97_ua.is_bot) + self.assertFalse(android_firefox_aurora_ua.is_bot) + + def test_is_email_client(self): + self.assertTrue(thunderbird_ua.is_email_client) + self.assertTrue(outlook_ua.is_email_client) + self.assertFalse(playbook_ua.is_email_client) + self.assertFalse(kindle_fire_ua.is_email_client) + self.assertFalse(nexus_7_ua.is_email_client) + self.assertFalse(windows_phone_ua.is_email_client) + self.assertFalse(blackberry_bold_touch_ua.is_email_client) + self.assertFalse(blackberry_torch_ua.is_email_client) + self.assertFalse(blackberry_bold_ua.is_email_client) + self.assertFalse(j2me_opera_ua.is_email_client) + self.assertFalse(mac_safari_ua.is_email_client) + self.assertFalse(windows_ie_ua.is_email_client) + self.assertFalse(ubuntu_firefox_ua.is_email_client) + self.assertFalse(ie_touch_ua.is_email_client) + self.assertFalse(ie_ua.is_email_client) + self.assertFalse(nokia_n97_ua.is_email_client) + self.assertFalse(android_firefox_aurora_ua.is_email_client) + + + def test_strings(self): + self.assertEqual(str(iphone_ua), "iPhone / iOS 5.1 / Mobile Safari 5.1") + self.assertEqual(str(ipad_ua), "iPad / iOS 3.2 / Mobile Safari 4.0.4") + self.assertEqual(str(galaxy_tab), "Samsung SCH-I800 / Android 2.2 / Android 2.2") + self.assertEqual(str(galaxy_s3_ua), "Samsung GT-I9300 / Android 4.0.4 / Android 4.0.4") + self.assertEqual(str(kindle_fire_ua), "Kindle / Android / Amazon Silk 1.1.0-80") + self.assertEqual(str(playbook_ua), "BlackBerry Playbook / BlackBerry Tablet OS 2.0.1 / BlackBerry WebKit 2.0.1") + self.assertEqual(str(nexus_7_ua), "Asus Nexus 7 / Android 4.1.1 / Chrome 18.0.1025") + self.assertEqual(str(windows_phone_ua), "Samsung SGH-i917 / Windows Phone 7.5 / IE Mobile 9.0") + self.assertEqual(str(windows_rt_ua), "PC / Windows RT / IE 10.0") + self.assertEqual(str(blackberry_torch_ua), "BlackBerry 9800 / BlackBerry OS 6.0.0 / BlackBerry WebKit 6.0.0") + self.assertEqual(str(blackberry_bold_ua), "BlackBerry 9700 / BlackBerry OS 5.0.0 / BlackBerry 9700") + self.assertEqual(str(blackberry_bold_touch_ua), "BlackBerry 9930 / BlackBerry OS 7.0.0 / BlackBerry WebKit 7.0.0") + self.assertEqual(str(j2me_opera_ua), "Generic Feature Phone / Other / Opera Mini 9.80") + self.assertEqual(str(ie_ua), "PC / Windows 8 / IE 10.0") + self.assertEqual(str(ie_touch_ua), "PC / Windows 8 / IE 10.0") + self.assertEqual(str(mac_safari_ua), "PC / Mac OS X 10.6.8 / WebKit Nightly 537.13") + self.assertEqual(str(windows_ie_ua), "PC / Windows 7 / IE 9.0") + self.assertEqual(str(ubuntu_firefox_ua), "PC / Ubuntu / Firefox 15.0.1") + self.assertEqual(str(google_bot_ua), "Spider / Other / Googlebot 2.1") + self.assertEqual(str(nokia_n97_ua), "Nokia N97 / Symbian OS 9.4 / Nokia Browser 7.1.12344") + self.assertEqual(str(android_firefox_aurora_ua), "Generic Smartphone / Android / Firefox Mobile 27.0") + + def test_unicode_strings(self): + try: + # Python 2 + unicode_ua_str = unicode(devices['iphone']['user_agent']) + self.assertEqual(unicode_ua_str, + u"iPhone / iOS 5.1 / Mobile Safari 5.1") + self.assertTrue(isinstance(unicode_ua_str, unicode)) + except NameError: + # Python 3 + unicode_ua_str = str(devices['iphone']['user_agent']) + self.assertEqual(unicode_ua_str, + "iPhone / iOS 5.1 / Mobile Safari 5.1") + + +with open(os.path.join(os.path.dirname(__file__), 'devices.json')) as f: + devices = json.load(f) + + +def test_wrapper(items): + def test_func(self): + attrs = ('is_bot', 'is_mobile', + 'is_pc', 'is_tablet', 'is_touch_capable') + for attr in attrs: + self.assertEqual( + getattr(items['user_agent'], attr), items[attr], msg=attr) + # Temporarily commenting this out since UserAgent.device + # may return different string depending ua-parser version + # self.assertEqual(str(items['user_agent']), items['str']) + return test_func + +for device, items in compat.iteritems(devices): + items['user_agent'] = parse(items['ua_string']) + setattr(UserAgentsTest, 'test_' + device, test_wrapper(items)) diff --git a/app_common/models/__init__.py b/app_common/models/__init__.py new file mode 100644 index 00000000..534ba2b7 --- /dev/null +++ b/app_common/models/__init__.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Part of odooai.cn. See LICENSE file for full copyright and licensing details. + +# Created on 2023-02-02 +# author: 欧度智能,http://www.odooai.cn +# email: 300883@qq.com +# resource of odooai +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# Odoo12在线用户手册(长期更新) +# http://www.odooai.cn/documentation/user/12.0/en/index.html + +# Odoo12在线开发者手册(长期更新) +# http://www.odooai.cn/documentation/12.0/index.html + +# Odoo10在线中文用户手册(长期更新) +# http://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# http://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# http://www.odooai.cn/odoo10_developer_document_offline/ +# description: + +from . import base +from . import ir_ui_view +from . import ir_cron +from . import res_users +from . import ir_mail_server +from . import mail_mail +from . import ir_http +from . import app_import +from . import res_partner + + + + diff --git a/app_common/models/app_import.py b/app_common/models/app_import.py new file mode 100644 index 00000000..04cd79fa --- /dev/null +++ b/app_common/models/app_import.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +import base64 +import io +import csv +import os.path + +from odoo import api, fields, models, modules, tools, SUPERUSER_ID, _ +from odoo.tools import pycompat +from odoo.tests import common +ADMIN_USER_ID = common.ADMIN_USER_ID + +def app_quick_import(cr, content_path, sep=None): + if not sep: + sep = '/' + dir_split = content_path.split(sep) + module_name = dir_split[0] + file_name = dir_split[2] + file_path, file_type = os.path.splitext(content_path) + model_name = file_name.replace(file_type, '') + file_path = modules.get_module_resource(module_name, dir_split[1], file_name) + content = open(file_path, 'rb').read() + uid = SUPERUSER_ID + if model_name == 'discuss.channel': + # todo: 创建discuss.channel时,如果用root用户会报错 + uid = 2 + env = api.Environment(cr, uid, {}) + if file_type == '.csv': + file_type = 'text/csv' + elif file_type in ['.xls', '.xlsx']: + file_type = 'application/vnd.ms-excel' + import_wizard = env['base_import.import'].create({ + 'res_model': model_name, + 'file_name': file_name, + 'file_type': file_type, + 'file': content, + }) + if file_type == 'text/csv': + preview = import_wizard.parse_preview({ + 'separator': ',', + 'has_headers': True, + 'quoting': '"', + }) + elif file_type == 'application/vnd.ms-excel': + preview = import_wizard.parse_preview({ + 'has_headers': True, + }) + result = import_wizard.execute_import( + preview["headers"], + preview["headers"], + preview["options"] + ) + + diff --git a/app_common/models/base.py b/app_common/models/base.py new file mode 100644 index 00000000..f9aa597c --- /dev/null +++ b/app_common/models/base.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _ +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from odoo.http import request + +import requests +import base64 +from io import BytesIO +import uuid + +from datetime import date, datetime, time +import pytz + +import logging + +_logger = logging.getLogger(__name__) + +# 常规的排除的fields +EXCLU_FIELDS = [ + '__last_update', + 'access_token', + 'access_url', + 'access_warning', + 'activity_date_deadline', + 'activity_exception_decoration', + 'activity_exception_icon', + 'activity_ids', + 'activity_state', + 'activity_summary', + 'activity_type_id', + 'activity_user_id', + 'display_name', + 'message_attachment_count', + 'message_channel_ids', + 'message_follower_ids', + 'message_has_error', + 'message_has_error_counter', + 'message_has_sms_error', + 'message_ids', + 'message_is_follower', + 'message_main_attachment_id', + 'message_needaction', + 'message_needaction_counter', + 'message_partner_ids', + 'message_unread', + 'message_unread_counter', + 'website_message_ids', + 'write_date', + 'write_uid', +] + + +class Base(models.AbstractModel): + _inherit = 'base' + + @api.model + def _app_check_sys_op(self): + if self.env.user.has_group('base.group_erp_manager'): + return True + return False + + @api.model + def _get_normal_fields(self): + f_list = [] + for k, v in self._fields.items(): + if k not in EXCLU_FIELDS: + f_list.append(k) + return f_list + + @api.model + def _app_get_m2o_default(self, fieldname, domain=[]): + if hasattr(self, fieldname) and self._fields[fieldname].type == 'many2one': + if self._context.get(fieldname) or self._context.get('default_%s' % fieldname): + return self._context.get(fieldname) or self._context.get('default_%s' % fieldname) + else: + if not domain: + domain = self._fields[fieldname].domain or [] + rec = self.env[self._fields[fieldname].comodel_name].sudo().search(domain, limit=1) + return rec.id if rec else False + return False + + def _app_dt2local(self, value, return_format=DEFAULT_SERVER_DATETIME_FORMAT): + """ + 将value中时间,按格式转为用户本地时间.注意只处理in str为字符串类型,如果是时间类型直接用 datetime.now(tz) + """ + if not value: + return value + if isinstance(value, datetime): + value = value.strftime(return_format) + dt = datetime.strptime(value, return_format) + user_tz = pytz.timezone(self.env.user.tz or 'Etc/GMT-8') + _logger.warning('============= user2 tz: %s' % user_tz) + dt = dt.replace(tzinfo=pytz.timezone('UTC')) + return dt.astimezone(user_tz).strftime(return_format) + + def _app_dt2utc(self, value, return_format=DEFAULT_SERVER_DATETIME_FORMAT): + """ + 将value中用户本地时间,按格式转为UTC时间,输出 str + """ + if not value: + return value + if isinstance(value, datetime): + value = value.strftime(return_format) + dt = datetime.strptime(value, return_format) + pytz_timezone = pytz.timezone('Etc/GMT+8') + dt = dt.replace(tzinfo=pytz.timezone('UTC')) + return dt.astimezone(pytz_timezone).strftime(return_format) + + @api.model + def _get_image_from_url(self, url): + # 返回这个图片的base64编码 + if not self._app_check_sys_op(): + return False + return get_image_from_url(url) + + @api.model + def _get_image_url2attachment(self, url, mimetype_list=None): + # Todo: mimetype filter + if not self._app_check_sys_op(): + return False + image, file_name = get_image_url2attachment(url) + if image and file_name: + try: + attachment = self.env['ir.attachment'].create({ + 'datas': image, + 'name': file_name, + 'website_id': False, + 'res_model': self._name, + 'res_id': self.id, + }) + attachment.generate_access_token() + return attachment + except Exception as e: + _logger.error('get_image_url2attachment error: %s' % str(e)) + return False + else: + return False + + @api.model + def _get_image_base642attachment(self, data): + if not self._app_check_sys_op(): + return False + image, file_name = get_image_base642attachment(data) + if image and file_name: + try: + attachment = self.env['ir.attachment'].create({ + 'datas': image, + 'name': file_name, + 'website_id': False, + 'res_model': self._name, + 'res_id': self.id, + }) + attachment.generate_access_token() + return attachment + except Exception as e: + _logger.error('get_image_base642attachment error: %s' % str(e)) + return False + else: + return False + + def get_ua_type(self): + return get_ua_type() + + +def get_image_from_url(url): + if not url: + return None + try: + response = requests.get(url, timeout=5) + except Exception as e: + return None + # 返回这个图片的base64编码 + return base64.b64encode(BytesIO(response.content).read()) + + +def get_image_url2attachment(url): + if not url: + return None + try: + if url.startswith('//'): + url = 'https:%s' % url + response = requests.get(url, timeout=30) + except Exception as e: + return None, None + # 返回这个图片的base64编码 + image = base64.b64encode(BytesIO(response.content).read()) + file_name = url.split('/')[-1] + return image, file_name + + +def get_image_base642attachment(data): + if not data: + return None + try: + image_data = data.split(',')[1] + file_name = str(uuid.uuid4()) + '.png' + return image_data, file_name + except Exception as e: + return None, None + + +def get_ua_type(): + ua = request.httprequest.headers.get('User-Agent') + # 临时用 agent 处理,后续要前端中正确处理或者都从后台来 + # 微信浏览器 + # MicroMessenger: Mozilla/5.0 (Linux; Android 10; ELE-AL00 Build/HUAWEIELE-AL00; wv) + # AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 + # MQQBrowser/6.2 TBS/045525 Mobile Safari/537.36 MMWEBID/3135 MicroMessenger/8.0.2.1860(0x2800023B) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 + # 微信浏览器,开发工具,网页 iphone + # ,Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1 + # wechatdevtools/1.03.2011120 MicroMessenger/7.0.4 Language/zh_CN webview/16178807094901773 + # webdebugger port/27772 token/b91f4a234b918f4e2a5d1a835a09c31e + + # 微信小程序 + # MicroMessenger: Mozilla/5.0 (Linux; Android 10; ELE-AL00 Build/HUAWEIELE-AL00; wv) + # AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.62 XWEB/2767 MMWEBSDK/20210302 Mobile Safari/537.36 MMWEBID/6689 MicroMessenger/8.0.2.1860(0x2800023B) Process/appbrand2 WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 + # MiniProgramEnv/android + # 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_7_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.37(0x18002529) NetType/WIFI Language/zh_CN' + # 微信浏览器,开发工具,小程序,iphone + # Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1 + # wechatdevtools/1.03.2011120 MicroMessenger/7.0.4 Language/zh_CN webview/ + # 微信内,iphone web + # Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 + # MicroMessenger/8.0.3(0x1800032a) NetType/WIFI Language/zh_CN + # 安卓app,h5 + # ELE-AL00(Android/10) (cn.erpapp.o20sticks.App/13.20.12.09) Weex/0.26.0 1080x2265 + + # web 表示普通浏览器,后续更深入处理 + utype = 'web' + # todo: 引入现成 py lib,处理企业微信 + if 'MicroMessenger' in ua and 'webdebugger' not in ua \ + and ('miniProgram' in ua or 'MiniProgram' in ua or 'MiniProgramEnv' in ua or 'wechatdevtools' in ua): + # 微信小程序及开发者工具 + utype = 'wxapp' + elif 'MicroMessenger' in ua: + # 微信浏览器 + utype = 'wxweb' + elif 'cn.erpapp.o20sticks.App' in ua: + # 安卓app + utype = 'native_android' + # _logger.warning('=========get ua %s,%s' % (utype, ua)) + return utype diff --git a/app_common/models/ir_cron.py b/app_common/models/ir_cron.py new file mode 100644 index 00000000..0d5955ad --- /dev/null +++ b/app_common/models/ir_cron.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import logging +from odoo import api, fields, models, modules, tools, _ + +_logger = logging.getLogger(__name__) + +class IrCron(models.Model): + _inherit = "ir.cron" + + trigger_user_id = fields.Many2one('res.users', string='Last Trigger User') + + def method_direct_trigger(self): + self.write({'trigger_user_id': self.env.user.id}) + return super(IrCron, self).method_direct_trigger() diff --git a/app_common/models/ir_http.py b/app_common/models/ir_http.py new file mode 100644 index 00000000..565d83ed --- /dev/null +++ b/app_common/models/ir_http.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import models +from odoo.http import request + + +class IrHttp(models.AbstractModel): + _inherit = 'ir.http' + + def session_info(self): + result = super(IrHttp, self).session_info() + result['ua_type'] = self.get_ua_type() + return result + + diff --git a/app_common/models/ir_mail_server.py b/app_common/models/ir_mail_server.py new file mode 100644 index 00000000..2321f0d0 --- /dev/null +++ b/app_common/models/ir_mail_server.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, _ + +import logging +_logger = logging.getLogger(__name__) + +class IrMailServer(models.Model): + _inherit = "ir.mail_server" + _order = "sequence" + + # 改默认发邮件逻辑 + @api.model + def send_email(self, message, mail_server_id=None, smtp_server=None, smtp_port=None, + smtp_user=None, smtp_password=None, smtp_encryption=None, + smtp_ssl_certificate=None, smtp_ssl_private_key=None, + smtp_debug=False, smtp_session=None): + email_to = message['To'] + + # 忽略掉无效email,避免被ban + if email_to: + if email_to.find('no-reply@odooai.cn') != -1 or email_to.find('postmaster-odoo@odooai.cn') != -1: + pass + elif email_to.find('example.com') != -1 or email_to.find('@sunpop.cn') != -1 or email_to.find('@odooapp.cn') != -1: + _logger.error(_("=================Email to ignore: %s") % email_to) + raise AssertionError(_("Email to ignore: %s") % email_to) + + return super(IrMailServer, self).send_email(message, mail_server_id, smtp_server, smtp_port, + smtp_user, smtp_password, smtp_encryption, smtp_ssl_certificate, smtp_ssl_private_key, + smtp_debug, smtp_session) diff --git a/app_common/models/ir_ui_view.py b/app_common/models/ir_ui_view.py new file mode 100644 index 00000000..a25941ad --- /dev/null +++ b/app_common/models/ir_ui_view.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from odoo import api, models, tools, SUPERUSER_ID +from odoo.modules.module import get_resource_path +from odoo.tools import view_validation +from odoo.tools.view_validation import _relaxng_cache, validate, _validators +from odoo.tools.safe_eval import safe_eval + +from lxml import etree +import logging +_logger = logging.getLogger(__name__) + +def app_relaxng(view_type): + """ Return a validator for the given view type, or None. """ + if view_type not in _relaxng_cache: + # tree, search 特殊 + if view_type in ['tree', 'search']: + _file = get_resource_path('app_common', 'rng', '%s_view.rng' % view_type) + else: + _file = get_resource_path('base', 'rng', '%s_view.rng' % view_type) + with tools.file_open(_file) as frng: + try: + relaxng_doc = etree.parse(frng) + _relaxng_cache[view_type] = etree.RelaxNG(relaxng_doc) + except Exception: + _logger.error('You can Ignore this. Failed to load RelaxNG XML schema for views validation') + _relaxng_cache[view_type] = None + return _relaxng_cache[view_type] + +# view_validation.relaxng = app_relaxng +# +# class View(models.Model): +# _inherit = 'ir.ui.view' +# +# def __init__(self, env, ids, prefetch_ids): +# # 这里应该是无必要,但为了更安全 +# super(View, self).__init__(env, ids, prefetch_ids) +# view_validation.relaxng = app_relaxng +# +# # todo: 有可能需要处理增加的 header等标签 +# # 直接重写原生方法 +# # def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False): diff --git a/app_common/models/mail_mail.py b/app_common/models/mail_mail.py new file mode 100644 index 00000000..1783926c --- /dev/null +++ b/app_common/models/mail_mail.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, _ + +import logging +_logger = logging.getLogger(__name__) + +class MailMail(models.Model): + _inherit = "mail.mail" + + # 猴子补丁模式,改默认发邮件逻辑 + def _send(self, auto_commit=False, raise_exception=False, smtp_session=None, alias_domain_id=False): + for m in self: + email_to = m.email_to + # 忽略掉无效email,避免被ban + if email_to: + if email_to.find('no-reply@odooai.cn') != -1 or email_to.find('postmaster-odoo@odooai.cn') != -1: + pass + elif email_to.find('example.com') != -1 or email_to.find('@sunpop.cn') != -1 or email_to.find('@odooapp.cn') != -1: + _logger.error(_("=================Email to ignore: %s") % email_to) + self = self - m + if not self: + return True + return super(MailMail, self)._send(auto_commit, raise_exception, smtp_session, alias_domain_id) diff --git a/app_common/models/res_partner.py b/app_common/models/res_partner.py new file mode 100644 index 00000000..43e5eed6 --- /dev/null +++ b/app_common/models/res_partner.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, tools, _ + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + def get_related_user_id(self): + self.ensure_one() + user = self.env['res.users'].sudo().with_context(active_test=False).search([('partner_id', '=', self.id)], limit=1) + if not user and self.commercial_partner_id: + user = self.env['res.users'].sudo().with_context(active_test=False).search([('partner_id', '=', self.commercial_partner_id.id)], + limit=1) + return user diff --git a/app_common/models/res_users.py b/app_common/models/res_users.py new file mode 100644 index 00000000..1c18b7ad --- /dev/null +++ b/app_common/models/res_users.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, tools, _ + + +class ResUsers(models.Model): + _inherit = 'res.users' + + login = fields.Char(index=True) diff --git a/app_common/report/__init__.py b/app_common/report/__init__.py new file mode 100644 index 00000000..633f8661 --- /dev/null +++ b/app_common/report/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/app_common/rng/common.rng b/app_common/rng/common.rng new file mode 100644 index 00000000..0acd4c8e --- /dev/null +++ b/app_common/rng/common.rng @@ -0,0 +1,428 @@ + + + + + + + + + + + + before + + after + + inside + + replace + + + + + + attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/search_view.rng b/app_common/rng/search_view.rng new file mode 100644 index 00000000..3f3c711f --- /dev/null +++ b/app_common/rng/search_view.rng @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/tree_view.rng b/app_common/rng/tree_view.rng new file mode 100644 index 00000000..770c808c --- /dev/null +++ b/app_common/rng/tree_view.rng @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/static/description/banner.png b/app_common/static/description/banner.png new file mode 100644 index 00000000..fe6f6818 Binary files /dev/null and b/app_common/static/description/banner.png differ diff --git a/app_common/static/description/banner1.png b/app_common/static/description/banner1.png new file mode 100644 index 00000000..91d450c8 Binary files /dev/null and b/app_common/static/description/banner1.png differ diff --git a/app_common/static/description/icon.png b/app_common/static/description/icon.png new file mode 100644 index 00000000..2565562f Binary files /dev/null and b/app_common/static/description/icon.png differ diff --git a/app_common/static/description/index.html b/app_common/static/description/index.html new file mode 100644 index 00000000..ff947c17 --- /dev/null +++ b/app_common/static/description/index.html @@ -0,0 +1,121 @@ +
+
+
+

odooAi Common Util and Tools

+

Network and media and base tools

+
+

Lastest update: v17.24.04.29

+
+ +
+
+
+ Key features: +
    +
  • + + 1. Quick import data from excel with .py code +
  • +
  • + + 2. Quick m2o default value +
  • +
  • + + 3. Filter for useless field +
  • +
  • + + 4. UTC local timezone convert +
  • +
  • + + 5. Get browser ua, user-agent +
  • +
  • + + 6. Image to local, image url to local, media to local attachment +
  • +
  • + + 7. Log cron job +
  • +
  • + + 8. Boost for less no use mail +
  • +
  • + + 9. Customize .rng file +
  • +
  • + + 10. Misc like get distance between two points +
  • +
  • + + 11. Multi-language Support. Multi-Company Support +
  • +
  • + + 12. Support Odoo 17,16,15,14,13,12, Enterprise and Community and odoo.sh Edition. +
  • +
  • + + 11. Multi-language Support. Multi-Company Support +
  • +
  • + + 13. Full Open Source. +
  • +
+
+
+
+
+
+
+ +
+
+

Setup, please run the follow command to install the lib.

+

pip install pyyaml ua-parser user-agents

+

After that, install the app_common

+
+
+ + +
+
+
+

Technical Help & Support

+
+
+
+

+ For any type of technical help & support requests, Feel free to contact us

+ + odoo@china.com +

+ Via QQ: 300883 (App user would not get QQ or any other IM support. Only for odoo project customize.)

+ + 300883@qq.com +
+
+

+ Visit our website for more support.

+

https://www.odooai.cn

+
+
+
+
+

More Powerful addons, Make your odoo very easy to use, easy customize: + odooai.cn Odoo Addons +

+
+
+ diff --git a/app_common/static/img/icon_sunpop.png b/app_common/static/img/icon_sunpop.png new file mode 100644 index 00000000..aa490954 Binary files /dev/null and b/app_common/static/img/icon_sunpop.png differ diff --git a/app_common/static/img/logo_sunpop.png b/app_common/static/img/logo_sunpop.png new file mode 100644 index 00000000..bc32c24e Binary files /dev/null and b/app_common/static/img/logo_sunpop.png differ diff --git a/app_common/static/src/js/sunpop.cn b/app_common/static/src/js/sunpop.cn new file mode 100644 index 00000000..e69de29b diff --git a/app_common/static/src/xml/sunpop.cn b/app_common/static/src/xml/sunpop.cn new file mode 100644 index 00000000..e69de29b diff --git a/app_common/views/ir_cron_views.xml b/app_common/views/ir_cron_views.xml new file mode 100644 index 00000000..597e6e4f --- /dev/null +++ b/app_common/views/ir_cron_views.xml @@ -0,0 +1,13 @@ + + + + app.ir.cron.tree + ir.cron + + + + + + + + \ No newline at end of file diff --git a/app_common/wizard/sunpop.cn b/app_common/wizard/sunpop.cn new file mode 100644 index 00000000..e69de29b diff --git a/app_odoo_customize/__init__.py b/app_odoo_customize/__init__.py new file mode 100644 index 00000000..b2860ba3 --- /dev/null +++ b/app_odoo_customize/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from . import wizard +from . import hooks +from .hooks import pre_init_hook +from .hooks import post_init_hook + + + diff --git a/app_odoo_customize/__manifest__.py b/app_odoo_customize/__manifest__.py new file mode 100644 index 00000000..c48d8293 --- /dev/null +++ b/app_odoo_customize/__manifest__.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- + +# Created on 2018-11-26 +# author: 欧度智能,https://www.odooai.cn +# email: 300883@qq.com +# resource of odooai +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# Odoo12在线用户手册(长期更新) +# https://www.odooai.cn/documentation/user/12.0/en/index.html + +# Odoo12在线开发者手册(长期更新) +# https://www.odooai.cn/documentation/12.0/index.html + +# Odoo10在线中文用户手册(长期更新) +# https://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# https://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# https://www.odooai.cn/odoo10_developer_document_offline/ +# description: + +{ + 'name': 'odoo18 Tweak,Ai Employee,Boost,Customize All in One. Customize,UI,Boost,Security,Data,Development Enhance', + 'version': '24.08.19', + 'author': 'odooai.cn', + 'category': 'Extra Tools', + 'website': 'https://www.odooai.cn', + 'live_test_url': 'https://demo.odooapp.cn', + 'license': 'LGPL-3', + 'sequence': 2, + 'images': ['static/description/banner.gif'], + 'summary': """ + Ai as employee. 1 click Tweak odoo. 48 Functions odoo enhancement. for Customize, UI, Boost, Security, Development. + Easy reset data, clear data, reset account chart, reset Demo data. + For quick debug. Set brand, Language Switcher all in one. + """, + 'depends': [ + 'app_common', + 'base_setup', + 'web', + 'mail', + # 'digest', + # when enterprise + # 'web_mobile' + ], + 'data': [ + 'security/res_groups.xml', + 'security/ir.model.access.csv', + 'views/app_odoo_customize_views.xml', + 'views/app_theme_config_settings_views.xml', + 'views/res_config_settings_views.xml', + 'views/ir_views.xml', + 'views/ir_module_module_views.xml', + 'views/ir_translation_views.xml', + 'views/ir_module_addons_path_views.xml', + 'views/ir_ui_menu_views.xml', + 'views/ir_ui_view_views.xml', + 'views/ir_model_fields_views.xml', + 'views/ir_model_data_views.xml', + # data + 'data/ir_config_parameter_data.xml', + 'data/ir_module_module_data.xml', + # 'data/digest_template_data.xml', + 'data/res_company_data.xml', + 'data/res_config_settings_data.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'app_odoo_customize/static/src/scss/app.scss', + 'app_odoo_customize/static/src/scss/ribbon.scss', + 'app_odoo_customize/static/src/scss/dialog.scss', + 'app_odoo_customize/static/src/js/user_menu.js', + 'app_odoo_customize/static/src/js/ribbon.js', + 'app_odoo_customize/static/src/js/dialog.js', + 'app_odoo_customize/static/src/webclient/*.js', + 'app_odoo_customize/static/src/webclient/user_menu.xml', + 'app_odoo_customize/static/src/xml/res_config_edition.xml', + ], + }, + 'pre_init_hook': 'pre_init_hook', + 'installable': True, + 'application': True, + 'auto_install': False, + 'description': """ + + App Customize Odoo (Change Title,Language,Documentation,Quick Debug) + ============ + White label odoo. + Support Odoo 17,16,15,14,13,12,11,10,9. + You can config odoo, make it look like your own platform. + 1. Deletes Odoo label in footer + 2. Replaces "Odoo" in Windows title + 3. Customize Documentation, Support, About links and title in usermenu + 4. Adds "Developer mode" link to the top right-hand User Menu. + 5. Adds Quick Language Switcher to the top right-hand User Menu. + 6. Adds Country flags to the top right-hand User Menu. + 7. Adds English and Chinese user documentation access to the top right-hand User Menu. + 8. Adds developer documentation access to the top right-hand User Menu. + 9. Customize "My odoo.com account" button + 10. Standalone setting panel, easy to setup. + 11. Provide 236 country flags. + 12. Multi-language Support. + 13. Change Powered by Odoo in login screen.(Please change '../views/app_odoo_customize_view.xml' #15) + 14. Quick delete test data in Apps: Sales/POS/Purchase/MRP/Inventory/Accounting/Project/Message/Workflow etc. + 15. Reset All the Sequence to beginning of 1: SO/PO/MO/Invoice... + 16. Fix odoo reload module translation bug while enable english language + 17. Stop Odoo Auto Subscribe(Moved to app_odoo_boost) + 18. Show/Hide Author and Website in Apps Dashboard + 19. One Click to clear all data (Sometime pls click twice) + 20. Show quick upgrade in app dashboard, click to show module info not go to odoo.com + 21. Can clear and reset account chart. Be cautious + 22. Update online manual and developer document to odoo12 + 23. Add reset or clear website blog data + 24. Customize Odoo Native Module(eg. Enterprise) Url + 25. Add remove expense data + 26. Add multi uninstall modules + 27. Add odoo boost modules link. + 28. Easy Menu manager. + 29. Apps version compare. Add Install version in App list. Add Local updatable filter in app list. + 30. 1 key export app translate file like .po file. + 31. Show or hide odoo Referral in the top menu. + 32. Fix odoo bug of complete name bug of product category and stock location.. + 33. Add Demo Ribbon Setting. + 34. Add Remove all quality data. + 35. Fixed for odoo 14. + 36. Add refresh translate for multi module. + 37. Easy noupdate manage for External Identifiers(xml_id) + 38. Add Draggable and sizeable Dialog enable. + 39. Only erp manager can see debug menu.. + 40. Fix support for enterprise version. + 41. Fix odoo bug, when click Preferences menu not hide in mobile. + 42. Mobile Enhance. Add menu navbar setup for top or bottom. navigator footer support. + 43. Check to only Debug / Debug Assets for Odoo Admin. Deny debug from url for other user. + 44. Check to stop subscribe and follow. This to make odoo speed up. + 45. Add addons path info to module. + 46. Add Help documentation anywhere. easy get help for any odoo operation or action. + 47. Add ai robot app integration. Use Ai as your employee. + + This module can help to white label the Odoo. + Also helpful for training and support for your odoo end-user. + The user can get the help document just by one click. + ## 在符合odoo开源协议的前提下,自定义你的odoo系统 + 可完全自行设置下列选项,将 odoo 整合进自有软件产品 + 支持odoo 16,15,14,13,12, 11, 10, 9 版本,社区版企业版通用 + 1. 删除菜单导航页脚的 Odoo 标签 + 2. 将弹出窗口中 "Odoo" 设置为自定义名称 + 3. 自定义用户菜单中的 Documentation, Support, About 的链接 + 4. 在用户菜单中增加快速切换开发模式 + 5. 在用户菜单中增加快速切换多国语言 + 6. 对语言菜单进行美化,设置国旗图标 + 7. 在用户菜单中增加中/英文用户手册,可以不用翻墙加速了 + 8. 在用户菜单中增加开发者手册,含python教程,jquery参考,Jinja2模板,PostgresSQL参考 + 9. 在用户菜单中自定义"My odoo.com account" + 10. 单独设置面板,每个选项都可以自定义 + 11. 提供236个国家的国旗文件(部份需要自行设置文件名) + 12. 多语言版本 + 13. 自定义登陆界面中的 Powered by Odoo + 14. 快速删除测试数据,支持模块包括:销售/POS门店/采购/生产/库存/会计/项目/消息与工作流等. + 15. 将各类单据的序号重置,从1开始,包括:SO/PO/MO/Invoice 等 + 16. 修复odoo启用英文后模块不显示中文的Bug + 17. 可停用odoo自动订阅功能,避免“同样对象关注2次”bug,同时提升性能 + 18. 显示/隐藏应用的作者和网站-在应用安装面板中 + 19. 一键清除所有数据(视当前数据情况,有时需点击2次) + 20. 在应用面板显示快速升级按键,点击时不会导航至 odoo.com + 21. 清除并重置会计科目表 + 22. 全新升级将odoo12用户及开发手册导航至国内网站,或者自己定义的网站 + 23. 增加清除网站数据功能 + 24. 自定义 odoo 原生模块跳转的url(比如企业版模块) + 25. 增加删除费用报销数据功能 + 26. 增加批量卸载模块功能 + 27. 增加odoo加速功能 + 28. 快速管理顶级菜单 + 29. App版本比较,快速查看可本地更新的模块 + 30. 一键导出翻译文件 po + 31. 显示或去除 odoo 推荐 + 32. 增加修复品类及区位名的操作 + 33. 增加 Demo 的显示设置 + 34. 增加清除质检数据 + 35. 优化至odoo14适用 + 36. 可为多个模块强制更新翻译 + 37. noupdate字段的快速管理,主要针对 xml_id + 38. 对话框可拖拽,可缩放,自动大屏优化 + 39. 只有系统管理员可以操作快速debug + 40. 增强对企业版的支持 + 41. 修正odoo原生移动端菜单bug,点击个人设置时,原菜单不隐藏等 + 42. 可设置导航栏在上方还是下方,分开桌面与移动端. + 43. 可设置只允许管理员进入开发者模式,不可在url中直接debut=1来调试 + 44. 可配置停用自动用户订阅功能,这会提速odoo,减少资源消耗 + 45. 为应用模块增加模块路径信息 + 46. 增加快速帮助文档,可以在任意操作中获取相关的 odoo 帮助. + """, +} diff --git a/app_odoo_customize/controllers/__init__.py b/app_odoo_customize/controllers/__init__.py new file mode 100644 index 00000000..0a3e6996 --- /dev/null +++ b/app_odoo_customize/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +# todo: website 有bug oauth +from . import controllers \ No newline at end of file diff --git a/app_odoo_customize/controllers/controllers.py b/app_odoo_customize/controllers/controllers.py new file mode 100644 index 00000000..e702c623 --- /dev/null +++ b/app_odoo_customize/controllers/controllers.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import http +from odoo.addons.portal.controllers.web import Home +from odoo.http import request + + +class AppHome(Home): + + @http.route() + def web_client(self, s_action=None, **kw): + # todo: 当前只对 web,要调整为也对 website + res = super(AppHome, self).web_client(s_action, **kw) + + if kw.get('debug', False): + config_parameter = request.env['ir.config_parameter'].sudo() + app_debug_only_admin = config_parameter.get_param('app_debug_only_admin') + if request.session.uid and request.env.user.browse(request.session.uid)._is_admin(): + pass + else: + if app_debug_only_admin: + return request.redirect('/web/session/logout?debug=0') + return res + + + diff --git a/app_odoo_customize/data/digest_template_data.xml b/app_odoo_customize/data/digest_template_data.xml new file mode 100644 index 00000000..a3baa335 --- /dev/null +++ b/app_odoo_customize/data/digest_template_data.xml @@ -0,0 +1,156 @@ + + + + + + + % set user = ctx.get('user', user) + % set company = user.company_id + % set data = object.compute_kpis(company, user) + % set tips = object.compute_tips(company, user) + % set kpi_actions = object.compute_kpis_actions(company, user) + % set kpis = data.yesterday.keys() + + + + + + +
+ ${company.name} at a glance +
${datetime.date.today().strftime('%B %d, %Y')}
+
+ +
+
+
+ % for kpi in kpis: + + + + + + + +

+ ${object.fields_get()[kpi]['string']} + %if kpi in kpi_actions: + + View more + + %endif +
+ + + + + + +
+ + + + +
+ ${data['yesterday'][kpi][kpi]}
+ Yesterday + % if data['yesterday'][kpi]['margin'] != 0.0: + + % if data['yesterday'][kpi]['margin'] > 0.0: + ${"%.2f" % data['yesterday'][kpi]['margin']} % + % endif + % if data['yesterday'][kpi]['margin'] < 0.0: + ${"%.2f" % data['yesterday'][kpi]['margin']} % + % endif + + % endif +
+
+ + + + +
+ ${data['lastweek'][kpi][kpi]}
+ Last 7 Days + % if data['lastweek'][kpi]['margin'] != 0.0: + + % if data['lastweek'][kpi]['margin'] > 0.0: + ${"%.2f" % data['lastweek'][kpi]['margin']} % + % endif + % if data['lastweek'][kpi]['margin'] < 0.0: + ${"%.2f" % data['lastweek'][kpi]['margin']} % + %endif + + %endif +
+
+ + + + +
+ ${data['lastmonth'][kpi][kpi]}
+ Last 30 Days + % if data['lastmonth'][kpi]['margin'] != 0.0: + + % if data['lastmonth'][kpi]['margin'] > 0.0: + ${"%.2f" % data['lastmonth'][kpi]['margin']} % + % endif + % if data['lastmonth'][kpi]['margin'] < 0.0: + ${"%.2f" % data['lastmonth'][kpi]['margin']} % + %endif + + %endif +
+
+
+ % endfor + % if tips: + + + + +

+
${ctx['tip_description']|safe}
+
+ % endif + + + + + + + +

+
Run your business from anywhere with Odoo Mobile.
+
+
+
+
+ + + + +
+ % if user.has_group('base.group_system'): +
+ Want to customize the email? + Choose the metrics you care about +
+
+ % endif +

+ + Sent by + Odoo - Unsubscribe + +

+
+ + + + ]]>
+
+
diff --git a/app_odoo_customize/data/ir_config_parameter_data.xml b/app_odoo_customize/data/ir_config_parameter_data.xml new file mode 100644 index 00000000..41e39780 --- /dev/null +++ b/app_odoo_customize/data/ir_config_parameter_data.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app_odoo_customize/data/ir_module_module_data.xml b/app_odoo_customize/data/ir_module_module_data.xml new file mode 100644 index 00000000..4280360b --- /dev/null +++ b/app_odoo_customize/data/ir_module_module_data.xml @@ -0,0 +1,64 @@ + + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + + https://www.odooai.cn + + + diff --git a/app_odoo_customize/data/res_company_data.xml b/app_odoo_customize/data/res_company_data.xml new file mode 100644 index 00000000..8d3b6cfa --- /dev/null +++ b/app_odoo_customize/data/res_company_data.xml @@ -0,0 +1,9 @@ + + + + + + https://www.odooai.cn + + + \ No newline at end of file diff --git a/app_odoo_customize/data/res_config_settings_data.xml b/app_odoo_customize/data/res_config_settings_data.xml new file mode 100644 index 00000000..ce9ab5b4 --- /dev/null +++ b/app_odoo_customize/data/res_config_settings_data.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app_odoo_customize/hooks.py b/app_odoo_customize/hooks.py new file mode 100644 index 00000000..ed66bfc5 --- /dev/null +++ b/app_odoo_customize/hooks.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Created on 2018-10-12 +# author: 欧度智能,https://www.odooai.cn +# email: 300883@qq.com +# resource of odooai +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +# Odoo在线中文用户手册(长期更新) +# https://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# https://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# https://www.odooai.cn/odoo10_developer_document_offline/ +# description: + +from odoo import api, SUPERUSER_ID, _ + + +def pre_init_hook(env): + try: + # 更新企业版指向 + sql = "UPDATE ir_module_module SET website = '%s' WHERE license like '%s' and website <> ''" % ('https://www.odooai.cn', 'OEEL%') + env.cr.execute(sql) + env.cr.commit() + except Exception as e: + pass + +def post_init_hook(env): + # a = check_module_installed(cr, ['app_web_superbar','aaaaa']) + pass + # cr.execute("") + +def uninstall_hook(env): + """ + 数据初始化,卸载时执行 + """ + pass + +def check_module_installed(env, modules): + # modules 输入参数是个 list,如 ['base', 'sale'] + installed = False + m = env['ir.module.module'].sudo().search([('name', 'in', modules), ('state', 'in', ['installed', 'to install', 'to upgrade'])]) + if len(m) == len(modules): + installed = True + return installed + diff --git a/app_odoo_customize/i18n/zh_CN.po b/app_odoo_customize/i18n/zh_CN.po new file mode 100644 index 00000000..23ba30db --- /dev/null +++ b/app_odoo_customize/i18n/zh_CN.po @@ -0,0 +1,772 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * app_odoo_customize +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e-20230721\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-08-24 11:23+0000\n" +"PO-Revision-Date: 2023-08-24 11:23+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "" +"\n" +"\t\t\t\t\t\t\t\t\tAccounting\n" +"\t\t\t\t\t\t\t\t" +msgstr "" +"\n" +" 财务\n" +" " + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "All Business" +msgstr "所有业务数据" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Base Models" +msgstr "基础数据" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Expense" +msgstr "费用" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Inventory" +msgstr "库存" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "MRP" +msgstr "制造" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "POS" +msgstr "POS收银" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Project" +msgstr "项目" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Purchase" +msgstr "采购" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Quality" +msgstr "质检" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Sale" +msgstr "销售" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Website And Blog" +msgstr "网站与博客" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.ir_module_addons_path_form_view +msgid " Modules " +msgstr " 模块 " + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Set to False to hide" +msgstr "设置为 False 则不显示" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Activate Assets Debugging" +msgstr "激活开发者模式 (assets)" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Activate the developer mode" +msgstr "激活开发者模式" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_module__addons_path +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_view_module_filter +msgid "Addons Path" +msgstr "模块所在目录" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_module__addons_path_id +msgid "Addons Path ID" +msgstr "模块路径" + +#. module: app_odoo_customize +#: model:ir.actions.act_window,name:app_odoo_customize.action_ir_module_addons_path +#: model:ir.ui.menu,name:app_odoo_customize.menu_ir_module_addons_path +msgid "Addons Paths" +msgstr "模块路径" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__module_app_chatgpt +msgid "Ai Center" +msgstr "Ai服务中心" + +#. module: app_odoo_customize +#: model:ir.model.fields.selection,name:app_odoo_customize.selection__res_config_settings__app_navbar_pos_mobile__bottom +#: model:ir.model.fields.selection,name:app_odoo_customize.selection__res_config_settings__app_navbar_pos_pc__bottom +msgid "Bottom" +msgstr "底部" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_debug_only_admin +msgid "" +"Check to only Debug / Debug Assets for Odoo Admin. Deny debug from url for " +"other user." +msgstr "勾选后仅Odoo管理员可操作开发者模式。 拒绝其他用户从URL进入调试模式。" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_stop_subscribe +msgid "Check to stop subscribe and follow. This to make odoo speed up." +msgstr "选中以停止订阅并关注。这是为了让odoo加速。" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Clean and reset Account Chart" +msgstr "清除会计科目,便于重置" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Click to set" +msgstr "点击设置" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__color +msgid "Color" +msgstr "颜色" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_res_config_settings +msgid "Config Settings" +msgstr "配置设置" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.replace_copyright_name +msgid "Copyright &copy;" +msgstr "版权所有 &copy;" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__create_uid +msgid "Created by" +msgstr "创建者" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__create_date +msgid "Created on" +msgstr "创建于" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_enterprise_url +msgid "Customize Module Url(eg. Enterprise)" +msgstr "自定义模块链接(如企业版)" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Data Cleaning (Be careful to do that!)" +msgstr "数据清理(请谨慎操作!)" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_debug_only_admin +msgid "Debug for Admin" +msgstr "仅系统管理员可调试" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All BOM" +msgstr "删除所有物料清单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Expense and Sheet" +msgstr "删除所有费用与报销申请" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "" +"Delete All MRP/Sale/Purchase/Account/MRP/Inventory/Project/Message/Workflow" +msgstr "" +"清除所有业务数据 MRP/Sale/Purchase/Account/MRP/Inventory/Project/Message/Workflow" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Manufacturing Order" +msgstr "删除所有生产单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Message" +msgstr "删除所有消息" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Move/Picking/Package/Lot" +msgstr "删除所有库存调拨/拣货/包装/批次数据" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All POS Order" +msgstr "删除所有POS订单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Product" +msgstr "删除所有产品及变体" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Product Attribute" +msgstr "删除所有产品属性" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Project/Task/Forecast" +msgstr "删除所有项目/任务/预测" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Purchase Order and Requisition" +msgstr "删除所有询价单、采购单,采购招标" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Quality" +msgstr "删除所有质检单据" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Quality Setting" +msgstr "删除所有质检设置" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Sales Order" +msgstr "删除所有报价单、销售单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Voucher/Invoice/Bill" +msgstr "删除所有收据/发票/账单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Website/Blog" +msgstr "删除所有网站/博客" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Delete All Workflow" +msgstr "删除所有工作流" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_documentation_dev_url +msgid "Developer Documentation Url" +msgstr "开发者手册链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__display_name +msgid "Display Name" +msgstr "显示名称" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Documentation" +msgstr "支持文档" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_documentation_url +msgid "Documentation Url" +msgstr "用户手册链接" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/xml/res_config_edition.xml:0 +#, python-format +msgid "Edition)" +msgstr "版)" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_mail_thread +msgid "Email Thread" +msgstr "邮件会话" + +#. module: app_odoo_customize +#: model:ir.actions.act_window,name:app_odoo_customize.action_server_module_multi_get_po +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_module_view_kanban +msgid "Export Translation" +msgstr "导出翻译" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Extra Feature" +msgstr "增强功能" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__module_app_odoo_doc +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Get Help Documentation on current odoo operation or topic." +msgstr "获取有关当前 odoo 操作或主题的帮助文档。" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "" +"Get Help Documentation on current odoo operation or topic. Click to get" +msgstr "获取有关odoo当前操作或主题的帮助文档。点击获取" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_ir_http +msgid "HTTP Routing" +msgstr "HTTP 路由" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__module_app_odoo_doc +msgid "Help Document Anywhere" +msgstr "即时查看帮助" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__id +msgid "ID" +msgstr "ID" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_base_language_install +msgid "Install Language" +msgstr "安装语言" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path____last_update +msgid "Last Modified on" +msgstr "上次修改时间" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__write_uid +msgid "Last Updated by" +msgstr "最后更新者" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__write_date +msgid "Last Updated on" +msgstr "最后更新于" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Leave the Developer Tools" +msgstr "离开开发者模式" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_module__license +msgid "License" +msgstr "许可协议" + +#. module: app_odoo_customize +#: model:ir.ui.menu,name:app_odoo_customize.menu_app_demo_data +msgid "Load demo data" +msgstr "加载演示数据" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_module__local_updatable +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_view_module_filter +msgid "Local updatable" +msgstr "可本地更新" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_ir_module_module +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__module_ids +msgid "Module" +msgstr "模块" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_ir_module_addons_path +msgid "Module Addons Path" +msgstr "模块所在目录" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__module_count +msgid "Module Count" +msgstr "模块计数" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.ir_module_addons_path_kanban_view +msgid "Modules" +msgstr "模块" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_account_title +msgid "My Odoo.com Account Title" +msgstr "我的帐户显示标题" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_account_url +msgid "My Odoo.com Account Url" +msgstr "我的帐户链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_navbar_pos_mobile +msgid "Navbar Mobile" +msgstr "导航栏移动" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_navbar_pos_pc +msgid "Navbar PC" +msgstr "导航栏电脑" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Navigator" +msgstr "导航栏" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_view_model_data_search +msgid "No Updatable" +msgstr "无需更新" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "OEM & Boost" +msgstr "定制与增强" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/xml/res_config_edition.xml:0 +#, python-format +msgid "Odoo" +msgstr "Odoo欧度" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__path +msgid "Path" +msgstr "路径" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__path_temp +msgid "Path Temp" +msgstr "路径Temp" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Please confirm to delete the select data?" +msgstr "您确认要删除指定数据?" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.replace_login +msgid "" +"Powered by\n" +" odooai.cn" +msgstr "技术支持 odooai.cn" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Preferences" +msgstr "偏好" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Refresh Page" +msgstr "刷新本页" + +#. module: app_odoo_customize +#: model:ir.actions.server,name:app_odoo_customize.action_server_module_multi_refresh_po +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_module_view_kanban +msgid "Refresh Translation" +msgstr "刷新翻译" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Reset Category And Location Complete Name" +msgstr "重置品类及区位显示名" + +#. module: app_odoo_customize +#: model:ir.ui.menu,name:app_odoo_customize.menu_ir_cron +msgid "Scheduled Actions" +msgstr "计划任务" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Security and Boost" +msgstr "安全与提速" + +#. module: app_odoo_customize +#: model:ir.actions.act_window,name:app_odoo_customize.action_app_theme_config +#: model:ir.ui.menu,name:app_odoo_customize.menu_app_theme_config +msgid "OEM and Boost" +msgstr "odoo定制OEM及增强" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_system_name +msgid "Setup System Name,which replace Odoo" +msgstr "设定系统名称,代替原 Odoo 字样" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_ir_module_addons_path__name +msgid "Short Name" +msgstr "简称" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__group_show_author_in_apps +#: model:res.groups,name:app_odoo_customize.group_show_author_in_apps +msgid "Show Author in Apps Dashboard" +msgstr "显示应用的作者-在安装面板中" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_ribbon_name +msgid "Show Demo Ribbon" +msgstr "显示全站测试标签" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_documentation_dev +msgid "Show Developer Documentation" +msgstr "显示开发者文档" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_documentation +msgid "Show Documentation" +msgstr "显示文档" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_enterprise +msgid "Show Enterprise Tag" +msgstr "显示升级企业版标签提醒" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_account +msgid "Show My Account" +msgstr "显示我的帐户" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__module_odoo_referral +msgid "Show Odoo Referral" +msgstr "显示Odoo推荐" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_poweredby +msgid "Show Powered by Odoo" +msgstr "显示Powered by Odoo" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_debug +msgid "Show Quick Debug" +msgstr "显示快速调试" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_lang +msgid "Show Quick Language Switcher" +msgstr "显示快速多语言切换" + +#. module: app_odoo_customize +#: model:res.groups,name:app_odoo_customize.group_show_quick_upgrade +msgid "Show Quick Upgrade in Apps Dashboard" +msgstr "在应用面板显示快速升级按键" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_share +msgid "Show Share Dashboard" +msgstr "显示分享Odoo" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_show_support +msgid "Show Support" +msgstr "显示支持" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_stop_subscribe +msgid "Stop Odoo Subscribe" +msgstr "停止Odoo订阅" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/js/user_menu.js:0 +#, python-format +msgid "Support" +msgstr "技术支持" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_support_url +msgid "Support Url" +msgstr "支持链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,field_description:app_odoo_customize.field_res_config_settings__app_system_name +msgid "System Name" +msgstr "系统名称" + +#. module: app_odoo_customize +#: model:ir.ui.menu,name:app_odoo_customize.menu_ir_config_list +msgid "System Parameters" +msgstr "系统参数" + +#. module: app_odoo_customize +#. odoo-python +#: code:addons/app_odoo_customize/models/ir_module_module.py:0 +#, python-format +msgid "" +"The languages that you selected have been successfully update." +" You still need to Upgrade the apps to make it " +"worked." +msgstr "你选择的翻译语言已更新。但你仍然需要更新相关模块才可生效。" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_edit_menu_access_search +msgid "Top Menu" +msgstr "顶级菜单" + +#. module: app_odoo_customize +#: model:ir.model.fields.selection,name:app_odoo_customize.selection__res_config_settings__app_navbar_pos_mobile__top +#: model:ir.model.fields.selection,name:app_odoo_customize.selection__res_config_settings__app_navbar_pos_pc__top +msgid "Top(Default)" +msgstr "顶部(默认)" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "UI Config" +msgstr "用户界面配置" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__group_show_author_in_apps +msgid "Uncheck to Hide Author and Website in Apps Dashboard" +msgstr "不选中则会隐藏 App 作者及网站" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_enterprise +msgid "Uncheck to hide the Enterprise tag" +msgstr "不选中则会隐藏升级到企业版标签" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_share +msgid "Uncheck to hide the Odoo Share Dashboard" +msgstr "不选中则会隐藏odoo分享" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_poweredby +msgid "Uncheck to hide the Powered by text" +msgstr "不勾选则不显示Odoo Powered by" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__module_odoo_referral +msgid "Uncheck to remove the Odoo Referral" +msgstr "不勾选则不显示Odoo推荐" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_module_view_kanban +msgid "Uninstall" +msgstr "卸载" + +#. module: app_odoo_customize +#: model:ir.actions.server,name:app_odoo_customize.action_server_module_multi_uninstall +msgid "Uninstall Modules" +msgstr "卸载应用" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_base_module_update +msgid "Update Module" +msgstr "更新模块" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.app_module_view_kanban +msgid "Upgrade" +msgstr "升级" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__module_app_chatgpt +msgid "Use Ai to boost you business." +msgstr "使用 Ai 促进您的业务发展。" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "Use Ai to make odoo more powerful." +msgstr "使用 Ai 使odoo更强大好用。" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "User Menu" +msgstr "用户菜单" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "User Menu Content" +msgstr "菜单项具体操作设置" + +#. module: app_odoo_customize +#: model_terms:ir.ui.view,arch_db:app_odoo_customize.view_app_theme_config_settings +msgid "" +"Visit our website for more apps and Support.\n" +"\t\t\t\t\t\t\t\t\t\t\t\t\thttps://www.odooai.cn" +msgstr "请访问我们的网站获取更多支持. http://www.odooai.cn" + +#. module: app_odoo_customize +#: model:ir.model,name:app_odoo_customize.model_web_environment_ribbon_backend +msgid "Web Environment Ribbon Backend" +msgstr "全站测试标签" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_account +msgid "When enable,User can login to your website" +msgstr "启用后,会显示登录到你的网站" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_lang +msgid "When enable,User can quick switch language in user menu" +msgstr "启用后,会显示快速语言切换菜单" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_documentation_dev +msgid "When enable,User can visit development documentation" +msgstr "启用后,会显示开发手册链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_documentation +msgid "When enable,User can visit user manual" +msgstr "启用后,会显示用户手册链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_support +msgid "When enable,User can vist your support site" +msgstr "启用后,会显示在线支持链接" + +#. module: app_odoo_customize +#: model:ir.model.fields,help:app_odoo_customize.field_res_config_settings__app_show_debug +msgid "When enable,everyone login can see the debug menu" +msgstr "启用后,会显示快速调试菜单" + +#. module: app_odoo_customize +#: model:ir.ui.menu,name:app_odoo_customize.menu_app_group +msgid "odooAi" +msgstr "odooAi" + +#. module: app_odoo_customize +#. odoo-javascript +#: code:addons/app_odoo_customize/static/src/xml/res_config_edition.xml:0 +#, python-format +msgid "odooai.cn" +msgstr "odooai.cn" diff --git a/app_odoo_customize/models/__init__.py b/app_odoo_customize/models/__init__.py new file mode 100644 index 00000000..8283382a --- /dev/null +++ b/app_odoo_customize/models/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from . import res_config_settings +from . import base_language_install +from . import ir_module_module +from . import web_environment_ribbon_backend +from . import ir_http +from . import ir_module_addons_path +from . import mail_thread +# from . import ir_ui_view +# from . import ir_ui_menu diff --git a/app_odoo_customize/models/base_language_install.py b/app_odoo_customize/models/base_language_install.py new file mode 100644 index 00000000..a163a6ed --- /dev/null +++ b/app_odoo_customize/models/base_language_install.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models, _ + + +class BaseLanguageInstall(models.TransientModel): + _inherit = "base.language.install" diff --git a/app_odoo_customize/models/ir_http.py b/app_odoo_customize/models/ir_http.py new file mode 100644 index 00000000..c7306cf3 --- /dev/null +++ b/app_odoo_customize/models/ir_http.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import models +from odoo.http import request + + + +class IrHttp(models.AbstractModel): + _inherit = 'ir.http' + + def session_info(self): + result = super(IrHttp, self).session_info() + config_parameter = request.env['ir.config_parameter'].sudo() + result['app_system_name'] = config_parameter.get_param('app_system_name', 'odooAi') + result['app_documentation_url'] = config_parameter.get_param('app_documentation_url') + result['app_documentation_dev_url'] = config_parameter.get_param('app_documentation_dev_url') + result['app_support_url'] = config_parameter.get_param('app_support_url') + result['app_account_title'] = config_parameter.get_param('app_account_title') + result['app_account_url'] = config_parameter.get_param('app_account_url') + result['app_show_lang'] = config_parameter.get_param('app_show_lang') + result['app_show_debug'] = config_parameter.get_param('app_show_debug') + result['app_show_documentation'] = config_parameter.get_param('app_show_documentation') + result['app_show_documentation_dev'] = config_parameter.get_param('app_show_documentation_dev') + result['app_show_support'] = config_parameter.get_param('app_show_support') + result['app_show_account'] = config_parameter.get_param('app_show_account') + result['app_show_poweredby'] = config_parameter.get_param('app_show_poweredby') + # 增加多语言 + result['app_lang_list'] = self.env['res.lang'].search_read([], ['id', 'code', 'name']) + result['is_erp_manager'] = self.env.user.has_group('base.group_erp_manager') + # 增加 bar位置处理 + result['app_navbar_pos_pc'] = config_parameter.get_param('app_navbar_pos_pc', 'top') + result['app_navbar_pos_mobile'] = config_parameter.get_param('app_navbar_pos_mobile', 'top') + # 此处直接取,不用 session + result['app_debug_only_admin'] = config_parameter.get_param('app_debug_only_admin') + result['app_stop_subscribe'] = config_parameter.get_param('app_stop_subscribe') + return result diff --git a/app_odoo_customize/models/ir_module_addons_path.py b/app_odoo_customize/models/ir_module_addons_path.py new file mode 100644 index 00000000..da9ddeaa --- /dev/null +++ b/app_odoo_customize/models/ir_module_addons_path.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +import random +from odoo import api, fields, models, modules, tools, _ + + +class IrModuleAddonsPath(models.Model): + _name = "ir.module.addons.path" + _description = 'Module Addons Path' + + def _default_bg_color(self): + colors = ['#F06050', '#F4A45F', '#F7CD2E', '#6CC1ED', '#EB7E7F', '#5CC482', + '#2c8297', '#D8485E', '#9365B8', '#804967', '#475576', ] + res = '#FFFFFF' + try: + res = random.choice(colors) + except: + pass + return res + + name = fields.Char(string='Short Name') + path = fields.Char(string='Path') + path_temp = fields.Char(string='Path Temp') + color = fields.Char(default=_default_bg_color) + module_ids = fields.One2many('ir.module.module', 'addons_path_id') + module_count = fields.Integer(compute='_compute_module_count') + + def _compute_module_count(self): + for rec in self: + rec.module_count = len(rec.module_ids) + + def open_apps_view(self): + self.ensure_one() + + return {'type': 'ir.actions.act_window', + 'name': 'Apps', + 'view_mode': 'kanban,tree,form', + 'res_model': 'ir.module.module', + 'context': {}, + 'domain': [('addons_path_id', '=', self.id)], + } diff --git a/app_odoo_customize/models/ir_module_module.py b/app_odoo_customize/models/ir_module_module.py new file mode 100644 index 00000000..d351c9b6 --- /dev/null +++ b/app_odoo_customize/models/ir_module_module.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, modules, tools, _ + +import operator + + +class IrModule(models.Model): + _inherit = 'ir.module.module' + + # attention: Incorrect field names !! + # installed_version refers the latest version (the one on disk) + # latest_version refers the installed version (the one in database) + # published_version refers the version available on the repository + # installed_version = fields.Char('Latest Version', compute='_get_latest_version') + # latest_version = fields.Char('Installed Version', readonly=True) + + local_updatable = fields.Boolean('Local updatable', compute=False, default=False, store=True) + addons_path_id = fields.Many2one('ir.module.addons.path', string='Addons Path ID', readonly=True) + addons_path = fields.Char(string='Addons Path', related='addons_path_id.path', readonly=True) + license = fields.Char(readonly=True) + + def module_multi_uninstall(self): + """ Perform the various steps required to uninstall a module completely + including the deletion of all database structures created by the module: + tables, columns, constraints, etc. + """ + modules = self.browse(self.env.context.get('active_ids')) + [module.button_immediate_uninstall() for module in modules if module not in ['base', 'web']] + + # 更新翻译,当前语言 + def module_multi_refresh_po(self): + lang = self.env.user.lang + modules = self.filtered(lambda r: r.state == 'installed') + # 先清理, odoo原生经常清理不干净 + # odoo 16中,不再使用 ir.translation,直接使用json字段 + # for rec in modules: + # translate = self.env['ir.translation'].search([ + # ('lang', '=', lang), + # ('module', '=', rec.name) + # ]) + # translate.sudo().unlink() + # 再重载 + modules._update_translations(filter_lang=lang, overwrite=True) + # odoo 16翻译模式改变,仍需更新模块 + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': _("The languages that you selected have been successfully update.\ + You still need to Upgrade the apps to make it worked."), + 'type': 'success', + 'sticky': False, + 'next': {'type': 'ir.actions.act_window_close'}, + } + } + + def button_get_po(self): + self.ensure_one() + action = self.env.ref('app_odoo_customize.action_server_module_multi_get_po').sudo().read()[0] + action['context'].update({ + 'default_lang': self.env.user.lang, + }) + return action + + def update_list(self): + res = super(IrModule, self).update_list() + default_version = modules.adapt_version('1.0') + known_mods = self.with_context(lang=None).search([]) + known_mods_names = {mod.name: mod for mod in known_mods} + # 处理可更新字段, 不要compute,会出错 + for mod_name in modules.get_modules(): + mod = known_mods_names.get(mod_name) + if mod: + installed_version = self.get_module_info(mod.name).get('version', default_version) + if installed_version and mod.latest_version and operator.gt(installed_version, mod.latest_version): + local_updatable = True + else: + local_updatable = False + if mod.local_updatable != local_updatable: + mod.write({'local_updatable': local_updatable}) + + return res diff --git a/app_odoo_customize/models/ir_ui_menu.py b/app_odoo_customize/models/ir_ui_menu.py new file mode 100644 index 00000000..98a32443 --- /dev/null +++ b/app_odoo_customize/models/ir_ui_menu.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import re + +from odoo import api, fields, models, tools, _ + +MENU_ITEM_SEPARATOR = "/" +NUMBER_PARENS = re.compile(r"\(([0-9]+)\)") + + +class IrUiMenu(models.Model): + _inherit = 'ir.ui.menu' + + def _get_full_name(self, level=6): + """ Return the full name of ``self`` (up to a certain level). """ + if level <= 0: + return '...' + if self.parent_id: + try: + name = self.parent_id._get_full_name(level - 1) + MENU_ITEM_SEPARATOR + (self.name or "") + except Exception: + name = self.name or "..." + else: + name = self.name + return name + diff --git a/app_odoo_customize/models/ir_ui_view.py b/app_odoo_customize/models/ir_ui_view.py new file mode 100644 index 00000000..d8e272ca --- /dev/null +++ b/app_odoo_customize/models/ir_ui_view.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + +class View(models.Model): + _inherit = 'ir.ui.view' + + def _render_template(self, template, values=None, engine='ir.qweb'): + # if template in ['web.login', 'web.webclient_bootstrap']: + if not values: + values = {} + values["title"] = values["app_title"] = self.env['ir.config_parameter'].sudo().get_param("app_system_name", "odooAi") + return super(View, self)._render_template(template, values=values, engine=engine) diff --git a/app_odoo_customize/models/mail_thread.py b/app_odoo_customize/models/mail_thread.py new file mode 100644 index 00000000..cc882872 --- /dev/null +++ b/app_odoo_customize/models/mail_thread.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models, _ + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None): + """ 停用订阅功能. """ + ir_config = self.env['ir.config_parameter'] + app_stop_subscribe = True if ir_config.get_param('app_stop_subscribe', False) == "True" else False + if app_stop_subscribe: + return True + else: + return super(MailThread, self).message_subscribe(partner_ids, subtype_ids) + + def _message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, customer_ids=None): + """ 停用订阅功能. """ + ir_config = self.env['ir.config_parameter'] + app_stop_subscribe = True if ir_config.get_param('app_stop_subscribe', False) == "True" else False + if app_stop_subscribe: + return True + else: + return super(MailThread, self)._message_subscribe(partner_ids, subtype_ids, customer_ids) + + def _message_auto_subscribe_followers(self, updated_values, default_subtype_ids): + """ 停用订阅功能. """ + ir_config = self.env['ir.config_parameter'] + app_stop_subscribe = True if ir_config.get_param('app_stop_subscribe', False) == "True" else False + if app_stop_subscribe: + return [] + else: + return super(MailThread, self)._message_auto_subscribe_followers(updated_values, default_subtype_ids) + + def _message_auto_subscribe_notify(self, partner_ids, template): + """ 停用订阅功能. """ + ir_config = self.env['ir.config_parameter'] + app_stop_subscribe = True if ir_config.get_param('app_stop_subscribe', False) == "True" else False + if app_stop_subscribe: + return True + else: + return super(MailThread, self)._message_auto_subscribe_notify(partner_ids, template) diff --git a/app_odoo_customize/models/res_config_settings.py b/app_odoo_customize/models/res_config_settings.py new file mode 100644 index 00000000..9f840a38 --- /dev/null +++ b/app_odoo_customize/models/res_config_settings.py @@ -0,0 +1,531 @@ +# -*- coding: utf-8 -*- + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError + +_logger = logging.getLogger(__name__) + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + app_system_name = fields.Char('System Name', help="Setup System Name,which replace Odoo", + default='odooAi', config_parameter='app_system_name') + app_show_lang = fields.Boolean('Show Quick Language Switcher', + help="When enable,User can quick switch language in user menu", + config_parameter='app_show_lang') + app_show_debug = fields.Boolean('Show Quick Debug', help="When enable,everyone login can see the debug menu", + config_parameter='app_show_debug') + app_show_documentation = fields.Boolean('Show Documentation', help="When enable,User can visit user manual", + config_parameter='app_show_documentation') + # 停用 + app_show_documentation_dev = fields.Boolean('Show Developer Documentation', + help="When enable,User can visit development documentation") + app_show_support = fields.Boolean('Show Support', help="When enable,User can vist your support site", + config_parameter='app_show_support') + app_show_account = fields.Boolean('Show My Account', help="When enable,User can login to your website", + config_parameter='app_show_account') + app_show_enterprise = fields.Boolean('Show Enterprise Tag', help="Uncheck to hide the Enterprise tag", + config_parameter='app_show_enterprise') + app_show_share = fields.Boolean('Show Share Dashboard', help="Uncheck to hide the Odoo Share Dashboard", + config_parameter='app_show_share') + app_show_poweredby = fields.Boolean('Show Powered by Odoo', help="Uncheck to hide the Powered by text", + config_parameter='app_show_poweredby') + group_show_author_in_apps = fields.Boolean(string="Show Author in Apps Dashboard", implied_group='app_odoo_customize.group_show_author_in_apps', + help="Uncheck to Hide Author and Website in Apps Dashboard") + module_odoo_referral = fields.Boolean('Show Odoo Referral', help="Uncheck to remove the Odoo Referral") + + app_documentation_url = fields.Char('Documentation Url', config_parameter='app_documentation_url') + app_documentation_dev_url = fields.Char('Developer Documentation Url', config_parameter='app_documentation_dev_url') + app_support_url = fields.Char('Support Url', config_parameter='app_support_url') + app_account_title = fields.Char('My Odoo.com Account Title', config_parameter='app_account_title') + app_account_url = fields.Char('My Odoo.com Account Url', config_parameter='app_account_url') + app_enterprise_url = fields.Char('Customize Module Url(eg. Enterprise)', config_parameter='app_enterprise_url') + app_ribbon_name = fields.Char('Show Demo Ribbon', config_parameter='app_ribbon_name') + app_navbar_pos_pc = fields.Selection(string="Navbar PC", selection=[ + ('top', 'Top(Default)'), + ('bottom', 'Bottom'), + # ('left', 'Left'), + ], config_parameter='app_navbar_pos_pc') + app_navbar_pos_mobile = fields.Selection(string="Navbar Mobile", selection=[ + ('top', 'Top(Default)'), + ('bottom', 'Bottom'), + # ('left', 'Left'), + ], config_parameter='app_navbar_pos_mobile') + + # 安全与提速 + app_debug_only_admin = fields.Boolean('Debug for Admin', config_parameter='app_debug_only_admin', + help="Check to only Debug / Debug Assets for Odoo Admin. Deny debug from url for other user.") + app_stop_subscribe = fields.Boolean('Stop Odoo Subscribe', help="Check to stop subscribe and follow. This to make odoo speed up.", + config_parameter='app_stop_subscribe') + # 处理额外模块 + module_app_odoo_doc = fields.Boolean("Help Document Anywhere", help='Get Help Documentation on current odoo operation or topic.') + module_app_chatgpt = fields.Boolean("Ai Center", help='Use Ai to boost you business.') + + # 应用帮助文档 + app_doc_root_url = fields.Char('Help of topic domain', config_parameter='app_doc_root_url', default='https://odooai.cn') + + @api.model + def set_module_url(self, rec=None): + if not self._app_check_sys_op(): + raise UserError(_('Not allow.')) + config_parameter = self.env['ir.config_parameter'].sudo() + app_enterprise_url = config_parameter.get_param('app_enterprise_url', 'https://www.odooai.cn') + modules = self.env['ir.module.module'].search([('license', 'like', 'OEEL%'), ('website', '!=', False)]) + if modules: + sql = "UPDATE ir_module_module SET website = '%s' WHERE id IN %s" % (app_enterprise_url, tuple(modules.ids)) + try: + self._cr.execute(sql) + except Exception as e: + pass + + # 清数据,o=对象, s=序列 + def _remove_app_data(self, o, s=[]): + if not self._app_check_sys_op(): + raise UserError(_('Not allow.')) + for line in o: + # 检查是否存在 + try: + if not self.env['ir.model']._get(line): + continue + except Exception as e: + _logger.warning('remove data error get ir.model: %s,%s', line, e) + continue + obj_name = line + obj = self.pool.get(obj_name) + if not obj: + # 有时安装出错数据乱,没有 obj 但有 table + t_name = obj_name.replace('.', '_') + else: + t_name = obj._table + + sql = "delete from %s" % t_name + # 增加多公司处理 + try: + self._cr.execute(sql) + self._cr.commit() + except Exception as e: + _logger.warning('remove data error: %s,%s', line, e) + # 更新序号 + for line in s: + domain = ['|', ('code', '=ilike', line + '%'), ('prefix', '=ilike', line + '%')] + try: + seqs = self.env['ir.sequence'].sudo().search(domain) + if seqs.exists(): + seqs.write({ + 'number_next': 1, + }) + except Exception as e: + _logger.warning('reset sequence data error: %s,%s', line, e) + return True + + def remove_sales(self): + to_removes = [ + # 清除销售单据 + 'sale.order.line', + 'sale.order', + # 销售提成,自用 + # 'sale.commission.line', + # 不能删除报价单模板 + 'sale.order.template.option', + 'sale.order.template.line', + 'sale.order.template', + ] + seqs = [ + 'sale', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_product(self): + to_removes = [ + # 清除产品数据 + 'product.product', + 'product.template', + ] + seqs = [ + 'product.product', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_product_attribute(self): + to_removes = [ + # 清除产品属性 + 'product.attribute.value', + 'product.attribute', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_pos(self): + to_removes = [ + # 清除POS单据 + 'pos.payment', + 'pos.order.line', + 'pos.order', + 'pos.session', + ] + seqs = [ + 'pos.', + ] + res = self._remove_app_data(to_removes, seqs) + + # 更新要关帐的值,因为 store=true 的计算字段要重置 + + try: + statement = self.env['account.bank.statement'].search([]) + for s in statement: + s._end_balance() + except Exception as e: + _logger.error('reset sequence data error: %s', e) + return res + + def remove_purchase(self): + to_removes = [ + # 清除采购单据 + 'purchase.order.line', + 'purchase.order', + 'purchase.requisition.line', + 'purchase.requisition', + ] + seqs = [ + 'purchase.', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_expense(self): + to_removes = [ + # 清除 + 'hr.expense.sheet', + 'hr.expense', + 'hr.payslip', + 'hr.payslip.run', + ] + seqs = [ + 'hr.expense.', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_mrp(self): + to_removes = [ + # 清除生产单据 + 'mrp.workcenter.productivity', + 'mrp.workorder', + # 'mrp.production.workcenter.line', + 'change.production.qty', + 'mrp.production', + # 'mrp.production.product.line', + 'mrp.unbuild', + 'change.production.qty', + # 'sale.forecast.indirect', + # 'sale.forecast', + ] + seqs = [ + 'mrp.', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_mrp_bom(self): + to_removes = [ + # 清除生产BOM + 'mrp.bom.line', + 'mrp.bom', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_inventory(self): + to_removes = [ + # 清除库存单据 + 'stock.quant', + 'stock.move.line', + 'stock.package_level', + 'stock.quantity.history', + 'stock.quant.package', + 'stock.move', + # 'stock.pack.operation', + 'stock.picking', + 'stock.scrap', + 'stock.picking.batch', + 'stock.inventory.adjustment.name', + 'stock.valuation.layer', + 'stock.lot', + # 'stock.fixed.putaway.strat', + 'procurement.group', + ] + seqs = [ + 'stock.', + 'picking.', + 'procurement.group', + 'product.tracking.default', + 'WH/', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_account(self): + to_removes = [ + # 清除财务会计单据 + 'payment.transaction', + # 'account.voucher.line', + # 'account.voucher', + # 'account.invoice.line', + # 'account.invoice.refund', + # 'account.invoice', + 'account.bank.statement.line', + 'account.payment', + 'account.batch.payment', + 'account.analytic.line', + 'account.analytic.account', + 'account.partial.reconcile', + 'account.move.line', + 'hr.expense.sheet', + 'account.move', + ] + res = self._remove_app_data(to_removes, []) + + # extra 更新序号 + domain = [ + ('company_id', '=', self.env.company.id), + '|', ('code', '=ilike', 'account.%'), + '|', ('prefix', '=ilike', 'BNK1/%'), + '|', ('prefix', '=ilike', 'CSH1/%'), + '|', ('prefix', '=ilike', 'INV/%'), + '|', ('prefix', '=ilike', 'EXCH/%'), + '|', ('prefix', '=ilike', 'MISC/%'), + '|', ('prefix', '=ilike', '账单/%'), + ('prefix', '=ilike', '杂项/%') + ] + try: + seqs = self.env['ir.sequence'].search(domain) + if seqs.exists(): + seqs.write({ + 'number_next': 1, + }) + except Exception as e: + _logger.error('reset sequence data error: %s,%s', domain, e) + return res + + def remove_account_chart(self): + company_id = self.env.company.id + self = self.with_company(self.env.company) + to_removes = [ + # 清除财务科目,用于重设 + 'res.partner.bank', + # 'account.invoice', + 'account.payment', + 'account.bank.statement', + # 'account.tax.account.tag', + 'account.tax', + # 'wizard_multi_charts_accounts', + 'account.account', + ] + # todo: 要做 remove_hr,因为工资表会用到 account + # 更新account关联,很多是多公司字段,故只存在 ir_property,故在原模型,只能用update + try: + field1 = self.env['ir.model.fields']._get('product.template', "taxes_id").id + field2 = self.env['ir.model.fields']._get('product.template', "supplier_taxes_id").id + + sql = "delete from ir_default where (field_id = %s or field_id = %s) and company_id=%d" \ + % (field1, field2, company_id) + sql2 = "update account_journal set bank_account_id=NULL where company_id=%d;" % company_id + self._cr.execute(sql) + self._cr.execute(sql2) + + self._cr.commit() + except Exception as e: + _logger.error('remove data error: %s,%s', 'account_chart: set tax and account_journal', e) + + # 增加对 pos的处理 + if self.env['ir.model']._get('pos.config'): + self.env['pos.config'].write({ + 'journal_id': False, + }) + # todo: 以下处理参考 res.partner的合并,将所有m2o的都一次处理,不需要次次找模型 + # partner 处理 + try: + rec = self.env['res.partner'].search([]) + for r in rec: + r.write({ + 'property_account_receivable_id': None, + 'property_account_payable_id': None, + }) + except Exception as e: + _logger.error('remove data error: %s,%s', 'account_chart', e) + # 品类处理 + try: + rec = self.env['product.category'].search([]) + for r in rec: + r.write({ + 'property_account_income_categ_id': None, + 'property_account_expense_categ_id': None, + 'property_account_creditor_price_difference_categ': None, + 'property_stock_account_input_categ_id': None, + 'property_stock_account_output_categ_id': None, + 'property_stock_valuation_account_id': None, + }) + except Exception as e: + pass + # 产品处理 + try: + rec = self.env['product.template'].search([]) + for r in rec: + r.write({ + 'property_account_income_id': None, + 'property_account_expense_id': None, + }) + except Exception as e: + pass + # 库存计价处理 + try: + rec = self.env['stock.location'].search([]) + for r in rec: + r.write({ + 'valuation_in_account_id': None, + 'valuation_out_account_id': None, + }) + except Exception as e: + pass # raise Warning(e) + + try: + rec = self.env['account.journal'].search([]) + rec.write({ + 'default_account_id': False, + 'suspense_account_id': False + }) + except Exception as e: + pass + + seqs = [] + + res = self._remove_app_data(to_removes, seqs) + return res + + def remove_project(self): + to_removes = [ + # 清除项目 + 'account.analytic.line', + 'project.task', + # 'project.forecast', + 'project.update', + 'project.project', + 'project.collaborator', + 'project.milestone', + # 'project.project.stage', + 'project.task.recurrence', + # 表名为 project_task_user_rel + 'project.task.stage.personal', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_quality(self): + to_removes = [ + # 清除质检数据 + 'quality.check', + 'quality.alert', + # 'quality.point', + # 'quality.alert.stage', + # 'quality.alert.team', + # 'quality.point.test_type', + # 'quality.reason', + # 'quality.tag', + ] + seqs = [ + 'quality.check', + 'quality.alert', + # 'quality.point', + ] + return self._remove_app_data(to_removes, seqs) + + def remove_quality_setting(self): + to_removes = [ + # 清除质检设置 + 'quality.point', + 'quality.alert.stage', + 'quality.alert.team', + 'quality.point.test_type', + 'quality.reason', + 'quality.tag', + ] + return self._remove_app_data(to_removes) + + def remove_website(self): + to_removes = [ + # 清除网站数据,w, w_blog + 'blog.tag.category', + 'blog.tag', + 'blog.post', + 'blog.blog', + 'product.wishlist', + 'website.published.multi.mixin', + 'website.published.mixin', + 'website.multi.mixin', + 'website.visitor', + 'website.rewrite', + 'website.seo.metadata', + # 'website.page', + # 'website.menu', + # 'website', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_message(self): + to_removes = [ + # 清除消息数据 + 'mail.message', + 'mail.followers', + 'mail.activity', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_workflow(self): + to_removes = [ + # 清除工作流 + # 'wkf.workitem', + # 'wkf.instance', + ] + seqs = [] + return self._remove_app_data(to_removes, seqs) + + def remove_all_biz(self): + self.remove_account() + self.remove_quality() + self.remove_inventory() + self.remove_purchase() + self.remove_mrp() + self.remove_sales() + self.remove_project() + self.remove_pos() + self.remove_expense() + self.remove_message() + return True + + def reset_cat_loc_name(self): + ids = self.env['product.category'].search([ + ('parent_id', '!=', False) + ], order='complete_name') + for rec in ids: + try: + rec._compute_complete_name() + except: + pass + ids = self.env['stock.location'].search([ + ('location_id', '!=', False), + ('usage', '!=', 'views'), + ], order='complete_name') + for rec in ids: + try: + rec._compute_complete_name() + except: + pass + return True + + def action_set_app_doc_root_to_my(self): + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + self.app_doc_root_url = base_url + + # def action_set_all_to_app_doc_root_url(self): + # if self.app_doc_root_url: diff --git a/app_odoo_customize/models/web_environment_ribbon_backend.py b/app_odoo_customize/models/web_environment_ribbon_backend.py new file mode 100644 index 00000000..b1ec801d --- /dev/null +++ b/app_odoo_customize/models/web_environment_ribbon_backend.py @@ -0,0 +1,36 @@ +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class WebEnvironmentRibbonBackend(models.AbstractModel): + + _name = "web.environment.ribbon.backend" + _description = "Web Environment Ribbon Backend" + + @api.model + def _prepare_ribbon_format_vals(self): + return {"db_name": self.env.cr.dbname} + + @api.model + def _prepare_ribbon_name(self): + name_tmpl = self.env["ir.config_parameter"].sudo().get_param("app_ribbon_name") or False + vals = self._prepare_ribbon_format_vals() + return name_tmpl and name_tmpl.format(**vals) or name_tmpl + + @api.model + def get_environment_ribbon(self): + """ + This method returns the ribbon data from ir config parameters + :return: dictionary + """ + ir_config_model = self.env["ir.config_parameter"] + name = self._prepare_ribbon_name() + return { + "name": name, + "color": ir_config_model.sudo().get_param("app_ribbon_color"), + "background_color": ir_config_model.sudo().get_param( + "app_ribbon_background_color" + ), + } diff --git a/app_odoo_customize/readme.md b/app_odoo_customize/readme.md new file mode 100644 index 00000000..90560133 --- /dev/null +++ b/app_odoo_customize/readme.md @@ -0,0 +1,92 @@ +##App Odoo Customize(Debranding Title,Language,Documentation,Quick Debug,Quick Data Clear) +============ +White label odoo. +Support Odoo 13,12, 11, 10, 9. Including communicate and enterprise version. +You can config odoo, make it look like your own platform. +1. Deletes Odoo label in footer +2. Replaces "Odoo" in Windows title +3. Customize Documentation, Support, About links and title in usermenu +4. Adds "Developer mode" link to the top right-hand User Menu. +5. Adds Quick Language Switcher to the top right-hand User Menu. +6. Adds Country flags to the top right-hand User Menu. +7. Adds English and Chinese user documentation access to the top right-hand User Menu. +8. Adds developer documentation access to the top right-hand User Menu. +9. Customize "My odoo.com account" button +10. Standalone setting panel, easy to setup. +11. Provide 236 country flags. +12. Multi-language Support. +13. Change Powered by Odoo in login screen.(Please change '../views/app_odoo_customize_view.xml' #15) +14. Quick delete test data in Apps: Sales/POS/Purchase/MRP/Inventory/Accounting/Project/Message/Workflow etc. +15. Reset All the Sequence to beginning of 1: SO/PO/MO/Invoice... +16. Fix odoo reload module translation bug while enable english language +17. Stop Odoo Auto Subscribe(Performance Improve) +18. Show/Hide Author and Website in Apps Dashboard +19. One Click to clear all data (Sometime pls click twice) +20. Show quick upgrade in app dashboard, click to show module info not go to odoo.com +21. Can clear and reset account chart. Be cautious +22. Update online manual and developer document to odoo17 +23. Add reset or clear website blog data +24. Customize Odoo Native Module(eg. Enterprise) Url +25. Add remove expense data +26. Add multi uninstall modules +27. Add odoo boost modules link. + +This module can help to white label the Odoo. +Also helpful for training and support for your odoo end-user. +The user can get the help document just by one click. + +For more support +https://www.odooai.cn + +## 在符合odoo开源协议的前提下,去除odoo版权信息,自定义你的odoo +可完全自行设置下列 odoo 选项,让 odoo 看上去像是你的软件产品 +支持Odoo 13,12, 11, 10, 9 版本,社区版企业版通用 + +1. 删除菜单导航页脚的 Odoo 标签 +2. 将弹出窗口中 "Odoo" 设置为自定义名称 +3. 自定义用户菜单中的 Documentation, Support, About 的链接 +4. 在用户菜单中增加快速切换开发模式 +5. 在用户菜单中增加快速切换多国语言 +6. 对语言菜单进行美化,设置国旗图标 +7. 在用户菜单中增加中/英文用户手册,可以不用翻墙加速了 +8. 在用户菜单中增加开发者手册,含python教程,jquery参考,Jinja2模板,PostgresSQL参考 +9. 在用户菜单中自定义"My odoo.com account" +10. 单独设置面板,每个选项都可以自定义 +11. 提供236个国家的国旗文件(部份需要自行设置文件名) +12. 多语言版本 +13. 自定义登陆界面中的 Powered by Odoo +14. 快速删除测试数据,支持模块包括:销售/POS门店/采购/生产/库存/会计/项目/消息与工作流等. +15. 将各类单据的序号重置,从1开始,包括:SO/PO/MO/Invoice 等 +16. 修复odoo启用英文后模块不显示中文的Bug +17. 可停用odoo自动订阅功能,避免“同样对象关注2次”bug,同时提升性能 +18. 显示/隐藏应用的作者和网站-在应用安装面板中 +19. 一键清除所有数据(视当前数据情况,有时需点击2次) +20. 在应用面板显示快速升级按键,点击时不会导航至 odoo.com +21. 清除并重置会计科目表 +22. 全新升级将odoo12用户及开发手册导航至国内网站,或者自己定义的网站 +23. 增加清除网站数据功能 +24. 自定义 odoo 原生模块跳转的url(比如企业版模块) +25. 增加删除费用报销数据功能 +26. 增加批量卸载模块功能 +27. 增加odoo加速功能 + +使用方法:将解压后的 app_odoo_customize 放到 odoo的 addons目录下,激活开发者模式,应用-->更新应用列表, +找到 "App odoo Customize"模块,安装即可。 + + +## 其它技术资源: +# Odoo12在线用户手册(长期更新) +# https://www.odooai.cn/documentation/user/12.0/en/index.html + +# Odoo12在线开发者手册(长期更新) +# https://www.odooai.cn/documentation/12.0/index.html + +# Odoo10在线中文用户手册(长期更新) +# https://www.odooai.cn/documentation/user/10.0/zh_CN/index.html + +# Odoo10离线中文用户手册下载 +# https://www.odooai.cn/odoo10_user_manual_document_offline/ +# Odoo10离线开发手册下载-含python教程,jquery参考,Jinja2模板,PostgresSQL参考(odoo开发必备) +# https://www.odooai.cn/odoo10_developer_document_offline/ +# description: + diff --git a/app_odoo_customize/security/ir.model.access.csv b/app_odoo_customize/security/ir.model.access.csv new file mode 100644 index 00000000..607dd696 --- /dev/null +++ b/app_odoo_customize/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ir_config_parameter_system,ir_config_parameter_system,base.model_ir_config_parameter,base.group_user,1,0,0,0 +access_ir_module_addons_path_user,ir_module_addons_path_user,app_odoo_customize.model_ir_module_addons_path,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/app_odoo_customize/security/res_groups.xml b/app_odoo_customize/security/res_groups.xml new file mode 100644 index 00000000..c4ae9968 --- /dev/null +++ b/app_odoo_customize/security/res_groups.xml @@ -0,0 +1,19 @@ + + + + + Show Author in Apps Dashboard + + + + Show Quick Upgrade in Apps Dashboard + + + + + + + + + \ No newline at end of file diff --git a/app_odoo_customize/static/description/app_web_enterprise_03.jpg b/app_odoo_customize/static/description/app_web_enterprise_03.jpg new file mode 100644 index 00000000..904ea326 Binary files /dev/null and b/app_odoo_customize/static/description/app_web_enterprise_03.jpg differ diff --git a/app_odoo_customize/static/description/banner.gif b/app_odoo_customize/static/description/banner.gif new file mode 100644 index 00000000..3cf5883e Binary files /dev/null and b/app_odoo_customize/static/description/banner.gif differ diff --git a/app_odoo_customize/static/description/banner.png b/app_odoo_customize/static/description/banner.png new file mode 100644 index 00000000..9a7b359f Binary files /dev/null and b/app_odoo_customize/static/description/banner.png differ diff --git a/app_odoo_customize/static/description/banner_ai.png b/app_odoo_customize/static/description/banner_ai.png new file mode 100644 index 00000000..e599d913 Binary files /dev/null and b/app_odoo_customize/static/description/banner_ai.png differ diff --git a/app_odoo_customize/static/description/cnreadme.png b/app_odoo_customize/static/description/cnreadme.png new file mode 100644 index 00000000..294a767d Binary files /dev/null and b/app_odoo_customize/static/description/cnreadme.png differ diff --git a/app_odoo_customize/static/description/icon.png b/app_odoo_customize/static/description/icon.png new file mode 100644 index 00000000..4158eace Binary files /dev/null and b/app_odoo_customize/static/description/icon.png differ diff --git a/app_odoo_customize/static/description/index.html b/app_odoo_customize/static/description/index.html new file mode 100644 index 00000000..ef61b507 --- /dev/null +++ b/app_odoo_customize/static/description/index.html @@ -0,0 +1,327 @@ + + + + + + +
+

odoo 17 Tweak,Ai Employee,Boost,Customize All in One.

+

Customize,UI,Boost,Security,Data,Development Enhance

+

Also you can follow out github for more odoo app.

+

https://github.com/guohuadeng/app-odoo

+

odoo优化48项大全,oem去Logo,odoo提速与数据清理

+
+ + +
+
+
+

This is a Long Term Support Apps.Update: v17.24.03.13

+
+
    +
  • 1. Deletes Odoo label in footer
  • +
  • 2. Replaces "Odoo" in Windows title
  • +
  • 3. Customize Documentation, Support, About links and title in usermenu
  • +
  • 4. Adds "Developer mode" link to the top right-hand User Menu.
  • +
  • 5. Adds Quick Language Switcher to the top right-hand User Menu.
  • +
  • 6. Adds Country flags to the top right-hand User Menu.
  • +
  • 7. Adds English and Chinese user documentation access to the top right-hand User Menu.
  • +
  • 8. Adds developer documentation access to the top right-hand User Menu.
  • +
  • 9. Customize "My odoo.com account" button
  • +
  • 10. Standalone setting panel, easy to setup.
  • +
  • 11. Provide 236 country flags.
  • +
  • 12. Multi-language Support.
  • +
  • 13. Change Powered by Odoo in login screen.(Please change '../views/app_odoo_customize_view.xml' #15)
  • +
  • 14. Quick delete test data in Apps: Sales/POS/Purchase/MRP/Inventory/Accounting/Project/Base Models.
  • +
  • 15. Reset All the Sequence to beginning of 1: SO/PO/MO/Invoice...
  • +
  • 16. Fix odoo reload module translation bug while enable english language
  • +
  • 17. Stop Odoo Auto Subscribe(Performance Improve)
  • +
  • 18. Show/Hide Author and Website in Apps Dashboard (odoo 11 only)
  • +
  • 19. One Click to clear all data (Sometime pls click twice)
  • +
  • 20. Show quick upgrade in app dashboard, click to show module info not go to odoo.com
  • +
  • 21. Can clear and reset account chart. Be cautious.
  • +
  • 22. Update online manual and developer document to odoo17.
  • +
  • 23. Add reset or clear website blog data
  • +
  • 24. Customize Odoo Native Module(eg. Enterprise) Url
  • +
  • 25. Add remove expense data
  • +
  • 26. Add multi uninstall modules
  • +
  • 27. Add odoo boost modules link.
  • +
  • 28. Easy Menu manager.
  • +
  • 29. Apps version compare. Add Install version in App list. Add Local updatable filter in app list.
  • +
  • 30. 1 key export app translate file like .po file.
  • +
  • 32. Fix odoo bug of complete name bug of product category and stock location..
  • +
  • 33. Add Demo Ribbon Setting.
  • +
  • 34. Add Remove all quality data.
  • +
  • 35. Fixed for odoo 14.
  • +
  • 36. Add refresh translate for multi module.
  • +
  • 37. Easy noupdate manage for External Identifiers(xml_id).
  • +
  • 38. Add Draggable Dialog enable.
  • +
  • 39. Only erp manager can see debug menu.
  • +
  • 40. Fix support for enterprise version.
  • +
  • 41. Fix odoo bug, when click Preferences menu not hide in mobile.
  • +
  • 42. Add menu navbar setup for top or bottom. navigator footer support.
  • +
  • 43. Check to only Debug / Debug Assets for Odoo Admin. Deny debug from url for other user.
  • +
  • 44. Check to stop subscribe and follow. This to make odoo speed up.
  • +
  • 45. Add addons path info to module.
  • +
  • 46. Add Help documentation anywhere. easy get help for any odoo operation or action.
  • +
  • 47. Add ai robot app integration. Use Ai as your employee.
  • +
  • 48. Add menu debug information of action and xml id.
  • +
+
+

If you want to change the login page.

+

Please modify the file \views\app_odoo_customize_views.xml

+
+

+ This module can help to white label the Odoo. + Also helpful for training and support for your odoo end-user.
+ The user can get the help document just by one click. +

+
+
+
+ +
+
+

Help Document Anywhere

+

Get Help Documentation on current odoo operation or topic.

+
+ +
+

You can use you company logo for the document with you help document

+
+ +
+

You can set extra help doc for any addons

+
+ +
+

Must in development mode

+
+ +
+
+
+ +
+
+

New Ai Center Support

+

You can install Ai service like chatgpt and google bard and azure openai.

+
+ +
+
+
+ +
+
+

Odoo Customize(Debranding Title,Language,Documentation,Quick Debug)

+
+
+ +
+
+
+

Multi-language support: Chinese ready

+
+
+ +
+ +
+
+ +
+
+

How to use: Go to Settings -> odooAi -> Customize Boost Odoo

+
+
+ +
+
+
+ +
+
+

OEM White label your odoo

+

Replaces "Odoo" in Windows title, Deletes Odoo label in footer, Customize all odoo link to my link

+
+ +
+
+
+ +
+
+

Mobile Enhance. Add menu navbar setup for top or bottom. navigator footer support.

+

Easy set navbar on bottom or top.

+
+ +
+
+
+ +
+
+

odoo SECURITY AND BOOST

+

Eonly Debug / Debug Assets for Odoo Admin. Deny debug from url for other user.stop subscribe and follow. This to make odoo speed up.

+
+ +
+
+
+ +
+
+

Add quick operation for odoo modules.

+

Refresh translate. Upgrade, Uninstall, help on topic

+
+ +
+

Easy mass module operation

+
+ +
+

Easy Export translate follow your language

+
+ +
+
+
+
+
+

Add Draggable and sizeable Dialog enable.

+
+ +
+
+
+ +
+
+

Show/Hide Author and Website in Apps Dashboard

+
+

Before

+
+ +
+
+

After uncheck "Show Author in Apps Dashboard"

+
+ +
+
+
+ +
+
+

Show quick upgrade in app dashboard, click to show module info not go to odoo.com

+
+

No more redirect to odoo.com

+
+ +
+
+
+ +
+
+

Setup more flags: just rename the flag pic to locale code of the country

+

You can find the pictures in "\app-odoo\app_odoo_customize\static\src\img\flags"

+
+
+ +
+
+
+ +
+
+

Customize Extra enterprise Module Url(eg. Enterprise).

+
+
+ +
+
+
+ +
+
+

Quick Delete test Data.

+

You can quickly delete all the test data in Apps: Sales/POS/Purchase/MRP/Inventory/Accounting/Message/Workflow etc.

+
+
+ +
+
+
+
+ +
+
+

Multi-language Support..

+

+
+ +
+
+ +

+ Also you can + + get more powerful odoo apps from us. + . like [superbar widget] +

+
+ + + +
+
+

This moduld allows user to quickly customize and debranding Odoo. Quick debug, Language Switcher, + Online Documentation Access,Quick Data Clear.

+

Support odoo 17,16,15,14,13,12,11,10,9. Including community and enterprise version.

+
+
+ +
+
+
+

Technical Help & Support

+
+
+
+

+ For any type of technical help & support requests, Feel free to contact us

+ + odoo@china.com +

+ Via QQ: 300883 (App user would not get QQ or any other IM support. Only for odoo project customize.)

+ + 300883@qq.com +
+
+

+ Visit our website for more support.

+

https://www.odooai.cn

+
+
+
+
+ \ No newline at end of file diff --git a/app_odoo_customize/static/description/index_cn.html b/app_odoo_customize/static/description/index_cn.html new file mode 100644 index 00000000..b6e68eff --- /dev/null +++ b/app_odoo_customize/static/description/index_cn.html @@ -0,0 +1,416 @@ + + + + + + +
+

odoo 17 Tweak,Ai Employee,Boost,Customize All in One.

+

Customize,UI,Boost,Security,Data,Development Enhance

+

Also you can follow out github for more odoo app.

+

https://github.com/guohuadeng/app-odoo

+

odoo优化48项大全,oem去Logo,odoo提速与数据清理

+
+ +
+
+
+

+ 如果你使用企业版,请获取我们针对企业版的定制优化模块
+ + 从odoo官方应用商店获取 "app_web_enterprise" 应用模块 + +

+

这是一个长期更新维护的odoo应用模块

+
+

Update: v24.03.13

+

安全优化,避免越权操作

+

47. 增加 Ai 如 ChatGpt 或百度ai的快速安装选项.

+

48. 增加菜单Debug功能.

+

Update: v16.24.01.11

+

应用界面升级,整合进odoo通用设置

+

46. 增加快速帮助文档,可以在任意操作中获取相关的 odoo 帮助.

+

45. 为应用模块增加模块路径信息

+

Update: v16.23.08.15

+

44. 可配置停用自动用户订阅功能,这会提速odoo,减少资源消耗

+

43. 可设置只允许管理员进入开发者模式,不可在url中直接debut=1来调试

+

Update: v16.23.07.25

+

42. 可设置导航栏在上方还是下方,分开桌面与移动端.

+

Update: v16.23.07.14

+

41. 修正odoo原生移动端菜单bug,点击个人设置时,原菜单不隐藏等

+

Update: v16.23.05.04

+

Fix bug in mobile view in popup menu.

+

Update: v16.23.02.17

+

Fix odoo Debug and Debug Assets.

+

Update: v16.23.02.06

+

Fix odoo ribbon.

+

Fix odoo translate update.

+

Update: v16.22.10.21

+

Add odoo16 support.

+

Update: v15.21.10.21

+

odoo15 remove data optimization. Easy and fast remove big data.

+

Update: v15.22.03.15

+

Add odoo15 supported.

+

Update: v13.21.08.04

+

40. 增强对企业版的支持

+

39. 只有系统管理员可以操作快速debug

+

38. 对话框可拖拽,可缩放,自动大屏优化

+

37. noupdate字段的快速管理,主要针对 xml_id.

+

Update: v14.21.03.31

+

Account date reset, and account chart reset support multi company reset.

+

Update: v14.21.03.03

+

Fixed odoo Title.

+

Update: v14.20.12.29

+

36. 可为多个模块强制更新翻译

+

Update: v14.20.08.29

+

35. 优化至odoo14适用

+

34. Add Remove all quality data(for odoo Enterprise)

+

33. Add Demo Ribbon Setting.

+

32. Fix odoo bug of complete name bug of product category and stock location.

+

Update: v13.20.08.29

+

36. Add odoo 14 support.

+

Update: v13.20.04.12

+

31. Show or hide odoo Referral in the top menu.

+

Update: v13.20.03.23

+

30. 1 key export app translate file like .po file.

+

Update: v13.20.02.25

+

28. Easy Menu manager.

+

29. Add Install version in App list. Add Local updatable filter in app list.

+

Update: v13.19.10.19

+

27. Add Odoo 13 support, all function add

+

Update: v12.19.04.30

+

26. Add multi uninstall modules

+

Update: v12.19.04.18

+

25. Add remove expense data

+

Update: v12.19.04.17

+

Add Customize Odoo Native Module(eg. Enterprise) Url

+

Update: v12.19.3.15

+

Add reset or clear website blog data

+

Update: v12.19.3.12

+

Optimize chinese translate and document.

+

Fix bug: Data reset.

+

Update: v12.19.1.20

+

Fix bug: Save config error.

+

Update: v12.19.1.05

+

Fix bug: If you install Muk moudle, odooapp customize would pop error like "attachment_location". Sometime you need to uninstall this app and restart odoo, install again to take affect.

+

Update: v12.0.12.25

+

add 22. Update online manual and developer document to odoo17.

+

Update: v12.0.11.08

+

Add 21. Reset Account Chart.

+

Update: v12.0.9.30

+

Add 20. Show quick upgrade in app dashboard

+

Update: v12.0.9.25

+

Now ready for Odoo 13,12, please email to me. guohuadeng@hotmail.com

+

Update: v12.0.7.23

+

Fix Login bug when install website.

+

Add 19. One Click to clear all data (Sometime pls click twice)

+

Add 18. Show/Hide Author and Website in Apps Dashboard

+

More Powerful addons:

+

+ odooai.cn Awesome Odoo + Addons +

+
+

支持odoo 17,16,15,14,13,12, 11, 10, 9 版本,社区版企业版通用

+
    +
  • 1. 删除菜单导航页脚的 Odoo 标签
  • +
  • 2. 将弹出窗口中 "Odoo" 设置为自定义名称
  • +
  • 3. 自定义用户菜单中的 Documentation, Support, About 的链接
  • +
  • 4. 在用户菜单中增加快速切换开发模式
  • +
  • 5. 在用户菜单中增加快速切换多国语言
  • +
  • 6. 对语言菜单进行美化,设置国旗图标
  • +
  • 7. 在用户菜单中增加中/英文用户手册,可以不用翻墙加速了
  • +
  • 8. 在用户菜单中增加开发者手册,含python教程,jquery参考,Jinja2模板,PostgresSQL参考
  • +
  • 9. 在用户菜单中自定义"My odoo.com account"
  • +
  • 10. 单独设置面板,每个选项都可以自定义
  • +
  • 11. 提供236个国家的国旗文件(部份需要自行设置文件名)
  • +
  • 12. 多语言版本
  • +
  • 13. 自定义登陆界面中的 Powered by Odoo
  • +
  • 14. 快速删除测试数据,支持模块包括:销售/POS门店/采购/生产/库存/会计/项目/消息与工作流等.
  • +
  • 15. 将各类单据的序号重置,从1开始,包括:SO/PO/MO/Invoice 等
  • +
  • 16. 修复odoo启用英文后模块不显示中文的Bug
  • +
  • 17. 可停用odoo自动订阅功能,避免“同样对象关注2次”bug,同时提升性能
  • +
  • 18. 显示/隐藏应用的作者和网站-在应用安装面板中
  • +
  • 19. 一键清除所有数据(视当前数据情况,有时需点击2次)
  • +
  • 20. 在应用面板显示快速升级按键,点击时不会导航至 odoo.com
  • +
  • 21. 清除并重置会计科目表
  • +
  • 22. 全新升级将odoo12用户及开发手册导航至国内网站,或者自己定义的网站
  • +
  • 23. 增加清除网站数据功能
  • +
  • 24. 自定义 odoo 原生模块跳转的url(比如企业版模块)
  • +
  • 25. 增加删除费用报销数据功能
  • +
  • 26. 增加批量卸载模块功能
  • +
  • 27. 增加odoo加速功能
  • +
  • 28. 快速管理顶级菜单
  • +
  • 29. App版本比较,快速查看可本地更新的模块
  • +
  • 30. 一键导出翻译文件 po
  • +
  • 31. 显示或去除 odoo 推荐
  • +
  • 32. 增加修复品类及区位名的操作
  • +
  • 33. 增加 Demo 的显示设置
  • +
  • 34. 增加清除质检数据
  • +
  • 35. 优化至odoo14适用
  • +
  • 36. 可为多个模块强制更新翻译
  • +
  • 37. noupdate字段的快速管理,主要针对 xml_id
  • +
  • 38. 对话框可拖拽,可缩放,自动大屏优化
  • +
  • 39. 只有系统管理员可以操作快速debug
  • +
  • 40. 增强对企业版的支持
  • +
  • 41. 修正odoo原生移动端菜单bug,点击个人设置时,原菜单不隐藏等
  • +
  • 42. 可设置导航栏在上方还是下方,分开桌面与移动端.
  • +
  • 43. 可设置只允许管理员进入开发者模式,不可在url中直接debut=1来调试
  • +
  • 44. 可配置停用自动用户订阅功能,这会提速odoo,减少资源消耗
  • +
  • 45. 为应用模块增加模块路径信息
  • +
  • 46. 增加快速帮助文档,可以在任意操作中获取相关的 odoo 帮助.
  • + +
+

此模块用于 OEM 你的 odoo,增加自己公司的 Logo 相关信息及版权相关信息。 +
+ 便于快速开发、快速培训、可速查找odoo帮助文档。也可以方便的进行odoo数据清理与重置。 +

+
+
+
+ +
+
+

odoo系统内快速帮助

+

增加快速帮助文档,可以在任意操作中获取相关的 odoo 帮助.

+
+ +
+

自动设置文章Logo为公司Logo

+
+ +
+

可手动设置模块帮助链接

+
+ +
+

注意必须在开发者模式

+
+ +
+
+
+ +
+
+

Ai服务中心支持

+

你可以安装最新的 ChatGPT 或者 微软、谷歌等Ai.

+
+ +
+
+
+ +
+
+

Odoo 快速自定义,OEM改造,快速Debug、语言切换等全面增强

+
+
+ +
+
+
+

多语言支持,中文已处理

+
+
+ +
+ +
+
+ +
+
+

如何使用: 转到 设置 -> odooAi -> 定制与增强

+
+
+ +
+
+
+ +
+
+

OEM White label your odoo

+

Replaces "Odoo" in Windows title, Deletes Odoo label in footer, Customize all odoo link to my link

+
+ +
+
+
+ +
+
+

Mobile Enhance. Add menu navbar setup for top or bottom. navigator footer support.

+

Easy set navbar on bottom or top.

+
+ +
+
+
+ +
+
+

odoo SECURITY AND BOOST

+

Eonly Debug / Debug Assets for Odoo Admin. Deny debug from url for other user.stop subscribe and follow. This to make odoo speed up.

+
+ +
+
+
+ +
+
+

Add quick operation for odoo modules.

+

Refresh translate. Upgrade, Uninstall, help on topic

+
+ +
+

Easy mass module operation

+
+ +
+

Easy Export translate follow your language

+
+ +
+
+
+
+
+

Add Draggable and sizeable Dialog enable.

+
+ +
+
+
+ +
+
+

Show/Hide Author and Website in Apps Dashboard

+
+

Before

+
+ +
+
+

After uncheck "Show Author in Apps Dashboard"

+
+ +
+
+
+ +
+
+

Show quick upgrade in app dashboard, click to show module info not go to odoo.com

+
+

No more redirect to odoo.com

+
+ +
+
+
+ +
+
+

Setup more flags: just rename the flag pic to locale code of the country

+

You can find the pictures in "\app-odoo\app_odoo_customize\static\src\img\flags"

+
+
+ +
+
+
+ +
+
+

Customize Extra enterprise Module Url(eg. Enterprise).

+
+
+ +
+
+
+ +
+
+

Quick Delete test Data.

+

You can quickly delete all the test data in Apps: Sales/POS/Purchase/MRP/Inventory/Accounting/Message/Workflow etc.

+
+
+ +
+
+
+
+ +
+
+

Multi-language Support..

+

+
+ +
+
+ +

+ Also you can + + get more powerful odoo apps from us. + . like [superbar widget] +

+
+ + + +
+
+

This moduld allows user to quickly customize and debranding Odoo. Quick debug, Language Switcher, + Online Documentation Access,Quick Data Clear.

+

Support Odoo 17,16,15,14,13, 12, 11, 10, 9. Including community and enterprise version.

+
+
+ +
+
+
+

Technical Help & Support

+
+
+
+

+ For any type of technical help & support requests, Feel free to contact us

+ + odoo@china.com +

+ Via QQ: 300883 (App user would not get QQ or any other IM support. Only for odoo project customize.)

+ + 300883@qq.com +
+
+

+ Visit our website for more support.

+

https://www.odooai.cn

+
+
+
+
+ \ No newline at end of file diff --git a/app_odoo_customize/static/description/set0.png b/app_odoo_customize/static/description/set0.png new file mode 100644 index 00000000..1b91ddac Binary files /dev/null and b/app_odoo_customize/static/description/set0.png differ diff --git a/app_odoo_customize/static/description/set1.jpg b/app_odoo_customize/static/description/set1.jpg new file mode 100644 index 00000000..f5e378f2 Binary files /dev/null and b/app_odoo_customize/static/description/set1.jpg differ diff --git a/app_odoo_customize/static/description/set18-1.jpg b/app_odoo_customize/static/description/set18-1.jpg new file mode 100644 index 00000000..56c40177 Binary files /dev/null and b/app_odoo_customize/static/description/set18-1.jpg differ diff --git a/app_odoo_customize/static/description/set18-2.jpg b/app_odoo_customize/static/description/set18-2.jpg new file mode 100644 index 00000000..32028d74 Binary files /dev/null and b/app_odoo_customize/static/description/set18-2.jpg differ diff --git a/app_odoo_customize/static/description/set19.jpg b/app_odoo_customize/static/description/set19.jpg new file mode 100644 index 00000000..8bea0b62 Binary files /dev/null and b/app_odoo_customize/static/description/set19.jpg differ diff --git a/app_odoo_customize/static/description/set2.png b/app_odoo_customize/static/description/set2.png new file mode 100644 index 00000000..896f33b1 Binary files /dev/null and b/app_odoo_customize/static/description/set2.png differ diff --git a/app_odoo_customize/static/description/set20.jpg b/app_odoo_customize/static/description/set20.jpg new file mode 100644 index 00000000..c4dccda1 Binary files /dev/null and b/app_odoo_customize/static/description/set20.jpg differ diff --git a/app_odoo_customize/static/description/set21.jpg b/app_odoo_customize/static/description/set21.jpg new file mode 100644 index 00000000..b86c19e3 Binary files /dev/null and b/app_odoo_customize/static/description/set21.jpg differ diff --git a/app_odoo_customize/static/description/set3.jpg b/app_odoo_customize/static/description/set3.jpg new file mode 100644 index 00000000..71984f38 Binary files /dev/null and b/app_odoo_customize/static/description/set3.jpg differ diff --git a/app_odoo_customize/static/description/setai1.png b/app_odoo_customize/static/description/setai1.png new file mode 100644 index 00000000..000090ad Binary files /dev/null and b/app_odoo_customize/static/description/setai1.png differ diff --git a/app_odoo_customize/static/description/setboost.png b/app_odoo_customize/static/description/setboost.png new file mode 100644 index 00000000..93f3a564 Binary files /dev/null and b/app_odoo_customize/static/description/setboost.png differ diff --git a/app_odoo_customize/static/description/setcn1.png b/app_odoo_customize/static/description/setcn1.png new file mode 100644 index 00000000..d272279c Binary files /dev/null and b/app_odoo_customize/static/description/setcn1.png differ diff --git a/app_odoo_customize/static/description/setcn2.png b/app_odoo_customize/static/description/setcn2.png new file mode 100644 index 00000000..738384fe Binary files /dev/null and b/app_odoo_customize/static/description/setcn2.png differ diff --git a/app_odoo_customize/static/description/setdialog.gif b/app_odoo_customize/static/description/setdialog.gif new file mode 100644 index 00000000..5295a046 Binary files /dev/null and b/app_odoo_customize/static/description/setdialog.gif differ diff --git a/app_odoo_customize/static/description/sethelp1.png b/app_odoo_customize/static/description/sethelp1.png new file mode 100644 index 00000000..f422f193 Binary files /dev/null and b/app_odoo_customize/static/description/sethelp1.png differ diff --git a/app_odoo_customize/static/description/sethelp2.png b/app_odoo_customize/static/description/sethelp2.png new file mode 100644 index 00000000..834563e6 Binary files /dev/null and b/app_odoo_customize/static/description/sethelp2.png differ diff --git a/app_odoo_customize/static/description/sethelp3.png b/app_odoo_customize/static/description/sethelp3.png new file mode 100644 index 00000000..b886e8b0 Binary files /dev/null and b/app_odoo_customize/static/description/sethelp3.png differ diff --git a/app_odoo_customize/static/description/sethelp4.png b/app_odoo_customize/static/description/sethelp4.png new file mode 100644 index 00000000..88ae14ac Binary files /dev/null and b/app_odoo_customize/static/description/sethelp4.png differ diff --git a/app_odoo_customize/static/description/setmodule1.png b/app_odoo_customize/static/description/setmodule1.png new file mode 100644 index 00000000..908456fa Binary files /dev/null and b/app_odoo_customize/static/description/setmodule1.png differ diff --git a/app_odoo_customize/static/description/setmodule2.png b/app_odoo_customize/static/description/setmodule2.png new file mode 100644 index 00000000..6a98903e Binary files /dev/null and b/app_odoo_customize/static/description/setmodule2.png differ diff --git a/app_odoo_customize/static/description/setmodule3.png b/app_odoo_customize/static/description/setmodule3.png new file mode 100644 index 00000000..cbd2ce22 Binary files /dev/null and b/app_odoo_customize/static/description/setmodule3.png differ diff --git a/app_odoo_customize/static/description/setnav.png b/app_odoo_customize/static/description/setnav.png new file mode 100644 index 00000000..2db084ae Binary files /dev/null and b/app_odoo_customize/static/description/setnav.png differ diff --git a/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.js b/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.js new file mode 100644 index 00000000..186cbab3 --- /dev/null +++ b/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.js @@ -0,0 +1,53 @@ +/** @odoo-module **/ + +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { useService } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; +import { browser } from "@web/core/browser/browser"; +import { symmetricalDifference } from "@web/core/utils/arrays"; + +import { Component, useState } from "@odoo/owl"; + +export class SwitchLangMenu extends Component { + setup() { + this.LangService = useService("Lang"); + this.currentLang = this.LangService.currentLang; + this.state = useState({ langToSet: [] }); + } + + setLang(LangId) { + this.state.langToSet = symmetricalDifference(this.state.langToSet, [ + LangId, + ]); + browser.clearTimeout(this.toggleTimer); + this.toggleTimer = browser.setTimeout(() => { + this.LangService.set2Lang("toggle", ...this.state.langToSet); + }, this.constructor.toggleDelay); + } + + logIntoLang(LangId) { + browser.clearTimeout(this.toggleTimer); + this.LangService.set2Lang("loginto", LangId); + } + + get selectedCompanies() { + return symmetricalDifference( + this.LangService.allowedLangIds, + this.state.langToSet + ); + } +} +SwitchLangMenu.template = "web.SwitchLangMenu"; +SwitchLangMenu.components = { Dropdown, DropdownItem }; +SwitchLangMenu.toggleDelay = 1000; + +export const systrayItem = { + Component: SwitchLangMenu, + isDisplayed(env) { + const { availableCompanies } = env.services.Lang; + return Object.keys(availableCompanies).length > 1; + }, +}; + +registry.category("systray").add("SwitchLangMenu", systrayItem, { sequence: 1 }); diff --git a/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.xml b/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.xml new file mode 100644 index 00000000..37a3efae --- /dev/null +++ b/app_odoo_customize/static/description/switch_lang_menu/switch_lang_menu.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + +
+
+
+
+ +
diff --git a/app_odoo_customize/static/src/img/flags/ar_SY.png b/app_odoo_customize/static/src/img/flags/ar_SY.png new file mode 100644 index 00000000..b7f684e0 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/ar_SY.png differ diff --git a/app_odoo_customize/static/src/img/flags/bg.png b/app_odoo_customize/static/src/img/flags/bg.png new file mode 100644 index 00000000..8716c7a0 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/bg.png differ diff --git a/app_odoo_customize/static/src/img/flags/bs_BA.png b/app_odoo_customize/static/src/img/flags/bs_BA.png new file mode 100644 index 00000000..d3595a50 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/bs_BA.png differ diff --git a/app_odoo_customize/static/src/img/flags/de_DE.png b/app_odoo_customize/static/src/img/flags/de_DE.png new file mode 100644 index 00000000..5bcb0b2b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/de_DE.png differ diff --git a/app_odoo_customize/static/src/img/flags/en_AU.png b/app_odoo_customize/static/src/img/flags/en_AU.png new file mode 100644 index 00000000..8108cc37 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/en_AU.png differ diff --git a/app_odoo_customize/static/src/img/flags/en_GB.png b/app_odoo_customize/static/src/img/flags/en_GB.png new file mode 100644 index 00000000..166fb20e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/en_GB.png differ diff --git a/app_odoo_customize/static/src/img/flags/en_US.png b/app_odoo_customize/static/src/img/flags/en_US.png new file mode 100644 index 00000000..830d8224 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/en_US.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_AR.png b/app_odoo_customize/static/src/img/flags/es_AR.png new file mode 100644 index 00000000..3adecbad Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_AR.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_BO.png b/app_odoo_customize/static/src/img/flags/es_BO.png new file mode 100644 index 00000000..e44677a1 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_BO.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_CL.png b/app_odoo_customize/static/src/img/flags/es_CL.png new file mode 100644 index 00000000..85d3a6c8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_CL.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_CO.png b/app_odoo_customize/static/src/img/flags/es_CO.png new file mode 100644 index 00000000..736d7618 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_CO.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_CR.png b/app_odoo_customize/static/src/img/flags/es_CR.png new file mode 100644 index 00000000..361a65fe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_CR.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_DO.png b/app_odoo_customize/static/src/img/flags/es_DO.png new file mode 100644 index 00000000..29970e73 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_DO.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_EC.png b/app_odoo_customize/static/src/img/flags/es_EC.png new file mode 100644 index 00000000..0273da66 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_EC.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_ES.png b/app_odoo_customize/static/src/img/flags/es_ES.png new file mode 100644 index 00000000..0c9580b0 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_ES.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_GT.png b/app_odoo_customize/static/src/img/flags/es_GT.png new file mode 100644 index 00000000..139e648b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_GT.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_MX.png b/app_odoo_customize/static/src/img/flags/es_MX.png new file mode 100644 index 00000000..539739fe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_MX.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_UY.png b/app_odoo_customize/static/src/img/flags/es_UY.png new file mode 100644 index 00000000..fa065560 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_UY.png differ diff --git a/app_odoo_customize/static/src/img/flags/es_VE.png b/app_odoo_customize/static/src/img/flags/es_VE.png new file mode 100644 index 00000000..facca467 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/es_VE.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_afghanistan.png b/app_odoo_customize/static/src/img/flags/flag_afghanistan.png new file mode 100644 index 00000000..e5ee97f1 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_afghanistan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_algeria.png b/app_odoo_customize/static/src/img/flags/flag_algeria.png new file mode 100644 index 00000000..afaf8142 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_algeria.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_american_samoa.png b/app_odoo_customize/static/src/img/flags/flag_american_samoa.png new file mode 100644 index 00000000..49a77ce6 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_american_samoa.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_andorra.png b/app_odoo_customize/static/src/img/flags/flag_andorra.png new file mode 100644 index 00000000..b1fb0225 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_andorra.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_angola.png b/app_odoo_customize/static/src/img/flags/flag_angola.png new file mode 100644 index 00000000..d16b690f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_angola.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_anguilla.png b/app_odoo_customize/static/src/img/flags/flag_anguilla.png new file mode 100644 index 00000000..b63d5f43 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_anguilla.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_antigua_and_barbuda.png b/app_odoo_customize/static/src/img/flags/flag_antigua_and_barbuda.png new file mode 100644 index 00000000..1ed51774 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_antigua_and_barbuda.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_armenia.png b/app_odoo_customize/static/src/img/flags/flag_armenia.png new file mode 100644 index 00000000..e819b9b5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_armenia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_aruba.png b/app_odoo_customize/static/src/img/flags/flag_aruba.png new file mode 100644 index 00000000..35cd54d6 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_aruba.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_austria.png b/app_odoo_customize/static/src/img/flags/flag_austria.png new file mode 100644 index 00000000..0993dfa8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_austria.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_azerbaijan.png b/app_odoo_customize/static/src/img/flags/flag_azerbaijan.png new file mode 100644 index 00000000..e0d7c6a7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_azerbaijan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_bahamas.png b/app_odoo_customize/static/src/img/flags/flag_bahamas.png new file mode 100644 index 00000000..5b75d213 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_bahamas.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_bahrain.png b/app_odoo_customize/static/src/img/flags/flag_bahrain.png new file mode 100644 index 00000000..131c494a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_bahrain.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_bangladesh.png b/app_odoo_customize/static/src/img/flags/flag_bangladesh.png new file mode 100644 index 00000000..14ccd4c7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_bangladesh.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_barbados.png b/app_odoo_customize/static/src/img/flags/flag_barbados.png new file mode 100644 index 00000000..1923c7a5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_barbados.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_belarus.png b/app_odoo_customize/static/src/img/flags/flag_belarus.png new file mode 100644 index 00000000..5d098126 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_belarus.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_belgium.png b/app_odoo_customize/static/src/img/flags/flag_belgium.png new file mode 100644 index 00000000..d19bd942 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_belgium.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_belize.png b/app_odoo_customize/static/src/img/flags/flag_belize.png new file mode 100644 index 00000000..e5c30ded Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_belize.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_benin.png b/app_odoo_customize/static/src/img/flags/flag_benin.png new file mode 100644 index 00000000..500fd032 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_benin.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_bermuda.png b/app_odoo_customize/static/src/img/flags/flag_bermuda.png new file mode 100644 index 00000000..f44a1100 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_bermuda.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_bhutan.png b/app_odoo_customize/static/src/img/flags/flag_bhutan.png new file mode 100644 index 00000000..94c81a9e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_bhutan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_botswana.png b/app_odoo_customize/static/src/img/flags/flag_botswana.png new file mode 100644 index 00000000..b458e964 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_botswana.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_british_indian_ocean_territory.png b/app_odoo_customize/static/src/img/flags/flag_british_indian_ocean_territory.png new file mode 100644 index 00000000..4ca19cfb Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_british_indian_ocean_territory.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_british_virgin_islands.png b/app_odoo_customize/static/src/img/flags/flag_british_virgin_islands.png new file mode 100644 index 00000000..5b88c96a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_british_virgin_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_brunei.png b/app_odoo_customize/static/src/img/flags/flag_brunei.png new file mode 100644 index 00000000..433a7465 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_brunei.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_burkina_faso.png b/app_odoo_customize/static/src/img/flags/flag_burkina_faso.png new file mode 100644 index 00000000..9d105b7e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_burkina_faso.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_burma.png b/app_odoo_customize/static/src/img/flags/flag_burma.png new file mode 100644 index 00000000..31a5fc27 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_burma.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_burundi.png b/app_odoo_customize/static/src/img/flags/flag_burundi.png new file mode 100644 index 00000000..7a11bf80 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_burundi.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cambodia.png b/app_odoo_customize/static/src/img/flags/flag_cambodia.png new file mode 100644 index 00000000..4b313803 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cambodia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cameroon.png b/app_odoo_customize/static/src/img/flags/flag_cameroon.png new file mode 100644 index 00000000..e5b98fd6 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cameroon.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_canada.png b/app_odoo_customize/static/src/img/flags/flag_canada.png new file mode 100644 index 00000000..30e76b08 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_canada.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cape_verde.png b/app_odoo_customize/static/src/img/flags/flag_cape_verde.png new file mode 100644 index 00000000..a12b2618 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cape_verde.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cayman_islands.png b/app_odoo_customize/static/src/img/flags/flag_cayman_islands.png new file mode 100644 index 00000000..2751dd1c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cayman_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_central_african_republic.png b/app_odoo_customize/static/src/img/flags/flag_central_african_republic.png new file mode 100644 index 00000000..5ce7df13 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_central_african_republic.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_chad.png b/app_odoo_customize/static/src/img/flags/flag_chad.png new file mode 100644 index 00000000..250ea6f4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_chad.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_checkered.png b/app_odoo_customize/static/src/img/flags/flag_checkered.png new file mode 100644 index 00000000..128d5d78 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_checkered.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_comoros.png b/app_odoo_customize/static/src/img/flags/flag_comoros.png new file mode 100644 index 00000000..c09c3927 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_comoros.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_congo_democratic_republic.png b/app_odoo_customize/static/src/img/flags/flag_congo_democratic_republic.png new file mode 100644 index 00000000..4091ed9a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_congo_democratic_republic.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_congo_republic.png b/app_odoo_customize/static/src/img/flags/flag_congo_republic.png new file mode 100644 index 00000000..2f4795aa Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_congo_republic.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cook_islands.png b/app_odoo_customize/static/src/img/flags/flag_cook_islands.png new file mode 100644 index 00000000..f337d0c9 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cook_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cote_divoire.png b/app_odoo_customize/static/src/img/flags/flag_cote_divoire.png new file mode 100644 index 00000000..536abd6a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cote_divoire.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_croatia.png b/app_odoo_customize/static/src/img/flags/flag_croatia.png new file mode 100644 index 00000000..1471bf15 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_croatia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cuba.png b/app_odoo_customize/static/src/img/flags/flag_cuba.png new file mode 100644 index 00000000..e5b23f52 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cuba.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_cyprus.png b/app_odoo_customize/static/src/img/flags/flag_cyprus.png new file mode 100644 index 00000000..1d30fa97 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_cyprus.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_czech_republic.png b/app_odoo_customize/static/src/img/flags/flag_czech_republic.png new file mode 100644 index 00000000..6c5556a8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_czech_republic.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_denmark.png b/app_odoo_customize/static/src/img/flags/flag_denmark.png new file mode 100644 index 00000000..c9e4450b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_denmark.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_djibouti.png b/app_odoo_customize/static/src/img/flags/flag_djibouti.png new file mode 100644 index 00000000..db95c50b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_djibouti.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_dominica.png b/app_odoo_customize/static/src/img/flags/flag_dominica.png new file mode 100644 index 00000000..d2dc6374 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_dominica.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_east_timor.png b/app_odoo_customize/static/src/img/flags/flag_east_timor.png new file mode 100644 index 00000000..1553f1e5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_east_timor.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_egypt.png b/app_odoo_customize/static/src/img/flags/flag_egypt.png new file mode 100644 index 00000000..ebe257ad Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_egypt.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_el_salvador.png b/app_odoo_customize/static/src/img/flags/flag_el_salvador.png new file mode 100644 index 00000000..0c114b89 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_el_salvador.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_england.png b/app_odoo_customize/static/src/img/flags/flag_england.png new file mode 100644 index 00000000..41940377 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_england.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_equatorial_guinea.png b/app_odoo_customize/static/src/img/flags/flag_equatorial_guinea.png new file mode 100644 index 00000000..5bf6458b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_equatorial_guinea.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_eritrea.png b/app_odoo_customize/static/src/img/flags/flag_eritrea.png new file mode 100644 index 00000000..6e7e770e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_eritrea.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_estonia.png b/app_odoo_customize/static/src/img/flags/flag_estonia.png new file mode 100644 index 00000000..5ef87c07 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_estonia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_ethiopia.png b/app_odoo_customize/static/src/img/flags/flag_ethiopia.png new file mode 100644 index 00000000..5330161f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_ethiopia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_eu.png b/app_odoo_customize/static/src/img/flags/flag_eu.png new file mode 100644 index 00000000..7b67730d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_eu.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_faeroe_islands.png b/app_odoo_customize/static/src/img/flags/flag_faeroe_islands.png new file mode 100644 index 00000000..5d781a32 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_faeroe_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_falkland_islands.png b/app_odoo_customize/static/src/img/flags/flag_falkland_islands.png new file mode 100644 index 00000000..788bee08 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_falkland_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_fiji.png b/app_odoo_customize/static/src/img/flags/flag_fiji.png new file mode 100644 index 00000000..ffc11731 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_fiji.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_finland.png b/app_odoo_customize/static/src/img/flags/flag_finland.png new file mode 100644 index 00000000..24f113ec Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_finland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_french_polynesia.png b/app_odoo_customize/static/src/img/flags/flag_french_polynesia.png new file mode 100644 index 00000000..43b6698f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_french_polynesia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_gabon.png b/app_odoo_customize/static/src/img/flags/flag_gabon.png new file mode 100644 index 00000000..bfdc1ea7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_gabon.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_gambia.png b/app_odoo_customize/static/src/img/flags/flag_gambia.png new file mode 100644 index 00000000..62194b7d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_gambia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_generic.png b/app_odoo_customize/static/src/img/flags/flag_generic.png new file mode 100644 index 00000000..c12b24df Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_generic.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_georgia.png b/app_odoo_customize/static/src/img/flags/flag_georgia.png new file mode 100644 index 00000000..09dae1d4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_georgia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_ghana.png b/app_odoo_customize/static/src/img/flags/flag_ghana.png new file mode 100644 index 00000000..e985e9ce Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_ghana.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_gibraltar.png b/app_odoo_customize/static/src/img/flags/flag_gibraltar.png new file mode 100644 index 00000000..731a99a2 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_gibraltar.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_greece.png b/app_odoo_customize/static/src/img/flags/flag_greece.png new file mode 100644 index 00000000..7d5f7019 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_greece.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_greenland.png b/app_odoo_customize/static/src/img/flags/flag_greenland.png new file mode 100644 index 00000000..94ccb785 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_greenland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_grenada.png b/app_odoo_customize/static/src/img/flags/flag_grenada.png new file mode 100644 index 00000000..6555c1b7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_grenada.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_guam.png b/app_odoo_customize/static/src/img/flags/flag_guam.png new file mode 100644 index 00000000..271cd8cc Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_guam.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_guernsey.png b/app_odoo_customize/static/src/img/flags/flag_guernsey.png new file mode 100644 index 00000000..e45ae1d8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_guernsey.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_guinea.png b/app_odoo_customize/static/src/img/flags/flag_guinea.png new file mode 100644 index 00000000..99839874 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_guinea.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_guinea_bissau.png b/app_odoo_customize/static/src/img/flags/flag_guinea_bissau.png new file mode 100644 index 00000000..9d743983 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_guinea_bissau.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_guyana.png b/app_odoo_customize/static/src/img/flags/flag_guyana.png new file mode 100644 index 00000000..0d9bb963 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_guyana.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_haiti.png b/app_odoo_customize/static/src/img/flags/flag_haiti.png new file mode 100644 index 00000000..4b4eb300 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_haiti.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_honduras.png b/app_odoo_customize/static/src/img/flags/flag_honduras.png new file mode 100644 index 00000000..62b85fa7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_honduras.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_hong_kong.png b/app_odoo_customize/static/src/img/flags/flag_hong_kong.png new file mode 100644 index 00000000..8426e7cc Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_hong_kong.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_hungary.png b/app_odoo_customize/static/src/img/flags/flag_hungary.png new file mode 100644 index 00000000..d903f3dc Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_hungary.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_iceland.png b/app_odoo_customize/static/src/img/flags/flag_iceland.png new file mode 100644 index 00000000..2fc4f7ab Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_iceland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_india.png b/app_odoo_customize/static/src/img/flags/flag_india.png new file mode 100644 index 00000000..ceb9971c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_india.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_indonesia.png b/app_odoo_customize/static/src/img/flags/flag_indonesia.png new file mode 100644 index 00000000..aff745b5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_indonesia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_iran.png b/app_odoo_customize/static/src/img/flags/flag_iran.png new file mode 100644 index 00000000..cc6acabe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_iran.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_iraq.png b/app_odoo_customize/static/src/img/flags/flag_iraq.png new file mode 100644 index 00000000..570f8994 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_iraq.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_ireland.png b/app_odoo_customize/static/src/img/flags/flag_ireland.png new file mode 100644 index 00000000..75c91bed Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_ireland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_isle_of_man.png b/app_odoo_customize/static/src/img/flags/flag_isle_of_man.png new file mode 100644 index 00000000..1f761fee Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_isle_of_man.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_israel.png b/app_odoo_customize/static/src/img/flags/flag_israel.png new file mode 100644 index 00000000..b05ebc26 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_israel.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_italy.png b/app_odoo_customize/static/src/img/flags/flag_italy.png new file mode 100644 index 00000000..f6aa615e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_italy.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_jamaica.png b/app_odoo_customize/static/src/img/flags/flag_jamaica.png new file mode 100644 index 00000000..3a422e62 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_jamaica.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_japan.png b/app_odoo_customize/static/src/img/flags/flag_japan.png new file mode 100644 index 00000000..066ab4d8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_japan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_jersey.png b/app_odoo_customize/static/src/img/flags/flag_jersey.png new file mode 100644 index 00000000..03f648fb Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_jersey.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_jordan.png b/app_odoo_customize/static/src/img/flags/flag_jordan.png new file mode 100644 index 00000000..6d680054 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_jordan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_kazakhstan.png b/app_odoo_customize/static/src/img/flags/flag_kazakhstan.png new file mode 100644 index 00000000..e1562a65 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_kazakhstan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_kenya.png b/app_odoo_customize/static/src/img/flags/flag_kenya.png new file mode 100644 index 00000000..14a202cf Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_kenya.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_kiribati.png b/app_odoo_customize/static/src/img/flags/flag_kiribati.png new file mode 100644 index 00000000..f5b08a25 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_kiribati.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_kuwait.png b/app_odoo_customize/static/src/img/flags/flag_kuwait.png new file mode 100644 index 00000000..82357429 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_kuwait.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_kyrgyzstan.png b/app_odoo_customize/static/src/img/flags/flag_kyrgyzstan.png new file mode 100644 index 00000000..b4974f47 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_kyrgyzstan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_laos.png b/app_odoo_customize/static/src/img/flags/flag_laos.png new file mode 100644 index 00000000..665aa7d8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_laos.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_latvia.png b/app_odoo_customize/static/src/img/flags/flag_latvia.png new file mode 100644 index 00000000..b466aa7f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_latvia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_lebanon.png b/app_odoo_customize/static/src/img/flags/flag_lebanon.png new file mode 100644 index 00000000..6e170b1d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_lebanon.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_lesotho.png b/app_odoo_customize/static/src/img/flags/flag_lesotho.png new file mode 100644 index 00000000..f6ee22d8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_lesotho.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_liberia.png b/app_odoo_customize/static/src/img/flags/flag_liberia.png new file mode 100644 index 00000000..d4c10fc4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_liberia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_libya.png b/app_odoo_customize/static/src/img/flags/flag_libya.png new file mode 100644 index 00000000..14f9a3a3 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_libya.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_liechtenstein.png b/app_odoo_customize/static/src/img/flags/flag_liechtenstein.png new file mode 100644 index 00000000..a2f5fc0b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_liechtenstein.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_lithuania.png b/app_odoo_customize/static/src/img/flags/flag_lithuania.png new file mode 100644 index 00000000..709383fc Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_lithuania.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_luxembourg.png b/app_odoo_customize/static/src/img/flags/flag_luxembourg.png new file mode 100644 index 00000000..dc4814f8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_luxembourg.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_macau.png b/app_odoo_customize/static/src/img/flags/flag_macau.png new file mode 100644 index 00000000..cf60bb75 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_macau.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_macedonia.png b/app_odoo_customize/static/src/img/flags/flag_macedonia.png new file mode 100644 index 00000000..2d9a8ae7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_macedonia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_madagascar.png b/app_odoo_customize/static/src/img/flags/flag_madagascar.png new file mode 100644 index 00000000..2e84d6ee Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_madagascar.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_malawi.png b/app_odoo_customize/static/src/img/flags/flag_malawi.png new file mode 100644 index 00000000..a17c4519 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_malawi.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_malaysia.png b/app_odoo_customize/static/src/img/flags/flag_malaysia.png new file mode 100644 index 00000000..1eeb7be3 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_malaysia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_maldives.png b/app_odoo_customize/static/src/img/flags/flag_maldives.png new file mode 100644 index 00000000..d9d72e23 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_maldives.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_mali.png b/app_odoo_customize/static/src/img/flags/flag_mali.png new file mode 100644 index 00000000..cae153fe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_mali.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_malta.png b/app_odoo_customize/static/src/img/flags/flag_malta.png new file mode 100644 index 00000000..736aa4ca Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_malta.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_marshall_islands.png b/app_odoo_customize/static/src/img/flags/flag_marshall_islands.png new file mode 100644 index 00000000..082eb763 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_marshall_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_martinique.png b/app_odoo_customize/static/src/img/flags/flag_martinique.png new file mode 100644 index 00000000..a20c9b38 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_martinique.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_mauritania.png b/app_odoo_customize/static/src/img/flags/flag_mauritania.png new file mode 100644 index 00000000..03db77f7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_mauritania.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_mauritius.png b/app_odoo_customize/static/src/img/flags/flag_mauritius.png new file mode 100644 index 00000000..5336f591 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_mauritius.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_micronesia.png b/app_odoo_customize/static/src/img/flags/flag_micronesia.png new file mode 100644 index 00000000..dfda47ee Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_micronesia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_moldova.png b/app_odoo_customize/static/src/img/flags/flag_moldova.png new file mode 100644 index 00000000..e7ee75db Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_moldova.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_monaco.png b/app_odoo_customize/static/src/img/flags/flag_monaco.png new file mode 100644 index 00000000..b51dc7f6 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_monaco.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_mongolia.png b/app_odoo_customize/static/src/img/flags/flag_mongolia.png new file mode 100644 index 00000000..825a0c29 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_mongolia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_montenegro.png b/app_odoo_customize/static/src/img/flags/flag_montenegro.png new file mode 100644 index 00000000..8d1ad79b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_montenegro.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_montserrat.png b/app_odoo_customize/static/src/img/flags/flag_montserrat.png new file mode 100644 index 00000000..53b21152 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_montserrat.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_morocco.png b/app_odoo_customize/static/src/img/flags/flag_morocco.png new file mode 100644 index 00000000..193f0cd2 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_morocco.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_mozambique.png b/app_odoo_customize/static/src/img/flags/flag_mozambique.png new file mode 100644 index 00000000..e8051d3c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_mozambique.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_namibia.png b/app_odoo_customize/static/src/img/flags/flag_namibia.png new file mode 100644 index 00000000..ebaa9ce1 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_namibia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_nauru.png b/app_odoo_customize/static/src/img/flags/flag_nauru.png new file mode 100644 index 00000000..c8cbecc4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_nauru.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_nepal.png b/app_odoo_customize/static/src/img/flags/flag_nepal.png new file mode 100644 index 00000000..c43e96da Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_nepal.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_netherlands.png b/app_odoo_customize/static/src/img/flags/flag_netherlands.png new file mode 100644 index 00000000..e566685f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_netherlands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_netherlands_antilles.png b/app_odoo_customize/static/src/img/flags/flag_netherlands_antilles.png new file mode 100644 index 00000000..c1fbf588 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_netherlands_antilles.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_new_zealand.png b/app_odoo_customize/static/src/img/flags/flag_new_zealand.png new file mode 100644 index 00000000..2ed6512e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_new_zealand.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_nicaragua.png b/app_odoo_customize/static/src/img/flags/flag_nicaragua.png new file mode 100644 index 00000000..e48abf3b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_nicaragua.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_niger.png b/app_odoo_customize/static/src/img/flags/flag_niger.png new file mode 100644 index 00000000..a54b1d8b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_niger.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_nigeria.png b/app_odoo_customize/static/src/img/flags/flag_nigeria.png new file mode 100644 index 00000000..afdc0fbd Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_nigeria.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_niue.png b/app_odoo_customize/static/src/img/flags/flag_niue.png new file mode 100644 index 00000000..ec9fa49a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_niue.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_norfolk_island.png b/app_odoo_customize/static/src/img/flags/flag_norfolk_island.png new file mode 100644 index 00000000..7dcad5b0 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_norfolk_island.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_north_korea.png b/app_odoo_customize/static/src/img/flags/flag_north_korea.png new file mode 100644 index 00000000..4df5f0a4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_north_korea.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_northern_mariana_islands.png b/app_odoo_customize/static/src/img/flags/flag_northern_mariana_islands.png new file mode 100644 index 00000000..3da7c7c0 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_northern_mariana_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_norway.png b/app_odoo_customize/static/src/img/flags/flag_norway.png new file mode 100644 index 00000000..f1e64f75 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_norway.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_oman.png b/app_odoo_customize/static/src/img/flags/flag_oman.png new file mode 100644 index 00000000..a50864e3 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_oman.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_pakistan.png b/app_odoo_customize/static/src/img/flags/flag_pakistan.png new file mode 100644 index 00000000..e4633af1 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_pakistan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_palau.png b/app_odoo_customize/static/src/img/flags/flag_palau.png new file mode 100644 index 00000000..2425cbf4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_palau.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_panama.png b/app_odoo_customize/static/src/img/flags/flag_panama.png new file mode 100644 index 00000000..1fdbc11f Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_panama.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_papua_new_guinea.png b/app_odoo_customize/static/src/img/flags/flag_papua_new_guinea.png new file mode 100644 index 00000000..cc0455c7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_papua_new_guinea.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_paraguay.png b/app_odoo_customize/static/src/img/flags/flag_paraguay.png new file mode 100644 index 00000000..e3b76cee Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_paraguay.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_peru.png b/app_odoo_customize/static/src/img/flags/flag_peru.png new file mode 100644 index 00000000..a40226fd Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_peru.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_philippines.png b/app_odoo_customize/static/src/img/flags/flag_philippines.png new file mode 100644 index 00000000..d847f8c1 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_philippines.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_pirate.png b/app_odoo_customize/static/src/img/flags/flag_pirate.png new file mode 100644 index 00000000..bc493602 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_pirate.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_pitcairn_islands.png b/app_odoo_customize/static/src/img/flags/flag_pitcairn_islands.png new file mode 100644 index 00000000..f2b9b5e2 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_pitcairn_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_poland.png b/app_odoo_customize/static/src/img/flags/flag_poland.png new file mode 100644 index 00000000..9fb2f6aa Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_poland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_portugal.png b/app_odoo_customize/static/src/img/flags/flag_portugal.png new file mode 100644 index 00000000..c3de0b19 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_portugal.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_puerto_rico.png b/app_odoo_customize/static/src/img/flags/flag_puerto_rico.png new file mode 100644 index 00000000..3c995992 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_puerto_rico.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_qatar.png b/app_odoo_customize/static/src/img/flags/flag_qatar.png new file mode 100644 index 00000000..902fc4d5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_qatar.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_rainbow.png b/app_odoo_customize/static/src/img/flags/flag_rainbow.png new file mode 100644 index 00000000..78fc721c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_rainbow.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_romania.png b/app_odoo_customize/static/src/img/flags/flag_romania.png new file mode 100644 index 00000000..4e7f9f13 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_romania.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_rwanda.png b/app_odoo_customize/static/src/img/flags/flag_rwanda.png new file mode 100644 index 00000000..444c8c79 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_rwanda.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saint_helena.png b/app_odoo_customize/static/src/img/flags/flag_saint_helena.png new file mode 100644 index 00000000..82a3b598 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saint_helena.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saint_kitts_and_nevis.png b/app_odoo_customize/static/src/img/flags/flag_saint_kitts_and_nevis.png new file mode 100644 index 00000000..796827d5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saint_kitts_and_nevis.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saint_lucia.png b/app_odoo_customize/static/src/img/flags/flag_saint_lucia.png new file mode 100644 index 00000000..d3a719dc Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saint_lucia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saint_pierre_and_miquelon.png b/app_odoo_customize/static/src/img/flags/flag_saint_pierre_and_miquelon.png new file mode 100644 index 00000000..ef0df15c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saint_pierre_and_miquelon.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saint_vincent_and_the_grenadines.png b/app_odoo_customize/static/src/img/flags/flag_saint_vincent_and_the_grenadines.png new file mode 100644 index 00000000..15f36674 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saint_vincent_and_the_grenadines.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_samoa.png b/app_odoo_customize/static/src/img/flags/flag_samoa.png new file mode 100644 index 00000000..209897e4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_samoa.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_san_marino.png b/app_odoo_customize/static/src/img/flags/flag_san_marino.png new file mode 100644 index 00000000..a1222635 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_san_marino.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_sao_tome_and_principe.png b/app_odoo_customize/static/src/img/flags/flag_sao_tome_and_principe.png new file mode 100644 index 00000000..40534684 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_sao_tome_and_principe.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_saudi_arabia.png b/app_odoo_customize/static/src/img/flags/flag_saudi_arabia.png new file mode 100644 index 00000000..20061b91 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_saudi_arabia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_scotland.png b/app_odoo_customize/static/src/img/flags/flag_scotland.png new file mode 100644 index 00000000..bb01bc94 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_scotland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_senegal.png b/app_odoo_customize/static/src/img/flags/flag_senegal.png new file mode 100644 index 00000000..1047e9be Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_senegal.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_serbia.png b/app_odoo_customize/static/src/img/flags/flag_serbia.png new file mode 100644 index 00000000..255e428b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_serbia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_seychelles.png b/app_odoo_customize/static/src/img/flags/flag_seychelles.png new file mode 100644 index 00000000..e1a139fe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_seychelles.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_sierra_leone.png b/app_odoo_customize/static/src/img/flags/flag_sierra_leone.png new file mode 100644 index 00000000..a223c5df Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_sierra_leone.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_singapore.png b/app_odoo_customize/static/src/img/flags/flag_singapore.png new file mode 100644 index 00000000..a4a4a812 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_singapore.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_slovakia.png b/app_odoo_customize/static/src/img/flags/flag_slovakia.png new file mode 100644 index 00000000..e7c39150 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_slovakia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_slovenia.png b/app_odoo_customize/static/src/img/flags/flag_slovenia.png new file mode 100644 index 00000000..85385895 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_slovenia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_solomon_islands.png b/app_odoo_customize/static/src/img/flags/flag_solomon_islands.png new file mode 100644 index 00000000..fcd10c18 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_solomon_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_somalia.png b/app_odoo_customize/static/src/img/flags/flag_somalia.png new file mode 100644 index 00000000..2f856d2e Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_somalia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_south_africa.png b/app_odoo_customize/static/src/img/flags/flag_south_africa.png new file mode 100644 index 00000000..617fe9f4 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_south_africa.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_south_georgia.png b/app_odoo_customize/static/src/img/flags/flag_south_georgia.png new file mode 100644 index 00000000..d4705488 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_south_georgia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_sri_lanka.png b/app_odoo_customize/static/src/img/flags/flag_sri_lanka.png new file mode 100644 index 00000000..3fbe5791 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_sri_lanka.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_sudan.png b/app_odoo_customize/static/src/img/flags/flag_sudan.png new file mode 100644 index 00000000..f607390b Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_sudan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_suriname.png b/app_odoo_customize/static/src/img/flags/flag_suriname.png new file mode 100644 index 00000000..d12cae84 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_suriname.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_swaziland.png b/app_odoo_customize/static/src/img/flags/flag_swaziland.png new file mode 100644 index 00000000..27fdf138 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_swaziland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_sweden.png b/app_odoo_customize/static/src/img/flags/flag_sweden.png new file mode 100644 index 00000000..870be17a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_sweden.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_switzerland.png b/app_odoo_customize/static/src/img/flags/flag_switzerland.png new file mode 100644 index 00000000..e487a0e5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_switzerland.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_tajikistan.png b/app_odoo_customize/static/src/img/flags/flag_tajikistan.png new file mode 100644 index 00000000..4c66da33 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_tajikistan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_tanzania.png b/app_odoo_customize/static/src/img/flags/flag_tanzania.png new file mode 100644 index 00000000..f2ee2ea2 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_tanzania.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_thailand.png b/app_odoo_customize/static/src/img/flags/flag_thailand.png new file mode 100644 index 00000000..445da6ab Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_thailand.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_togo.png b/app_odoo_customize/static/src/img/flags/flag_togo.png new file mode 100644 index 00000000..28a696ee Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_togo.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_tonga.png b/app_odoo_customize/static/src/img/flags/flag_tonga.png new file mode 100644 index 00000000..bb9e9afb Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_tonga.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_trinidad_and_tobago.png b/app_odoo_customize/static/src/img/flags/flag_trinidad_and_tobago.png new file mode 100644 index 00000000..25226659 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_trinidad_and_tobago.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_tunisia.png b/app_odoo_customize/static/src/img/flags/flag_tunisia.png new file mode 100644 index 00000000..13bb8782 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_tunisia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_turkmenistan.png b/app_odoo_customize/static/src/img/flags/flag_turkmenistan.png new file mode 100644 index 00000000..981a9308 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_turkmenistan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_turks_and_caicos_islands.png b/app_odoo_customize/static/src/img/flags/flag_turks_and_caicos_islands.png new file mode 100644 index 00000000..8efd241c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_turks_and_caicos_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_tuvalu.png b/app_odoo_customize/static/src/img/flags/flag_tuvalu.png new file mode 100644 index 00000000..6c38b44a Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_tuvalu.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_uganda.png b/app_odoo_customize/static/src/img/flags/flag_uganda.png new file mode 100644 index 00000000..52ee97d8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_uganda.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_united_arab_emirates.png b/app_odoo_customize/static/src/img/flags/flag_united_arab_emirates.png new file mode 100644 index 00000000..460a9d5c Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_united_arab_emirates.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_uzbekistan.png b/app_odoo_customize/static/src/img/flags/flag_uzbekistan.png new file mode 100644 index 00000000..8df3de42 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_uzbekistan.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_vanuatu.png b/app_odoo_customize/static/src/img/flags/flag_vanuatu.png new file mode 100644 index 00000000..697f6212 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_vanuatu.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_vatican_city.png b/app_odoo_customize/static/src/img/flags/flag_vatican_city.png new file mode 100644 index 00000000..ffb303b5 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_vatican_city.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_vietnam.png b/app_odoo_customize/static/src/img/flags/flag_vietnam.png new file mode 100644 index 00000000..4e89f65d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_vietnam.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_virgin_islands.png b/app_odoo_customize/static/src/img/flags/flag_virgin_islands.png new file mode 100644 index 00000000..ce23e137 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_virgin_islands.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_wales.png b/app_odoo_customize/static/src/img/flags/flag_wales.png new file mode 100644 index 00000000..4e30af59 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_wales.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_wallis_and_futuna.png b/app_odoo_customize/static/src/img/flags/flag_wallis_and_futuna.png new file mode 100644 index 00000000..d97fc3f9 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_wallis_and_futuna.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_white.png b/app_odoo_customize/static/src/img/flags/flag_white.png new file mode 100644 index 00000000..0efc9ee7 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_white.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_yemen.png b/app_odoo_customize/static/src/img/flags/flag_yemen.png new file mode 100644 index 00000000..e9dea302 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_yemen.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_zambia.png b/app_odoo_customize/static/src/img/flags/flag_zambia.png new file mode 100644 index 00000000..96d06e4d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_zambia.png differ diff --git a/app_odoo_customize/static/src/img/flags/flag_zimbabwe.png b/app_odoo_customize/static/src/img/flags/flag_zimbabwe.png new file mode 100644 index 00000000..7501780d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/flag_zimbabwe.png differ diff --git a/app_odoo_customize/static/src/img/flags/fr_FR.png b/app_odoo_customize/static/src/img/flags/fr_FR.png new file mode 100644 index 00000000..22b4c411 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/fr_FR.png differ diff --git a/app_odoo_customize/static/src/img/flags/ko_KR.png b/app_odoo_customize/static/src/img/flags/ko_KR.png new file mode 100644 index 00000000..b0b21dfa Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/ko_KR.png differ diff --git a/app_odoo_customize/static/src/img/flags/logo_red_200x50.png b/app_odoo_customize/static/src/img/flags/logo_red_200x50.png new file mode 100644 index 00000000..8b5b87b8 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/logo_red_200x50.png differ diff --git a/app_odoo_customize/static/src/img/flags/pt_BR.png b/app_odoo_customize/static/src/img/flags/pt_BR.png new file mode 100644 index 00000000..9b01d07d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/pt_BR.png differ diff --git a/app_odoo_customize/static/src/img/flags/ru_RU.png b/app_odoo_customize/static/src/img/flags/ru_RU.png new file mode 100644 index 00000000..c251d628 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/ru_RU.png differ diff --git a/app_odoo_customize/static/src/img/flags/sq_AL.png b/app_odoo_customize/static/src/img/flags/sq_AL.png new file mode 100644 index 00000000..8aac6dce Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/sq_AL.png differ diff --git a/app_odoo_customize/static/src/img/flags/tr_TR.png b/app_odoo_customize/static/src/img/flags/tr_TR.png new file mode 100644 index 00000000..4e27f6a2 Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/tr_TR.png differ diff --git a/app_odoo_customize/static/src/img/flags/uk_UA.png b/app_odoo_customize/static/src/img/flags/uk_UA.png new file mode 100644 index 00000000..4eac918d Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/uk_UA.png differ diff --git a/app_odoo_customize/static/src/img/flags/zh_CN.png b/app_odoo_customize/static/src/img/flags/zh_CN.png new file mode 100644 index 00000000..dd1b0efe Binary files /dev/null and b/app_odoo_customize/static/src/img/flags/zh_CN.png differ diff --git a/app_odoo_customize/static/src/img/icon76red.png b/app_odoo_customize/static/src/img/icon76red.png new file mode 100644 index 00000000..aa490954 Binary files /dev/null and b/app_odoo_customize/static/src/img/icon76red.png differ diff --git a/app_odoo_customize/static/src/img/logo_sunpop.png b/app_odoo_customize/static/src/img/logo_sunpop.png new file mode 100644 index 00000000..bc32c24e Binary files /dev/null and b/app_odoo_customize/static/src/img/logo_sunpop.png differ diff --git a/app_odoo_customize/static/src/js/dialog.js b/app_odoo_customize/static/src/js/dialog.js new file mode 100644 index 00000000..c86722cd --- /dev/null +++ b/app_odoo_customize/static/src/js/dialog.js @@ -0,0 +1,15 @@ +/** @odoo-module **/ + +import { Dialog } from "@web/core/dialog/dialog"; +import { patch } from "@web/core/utils/patch"; +import { session } from "@web/session"; + +patch(Dialog.prototype, { + setup() { + super.setup(); + const app_system_name = session.app_system_name || "odooApp"; + this.title = app_system_name; + }, + +}); + diff --git a/app_odoo_customize/static/src/js/ribbon.js b/app_odoo_customize/static/src/js/ribbon.js new file mode 100644 index 00000000..0019a969 --- /dev/null +++ b/app_odoo_customize/static/src/js/ribbon.js @@ -0,0 +1,76 @@ +/** @odoo-module **/ +/* Copyright 2015 Sylvain Calador + Copyright 2015 Javi Melendez + Copyright 2016 Antonio Espinosa + Copyright 2017 Thomas Binsfeld + Copyright 2017 Xavier Jiménez + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + + + +import {Component, xml} from "@odoo/owl"; +import {useBus, useService} from "@web/core/utils/hooks"; +import {registry} from "@web/core/registry"; + +export class WebEnvironmentRibbon extends Component { + setup() { + this.orm = useService("orm"); + useBus(this.env.bus, "WEB_CLIENT_READY", this.showRibbon.bind(this)); + } + + // Code from: http://jsfiddle.net/WK_of_Angmar/xgA5C/ + validStrColour(strToTest) { + if (strToTest === "") { + return false; + } + if (strToTest === "inherit") { + return true; + } + if (strToTest === "transparent") { + return true; + } + const image = document.createElement("img"); + image.style.color = "rgb(0, 0, 0)"; + image.style.color = strToTest; + if (image.style.color !== "rgb(0, 0, 0)") { + return true; + } + image.style.color = "rgb(255, 255, 255)"; + image.style.color = strToTest; + return image.style.color !== "rgb(255, 255, 255)"; + } + + showRibbon() { + const ribbon = $(".test-ribbon"); + const self = this; + ribbon.hide(); + // Get ribbon data from backend + self.orm + .call("web.environment.ribbon.backend", "get_environment_ribbon") + .then(function (ribbon_data) { + // Ribbon name + if (ribbon_data.name && ribbon_data.name !== "False") { + ribbon.show(); + ribbon.html(ribbon_data.name); + } + // Ribbon color + if (ribbon_data.color && self.validStrColour(ribbon_data.color)) { + ribbon.css("color", ribbon_data.color); + } + // Ribbon background color + if ( + ribbon_data.background_color && + self.validStrColour(ribbon_data.background_color) + ) { + ribbon.css("background-color", ribbon_data.background_color); + } + }); + } +} + +WebEnvironmentRibbon.props = {}; +WebEnvironmentRibbon.template = xml`
`; + +registry.category("main_components").add("WebEnvironmentRibbon", { + Component: WebEnvironmentRibbon, +}); diff --git a/app_odoo_customize/static/src/js/user_menu.js b/app_odoo_customize/static/src/js/user_menu.js new file mode 100644 index 00000000..0ec5b08b --- /dev/null +++ b/app_odoo_customize/static/src/js/user_menu.js @@ -0,0 +1,223 @@ +/** @odoo-module **/ +/* jshint esversion: 6 */ + +import { _t } from "@web/core/l10n/translation"; +import { UserMenu } from "@web/webclient/user_menu/user_menu"; +import { routeToUrl } from "@web/core/browser/router_service"; +import { patch } from "@web/core/utils/patch"; +import { browser } from "@web/core/browser/browser"; +import { registry } from "@web/core/registry"; +import { session } from "@web/session"; +import { useService } from "@web/core/utils/hooks"; +const userMenuRegistry = registry.category("user_menuitems"); + +patch(UserMenu.prototype, { + setup() { + super.setup(); + + "use strict"; + // this.companyService = useService("company"); + this.rpc = useService("rpc"); + this.orm = useService("orm"); + this.app_show_lang = session.app_show_lang; + this.app_lang_list = session.app_lang_list; + //todo: 演习 shortCutsItem 中的用法,当前是直接 xml 写了展现 + + //修正 bug,在移动端不会关闭本身 + //o_burger_menu position-fixed top-0 bottom-0 start-100 d-flex flex-column flex-nowrap burgerslide burgerslide-enter-active + function preferencesItem(env) { + return { + type: "item", + id: "settings", + description: _t("Preferences"), + callback: async function () { + const actionDescription = await env.services.orm.call("res.users", "action_get"); + actionDescription.res_id = env.services.user.userId; + try { + let m = document.getElementsByClassName("o_burger_menu_close"); + if (m) { + m[0].click(); + } + } catch (e) { + ; + } + env.services.action.doAction(actionDescription); + //修正 bug,在移动端不会关闭本身 + }, + sequence: 50, + }; + } + userMenuRegistry.add("profile", preferencesItem, {'force': true, 'menu': this}); + userMenuRegistry.add("refresh_current", refresh_current, {'force': true}); + + if (session.app_show_lang) { + userMenuRegistry.add("separator1", separator1, {'force': true}) + } + if (session.app_show_debug && session.is_erp_manager) { + userMenuRegistry.add("debug", debugItem, {'force': true}) + .add("asset_asset", activateAssetsDebugging, {'force': true}) + .add("leave_debug", leaveDebugMode, {'force': true}) + .add("separator10", separator10, {'force': true}) + } + if (session.app_show_documentation) { + userMenuRegistry.add("documentation", documentationItem, {'force': true}); + } + if (session.app_show_support) { + try { + userMenuRegistry.add("support", supportItem, {'force': true}); + } catch (err) { + ; + } + } else if (userMenuRegistry.get('support', false)) { + try { + userMenuRegistry.remove("support"); + } catch (err) { + ; + } + } + if (session.app_show_account) { + userMenuRegistry.add("odoo_account", odooAccountItem, {'force': true}); + } else if (userMenuRegistry.get('odoo_account', false)){ + try { + userMenuRegistry.remove("odoo_account"); + } catch (err) { + ; + } + } + }, + + async setLang(lang_code) { + "use strict"; + // alert(lang_code); + browser.clearTimeout(this.toggleTimer); + if (this.user.lang !== lang_code) { + const res = await this.orm.call("res.users", "write", [ + session.uid, {'lang': lang_code} + ]); + location.reload(); + // 调用 action , 要先定义 this.action = useService("action") + // this.action.action({ + // type: 'ir.actions.client', + // tag: 'reload_context', + // }); + } + } +}); + +function debugItem(env) { + "use strict"; + return { + type: "item", + id: "debug", + description: _t("Activate the developer mode"), + callback: () => { + browser.location.search = "?debug=1"; + }, + sequence: 5, + }; +} + +function activateAssetsDebugging(env) { + "use strict"; + return { + type: "item", + description: _t("Activate Assets Debugging"), + callback: () => { + browser.location.search = "?debug=assets"; + }, + sequence: 6, + }; +} + +function leaveDebugMode(env) { + "use strict"; + return { + type: "item", + description: _t("Leave the Developer Tools"), + callback: () => { + const route = env.services.router.current; + route.search.debug = ""; + browser.location.href = browser.location.origin + routeToUrl(route); + }, + sequence: 7, + }; +} + +function separator1() { + "use strict"; + return { + type: "separator", + sequence: 1, + }; +} + +function separator10() { + "use strict"; + return { + type: "separator", + sequence: 10, + }; +} + +function documentationItem(env) { + "use strict"; + const documentationURL = session.app_documentation_url; + + return { + type: "item", + id: "documentation", + description: _t("Documentation"), + href: documentationURL, + callback: () => { + browser.open(documentationURL, "_blank"); + }, + sequence: 21, + }; +} + +function supportItem(env) { + "use strict"; + const url = session.app_support_url; + return { + type: "item", + id: "support", + description: _t("Support"), + href: url, + callback: (ev) => { + browser.open(url, "_blank"); + }, + sequence: 22, + }; +} + +function odooAccountItem(env) { + "use strict"; + const app_account_title = session.app_account_title; + const app_account_url = session.app_account_url; + return { + type: "item", + id: "account", + description: _t(app_account_title), + href: app_account_url, + callback: () => { + top.location.href = app_account_url; + // browser.open(app_account_url, "_blank"); + }, + sequence: 60, + }; +} + +function refresh_current(env) { + //移动端,主要为了小程序 + "use strict"; + return { + type: "item", + id: "refresh_current", + description: _t("Refresh Page"), + hide: !env.isSmall, + callback: () => { + location.reload(); + }, + sequence: 58, + }; +} diff --git a/app_odoo_customize/static/src/scss/app.scss b/app_odoo_customize/static/src/scss/app.scss new file mode 100644 index 00000000..7101f836 --- /dev/null +++ b/app_odoo_customize/static/src/scss/app.scss @@ -0,0 +1,87 @@ +.o_user_menu .dropdown-menu img, +.o_user_menu_mobile .dropdown-item img { + height: 20px; + margin: 2px; +} + +.oe_form_configuration { + .o_settings_container { + span { + display:inline-block; + } + } +} + +.o_web_client { + .breadcrumb-item + .breadcrumb-item::before { + content: '/'; + } +} +// navbar在下方的特殊处理 +.o_web_client.navbar_pos_bottom { + + @media (max-width: 768px) { + --o-navbar-height: 80px; + } + flex-direction: column-reverse!important; + .o_navbar { + display: block; + + .o_main_navbar { + @media (max-width: 768px) { + --o-navbar-height: 80px; + padding: 0 var(--o-navbar-height); + } + + button.o_mobile_menu_toggle, .o_menu_systray .o_debug_manager .dropdown-toggle { + font-size: 2em; + } + + // toggle处理 + .dropdown .dropdown-toggle::after { + border-top: 0; + border-bottom: 4px solid; + } + } + } + + //message + .o_MessagingMenu_dropdownMenu:not(.o-isDeviceSmall) { + bottom: var(--o-navbar-height) ; + top: auto; + } + .o_MessagingMenu_dropdownMenu.o-isDeviceSmall { + bottom: var(--o-navbar-height)+6!important; ; + height: calc(100% - var(--o-navbar-height)); + top: 0; + } + //Activity + .o_ActivityMenuView_dropdownMenu { + bottom: var(--o-navbar-height); + top: auto; + } + //homeMenu + .o_home_menu { + .container, .o_container_small, .container-fluid, .container-xxl, .container-xl, .container-lg, .container-md, .container-sm { + //bottom: 60px; + } + } + + //userMenu + .o_burger_menu { + width: 80%; + .o_burger_menu_topbar { + height: var(--o-navbar-height); + } + &.flex-column { + flex-direction: column-reverse!important; + } + .oi:before, .fa:before, .dropdown-toggle { + font-size: 2em; + line-height: var(--o-navbar-height); + } + } +} +// Colors replace to ent +//$o-community-color: #875A7B; + diff --git a/app_odoo_customize/static/src/scss/dialog.scss b/app_odoo_customize/static/src/scss/dialog.scss new file mode 100644 index 00000000..c740a405 --- /dev/null +++ b/app_odoo_customize/static/src/scss/dialog.scss @@ -0,0 +1,23 @@ +.modal { + .modal-content { + .modal-header.ui-draggable-handle { + cursor: move; + } + } + + .modal-dialog_full_screen { + @include media-breakpoint-up(sm) { + max-width: 100%; + width: calc(100% - 50px); + } + } + + .dialog_button_restore, + .dialog_button_extend { + margin-left: auto; + + + .btn-close { + margin: -8px -8px -8px 0px; + } + } +} \ No newline at end of file diff --git a/app_odoo_customize/static/src/scss/ribbon.scss b/app_odoo_customize/static/src/scss/ribbon.scss new file mode 100644 index 00000000..5d454ade --- /dev/null +++ b/app_odoo_customize/static/src/scss/ribbon.scss @@ -0,0 +1,35 @@ +/* Copyright 2015 Francesco OpenCode Apruzzese + Copyright 2017 Thomas Binsfeld + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +.test-ribbon { + width: 300px; + top: 25px; + right: -100px; + text-align: center; + padding: 10px; + line-height: 20px; + letter-spacing: 1px; + color: #f0f0f0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + z-index: 9999; + position: fixed; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); + background: rgba(255, 0, 0, 0.5); + pointer-events: none; +} + +.test-ribbon b { + font-size: 20px; +} + +header:hover ~ .test-ribbon, +nav:hover ~ .test-ribbon { + /* Ease out ribbon when user is using the navigation in Odoo */ + opacity: 0; + transition: 0.2s ease; +} diff --git a/app_odoo_customize/static/src/webclient/user_menu.xml b/app_odoo_customize/static/src/webclient/user_menu.xml new file mode 100644 index 00000000..b20c4466 --- /dev/null +++ b/app_odoo_customize/static/src/webclient/user_menu.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app_odoo_customize/static/src/webclient/webclient.js b/app_odoo_customize/static/src/webclient/webclient.js new file mode 100644 index 00000000..87d3ec50 --- /dev/null +++ b/app_odoo_customize/static/src/webclient/webclient.js @@ -0,0 +1,27 @@ +/** @odoo-module **/ + +import { WebClient } from "@web/webclient/webclient"; +import { patch } from "@web/core/utils/patch"; +import { session } from "@web/session"; + +patch(WebClient.prototype, { + setup() { + // 处理 navbar 全局可配置位置 + var self = this; + super.setup(); + this.state.navbar_pos_pc = session.app_navbar_pos_pc || 'top'; + this.state.navbar_pos_mobile = session.app_navbar_pos_mobile || 'top'; + if (self.env.isSmall) + this.state.navbar_pos = this.state.navbar_pos_mobile; + else + this.state.navbar_pos = this.state.navbar_pos_pc; + if (this.state.navbar_pos === 'bottom') { + document.body.className += ' navbar_pos_bottom'; + } + + const app_system_name = session.app_system_name || 'odooAi'; + // zopenerp is easy to grep + this.title.setParts({ zopenerp: app_system_name }); + + } +}); diff --git a/app_odoo_customize/static/src/xml/res_config_edition.xml b/app_odoo_customize/static/src/xml/res_config_edition.xml new file mode 100644 index 00000000..47362421 --- /dev/null +++ b/app_odoo_customize/static/src/xml/res_config_edition.xml @@ -0,0 +1,10 @@ + + + + +

+ Odoo (odooai.cn Edition) +

+
+
+
diff --git a/app_odoo_customize/views/app_odoo_customize_views.xml b/app_odoo_customize/views/app_odoo_customize_views.xml new file mode 100644 index 00000000..55199d2c --- /dev/null +++ b/app_odoo_customize/views/app_odoo_customize_views.xml @@ -0,0 +1,26 @@ + + + +