mirror of
https://github.com/guohuadeng/app-odoo.git
synced 2025-02-23 04:11:36 +02:00
update common
This commit is contained in:
@@ -64,9 +64,6 @@
|
||||
'base',
|
||||
'web',
|
||||
],
|
||||
'external_dependencies': {
|
||||
'python': ['pyyaml', 'ua-parser', 'user-agents'],
|
||||
},
|
||||
'data': [
|
||||
# 'security/*.xml',
|
||||
# 'security/ir.model.access.csv',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
import requests
|
||||
from user_agents import parse
|
||||
from ..lib.user_agents import parse
|
||||
|
||||
from odoo import api, http, SUPERUSER_ID, _
|
||||
from odoo import http, exceptions
|
||||
|
||||
1
app_common/lib/ua_parser/__init__.py
Normal file
1
app_common/lib/ua_parser/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = (0, 10, 0)
|
||||
7406
app_common/lib/ua_parser/_regexes.py
Normal file
7406
app_common/lib/ua_parser/_regexes.py
Normal file
File diff suppressed because it is too large
Load Diff
544
app_common/lib/ua_parser/user_agent_parser.py
Normal file
544
app_common/lib/ua_parser/user_agent_parser.py
Normal file
@@ -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 <elsigh@gmail.com>"
|
||||
|
||||
|
||||
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
|
||||
290
app_common/lib/ua_parser/user_agent_parser_test.py
Normal file
290
app_common/lib/ua_parser/user_agent_parser_test.py
Normal file
@@ -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()
|
||||
3
app_common/lib/user_agents/__init__.py
Normal file
3
app_common/lib/user_agents/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
VERSION = (2, 2, 0)
|
||||
|
||||
from .parsers import parse
|
||||
14
app_common/lib/user_agents/compat.py
Normal file
14
app_common/lib/user_agents/compat.py
Normal file
@@ -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))
|
||||
268
app_common/lib/user_agents/parsers.py
Normal file
268
app_common/lib/user_agents/parsers.py
Normal file
@@ -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)
|
||||
268
app_common/lib/user_agents/tests.py
Normal file
268
app_common/lib/user_agents/tests.py
Normal file
@@ -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))
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="inherit_id" ref="sale.sale_order_view_search_inherit_quotation"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search">
|
||||
<searchpanel view_types="tree,kanban,pivot,graph">
|
||||
<searchpanel view_types="tree,kanban,pivot,graph,search">
|
||||
<field name="team_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="partner_id" filter_domain="[('customer_rank','>', 0)]"/>
|
||||
@@ -23,7 +23,7 @@
|
||||
<field name="inherit_id" ref="sale.sale_order_view_search_inherit_sale"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search">
|
||||
<searchpanel view_types="tree,kanban,pivot,graph">
|
||||
<searchpanel view_types="tree,kanban,pivot,graph,search">
|
||||
<field name="invoice_status"/>
|
||||
<field name="team_id"/>
|
||||
<field name="user_id"/>
|
||||
|
||||
Reference in New Issue
Block a user