Browse Source

Cleanup

tags/0.2
Johann Schmitz 2 years ago
parent
commit
70b08dd74c
Signed by: Johann Schmitz <johann@j-schmitz.net> GPG Key ID: A084064277C501ED
14 changed files with 720 additions and 687 deletions
  1. 4
    2
      .gitignore
  2. 31
    3
      Makefile
  3. 1
    0
      pypiwik/__init__.py
  4. BIN
      pypiwik/__init__.pyo
  5. 28
    0
      pypiwik/decorators.py
  6. BIN
      pypiwik/decorators.pyo
  7. 329
    0
      pypiwik/tracker.py
  8. BIN
      pypiwik/tracker.pyo
  9. 15
    13
      setup.py
  10. 0
    28
      src/pypiwik/decorators.py
  11. 0
    329
      src/pypiwik/tracker.py
  12. 107
    107
      tests/builder.py
  13. 51
    51
      tests/decorators.py
  14. 154
    154
      tests/tracker.py

+ 4
- 2
.gitignore View File

@@ -1,4 +1,6 @@
1 1
 .idea
2
-*.pyc
2
+*.py?
3
+__pycache__
3 4
 src/test.py
4
-.coverage
5
+.coverage
6
+coverage.xml

+ 31
- 3
Makefile View File

@@ -1,16 +1,44 @@
1 1
 TARGET?=tests
2 2
 
3
+SRC_PATH := pypiwik
4
+
5
+VERSION := $(shell grep -Po '"(.*)"' $(SRC_PATH)/__init__.py | sed -e 's/"//g')
6
+
7
+test_default_python:
8
+	PYTHONPATH="." python tests/ -v
9
+
3 10
 test_py2:
4 11
 	@echo Executing test with python2
5
-	PYTHONPATH=".:./src" python2 tests/
12
+	PYTHONPATH="." python2 tests/ -v
6 13
 
7 14
 test_py3:
8 15
 	@echo Executing test with python3
9
-	PYTHONPATH=".:./src" python3 tests/
16
+	PYTHONPATH="." python3 tests/ -v
10 17
 
11 18
 test: test_py2 test_py3
12 19
 
20
+compile:
21
+	@echo Compiling python code
22
+	python -m compileall $(SRC_PATH)/
23
+
24
+compile_optimized:
25
+	@echo Compiling python code optimized
26
+	python -O -m compileall $(SRC_PATH)/
27
+
13 28
 coverage:
14 29
 	coverage erase
15
-	PYTHONPATH=".:./src" coverage run --source='src' --omit='src/test.py' --branch tests/__main__.py
30
+	PYTHONPATH="." coverage run --source='$(SRC_PATH)' --branch tests/__main__.py
31
+	coverage xml -i
16 32
 	coverage report -m
33
+
34
+sonar:
35
+	/usr/local/bin/sonar-scanner/bin/sonar-scanner -Dsonar.projectVersion=$(VERSION)
36
+
37
+clean:
38
+	find -name "*.py?" -delete
39
+	rm -f coverage.xml testresults.xml
40
+	rm -fr htmlcov dist build *.egg-info
41
+
42
+travis: compile compile_optimized test_default_python coverage
43
+
44
+jenkins: travis sonar

src/pypiwik/__init__.py → pypiwik/__init__.py View File

@@ -1,2 +1,3 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3
+VERSION="0.1.5.1"

BIN
pypiwik/__init__.pyo View File


+ 28
- 0
pypiwik/decorators.py View File

@@ -0,0 +1,28 @@
1
+# -*- coding: utf-8 -*-
2
+from pypiwik.tracker import PiwikTracker
3
+
4
+ON_SUCCESS = 1
5
+ON_ERROR = 2
6
+ALWAYS = 3
7
+
8
+def track_page_view(tracker=None, piwik_url=None, site_id=None, when=ON_SUCCESS, **tracker_kwargs):
9
+    if not ((piwik_url and site_id) or tracker):
10
+        raise ValueError("Either 'tracker' or 'piwik_url' and 'site_id' must be set")
11
+
12
+    def decorator_wrapper(func):
13
+        def inner(*args, **kwargs):
14
+            pt = tracker or PiwikTracker(piwik_url, site_id, **tracker_kwargs)
15
+
16
+            try:
17
+                ret = func(*args, **kwargs)
18
+
19
+                if when in (ON_SUCCESS, ALWAYS):
20
+                    pt.track_page_view(**kwargs)
21
+
22
+                return ret
23
+            except:
24
+                if when in (ON_ERROR, ALWAYS):
25
+                    pt.track_page_view(**kwargs)
26
+                raise
27
+        return inner
28
+    return decorator_wrapper

BIN
pypiwik/decorators.pyo View File


+ 329
- 0
pypiwik/tracker.py View File

@@ -0,0 +1,329 @@
1
+# -*- coding: utf-8 -*-
2
+from hashlib import md5
3
+import json
4
+import logging
5
+import os
6
+import time
7
+import datetime
8
+import requests
9
+
10
+try:
11
+    from urllib.parse import urljoin, urlencode
12
+except ImportError:
13
+    from urlparse import urljoin
14
+    from urllib import urlencode
15
+
16
+PARAMETERS = {
17
+    # required parameters
18
+    'url': 'url', # The full URL for the current action.
19
+
20
+    # recommended parameters
21
+    'action_name': 'action_name', # The title of the action being tracked.
22
+    'referer': 'urlref', # The full HTTP Referrer URL.
23
+    'visit_custom_vars': '_cvar', # Visit scope custom variables.
24
+    'visit_count': '_idvc', # The current count of visits for this visitor.
25
+    'view_timestamp': '_viewts', # The UNIX timestamp of this visitor's previous visit.
26
+    'first_visit_timestamp': '_idts', # The UNIX timestamp of this visitor's first visit.
27
+    'campaign_name': '_rcn', # The Campaign name (see Tracking Campaigns).
28
+    'campaign_keywords': '_rck', # The Campaign Keyword (see Tracking Campaigns).
29
+    'resolution': 'res', # The resolution of the device the visitor is using, eg 1280x1024.
30
+    'hour': 'h', # The current hour (local time).
31
+    'minute': 'm', # The current minute (local time).
32
+    'second': 's', # The current second (local time).
33
+    'flash': 'fla', # Flash,
34
+    'java': 'java', # Java
35
+    'director': 'dir', # Director,
36
+    'quicktime': 'qt', # Quicktime,
37
+    'real_player': 'realp', # Real Player,
38
+    'pdf': 'pdf', # PDF
39
+    'wma': 'wma', # Windows Media
40
+    'gears': 'gears', # Gears
41
+    'silverlight': 'ag', # Silverlight
42
+    'cookie': 'cookie', # when set to 1, the visitor's client is known to support cookies.
43
+    'user_agent': 'ua', # An override value for the User-Agent HTTP header field.
44
+    'lang': 'lang', # An override value for the Accept-Language HTTP header field. This value is used to detect the visitor's country if GeoIP is not enabled.
45
+    'user_id': 'uid', # defines the User ID for this request. User ID is any non empty unique string identifying the user (such as an email address or a username).
46
+    'visitor_id': 'cid', # defines the visitor ID for this request.
47
+    'new_visit': 'new_visit', # If set to 1, will force a new visit to be created for this action. This feature is also available in Javascript.
48
+
49
+    # 'Optional Action info (measure Page view, Outlink, Download, Site search)',
50
+    'page_custom_vars': 'cvar', # Page scope custom variables.
51
+    'link': 'link', # An external URL the user has opened. Used for tracking outlink clicks. We recommend to also set the url parameter to this same value.
52
+    'download': 'download', # URL of a file the user has downloaded. Used for tracking downloads. We recommend to also set the url parameter to this same value.
53
+    'search_keyword': 'search', # The Site Search keyword. When specified, the request will not be tracked as a normal pageview but will instead be tracked as a Site Search request.
54
+    'search_category': 'search_cat', # when search is specified, you can optionally specify a search category with this parameter.
55
+    'search_count': 'search_count', # when search is specified, we also recommend to set the search_count to the number of search results displayed on the results page.
56
+
57
+    'goal_id': 'idgoal', # If specified, the tracking request will trigger a conversion for the goal of the website being tracked with this ID.
58
+    'revenue': 'revenue', # A monetary value that was generated as revenue by this goal conversion. Only used if idgoal is specified in the request.
59
+    'gt_ms': 'gt_ms', # The amount of time it took the server to generate this action, in milliseconds.
60
+    'charset': 'cs', # The charset of the page being tracked. Specify the charset if the data you send to Piwik is encoded in a different character set than the default utf-8.
61
+
62
+    # Optional Event Tracking info
63
+    'event_category': 'e_c', # The event category. Must not be empty. (eg. Videos, Music, Games...)
64
+    'event_action': 'e_a', # The event action. Must not be empty. (eg. Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
65
+    'event_name': 'e_n', # The event name. (eg. a Movie name, or Song name, or File name...)
66
+    'event_value': 'e_v', # The event value. Must be a float or integer value (numeric), not a string.
67
+
68
+    # Optional Content Tracking info
69
+    'content_name': 'c_n', # The name of the content. For instance 'Ad Foo Bar'
70
+    'content_piece': 'c_p', # The actual content piece. For instance the path to an image, video, audio, any text
71
+    'content_target': 'c_t', # The target of the content. For instance the URL of a landing page
72
+    'content_interaction': 'c_i', # The name of the interaction with the content. For instance a 'click'
73
+
74
+    # Other parameters (require authentication via token_auth)
75
+    'token_auth': 'token_auth', # 32 character authorization key used to authenticate the API request.
76
+    'client_ip': 'cip', # Override value for the visitor IP (both IPv4 and IPv6 notations supported).
77
+    'client_dt': 'cdt', # Override for the datetime of the request (normally the current time is used).
78
+    'country': 'country', # An override value for the country. Should be set to the two letter country code of the visitor (lowercase), eg fr, de, us.
79
+    'region': 'region', # An override value for the region. Should be set to the two letter region code as defined by MaxMind's GeoIP databases.
80
+    'city': 'city', # An override value for the city. The name of the city the visitor is located in, eg, Tokyo.
81
+    'lat': 'lat', # An override value for the visitor's latitude, eg 22.456.
82
+    'long': 'long', # An override value for the visitor's longitude, eg 22.456.
83
+
84
+    'track_bots': 'bots', # Set to true to track bots
85
+
86
+    'heartbeat_timer': None, # Set to a positive integer to enable the heartbeat timer
87
+}
88
+
89
+AUTH_RESTRICTED_PARAMS = ('token_auth', 'client_ip', 'client_dt', 'country', 'region', 'city', 'lat', 'long')
90
+
91
+class PiwikTracker(object):
92
+    API_VERSION = 1
93
+
94
+    def __init__(self, piwik_url, site_id, request=None, values=None, **kwargs):
95
+        super(PiwikTracker, self).__init__()
96
+        self.piwik_url = piwik_url
97
+        self.idsite = site_id
98
+
99
+        # initialize all tracking variables on this instance
100
+        values = values or {}
101
+        values.update(kwargs)
102
+        self.update(dict((p, values.get(p, None)) for p in PARAMETERS.keys()))
103
+
104
+        self.visit_custom_vars = {}
105
+        self.page_custom_vars = {}
106
+
107
+        self.spoof_request = True
108
+
109
+        # defaults for the requests module
110
+        self.request_headers = {}
111
+        self.requests_arguments = {
112
+            'timeout': 3
113
+        }
114
+
115
+        # default filenames for the tracker file and the js file
116
+        self.piwik_php_file = 'piwik.php'
117
+        self.piwik_js_file = 'piwik.js'
118
+
119
+        self.update_from_request(request)
120
+
121
+    def update(self, values):
122
+        for property_name in PARAMETERS.keys():
123
+            if property_name in values:
124
+                setattr(self, property_name, values[property_name])
125
+
126
+    @property
127
+    def php_url(self):
128
+        return urljoin(self.piwik_url, self.piwik_php_file)
129
+
130
+    @property
131
+    def js_url(self):
132
+        return urljoin(self.piwik_url, self.piwik_js_file)
133
+
134
+    def update_from_request(self, request):
135
+        """
136
+        Initializes the current tracker instance from a Django-like requests object.
137
+        If the request argument is None or does not have a dict as the META attribute, this function does nothing.
138
+        """
139
+        if not request:
140
+            return
141
+
142
+        meta = getattr(request, 'META', {})
143
+
144
+        if not isinstance(meta, dict):
145
+            return
146
+
147
+        self.user_agent = meta.get('HTTP_USER_AGENT', None)
148
+        self.referer = meta.get('HTTP_REFERER', None)
149
+        self.lang = meta.get('HTTP_ACCEPT_LANGUAGE', None)
150
+
151
+        if hasattr(request, 'build_absolute_uri'):
152
+            bau = request.build_absolute_uri
153
+            if callable(bau):
154
+                self.url = bau()
155
+
156
+        def _get_client_ip():
157
+            if 'HTTP_X_FORWARDED_FOR' in meta:
158
+                return meta['HTTP_X_FORWARDED_FOR'].split(",")[0]
159
+            else:
160
+                return meta.get('REMOTE_ADDR', None)
161
+
162
+        self.client_ip = _get_client_ip()
163
+
164
+    def _build_cvars(self, value):
165
+        """
166
+        Converts a custom vars dictionary to it's JSON representation usable for the Piwik API.
167
+        """
168
+        if not value:
169
+            return None
170
+
171
+        d = {}
172
+
173
+        for i, item in enumerate(value.items(), start=1):
174
+            d[i] = list(item)
175
+
176
+        return json.dumps(d)
177
+
178
+    def _build_parameters(self, **kwargs):
179
+        d = {
180
+            'idsite': self.idsite,
181
+            'rec': '1',
182
+            'apiv': PiwikTracker.API_VERSION,
183
+        }
184
+
185
+        for property_name, parameter_name in PARAMETERS.items():
186
+            if not parameter_name:
187
+                continue
188
+
189
+            value = kwargs.get(property_name, None) or getattr(self, property_name, None)
190
+
191
+            token_auth = kwargs.get('token_auth', None) or getattr(self, 'token_auth', None)
192
+            if value and property_name in AUTH_RESTRICTED_PARAMS and not token_auth:
193
+                logging.info("Skipping %s because token_auth not set" % property_name)
194
+                continue
195
+
196
+            if value is None:
197
+                continue
198
+
199
+            if isinstance(value, bool):
200
+                value = 1 if value else 0
201
+            elif isinstance(value, datetime.datetime):
202
+                if not value.tzinfo:
203
+                    logging.warning("Passing a naive datetime may result in wrong data. Make sure you pass a datetime object with UTC timezone")
204
+                value = value.strftime('%Y-%m-%d %H:%M:%S')
205
+
206
+            if property_name in ('page_custom_vars', 'visit_custom_vars'):
207
+                value = self._build_cvars(value)
208
+                if not value:
209
+                    continue
210
+
211
+            d[parameter_name] = value
212
+
213
+        return d
214
+
215
+    def build_request_headers(self, params):
216
+        headers = {
217
+            'Accept': '*/*',
218
+            'Accept-Encoding': 'gzip, deflate',
219
+        }
220
+        headers.update(self.request_headers)
221
+
222
+        if self.spoof_request:
223
+            # this is only used for server-to-server calls. By putting the values into the HTTP headers
224
+            # and dropping them from the payload we will transfer less to the server while carrying the same information.
225
+            for p, h in (('ua', 'User-Agent'), ('lang', 'Accept-Language'), ('urlref', 'Referer')):
226
+                if p in params:
227
+                    headers[h] = params[p]
228
+                    del params[p]
229
+
230
+        return headers
231
+
232
+    def track_page_view(self, **kwargs):
233
+        params = self._build_parameters(**kwargs)
234
+        if '_id' not in params:
235
+            params['_id'] = md5(os.urandom(16)).hexdigest()[:15]
236
+        if '_idts' not in params:
237
+            params['_idts'] = int(time.time())
238
+
239
+        headers = self.build_request_headers(params)
240
+        logging.debug("Tracking variables: %s" % params)
241
+        logging.debug("Tracking headers: %s" % headers)
242
+        try:
243
+            response = requests.post(self.php_url, data=params, headers=headers, **self.requests_arguments)
244
+            logging.debug("Tracking response: %s" % response)
245
+        except:
246
+            logging.exception("Tracking request failed")
247
+
248
+    def tracking_code(self, **kwargs):
249
+        return TrackingCodeBuilder(self).render(self._build_parameters(**kwargs))
250
+
251
+
252
+class TrackingCodeBuilder(object):
253
+    template = """<script type="text/javascript">
254
+  var _paq = _paq || [];
255
+{custom_vars}
256
+{event_tracking}
257
+{js_vars}
258
+  _paq.push(['trackPageView']);
259
+  _paq.push(['enableLinkTracking']);
260
+  (function() {{
261
+    _paq.push(['setTrackerUrl', '{tracker_url}']);
262
+    _paq.push(['setSiteId', {idsite}]);
263
+    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
264
+    g.type='text/javascript'; g.async=true; g.defer=true; g.src='{javascript_url}'; s.parentNode.insertBefore(g,s);
265
+  }})();
266
+</script>
267
+<noscript><p><img src="{tracker_url}?{tracking_args}" style="border:0;" alt="" /></p></noscript>"""
268
+
269
+    def __init__(self, tracker):
270
+        self.tracker = tracker
271
+
272
+    def _paq_push(self, l):
273
+        # python3's filter() does not return a list
274
+        return "_paq.push(%s);" % json.dumps(list(l))
275
+
276
+    def _event_tracker(self):
277
+        if not (self.tracker.event_category and self.tracker.event_action):
278
+            return ""
279
+        l = ['trackEvent', self.tracker.event_category, self.tracker.event_action, self.tracker.event_name, self.tracker.event_value]
280
+        return self._paq_push(filter(lambda x: x, l))
281
+
282
+    def _custom_vars(self):
283
+        def _inner():
284
+            for d, scope in (self.tracker.page_custom_vars, 'page'), (self.tracker.visit_custom_vars, 'visit'):
285
+                for i, item in enumerate(d.items(), start=1):
286
+                    if i > 5:
287
+                        break
288
+                    k, v = item
289
+                    l = ['setCustomVariable', i, k, v, scope]
290
+                    yield self._paq_push(l)
291
+        return '\n'.join(_inner())
292
+
293
+    def _common_vars(self, params):
294
+        def _inner():
295
+            extra_tracking_params = {}
296
+            if 'url' in params:
297
+                yield self._paq_push(['setCustomUrl', params['url']])
298
+            if 'urlref' in params:
299
+                yield self._paq_push(['setReferrerUrl', params['urlref']])
300
+            if 'action_name' in params:
301
+                yield self._paq_push(['setDocumentTitle', params['action_name']])
302
+            if 'new_visit' in params and params['new_visit']: # http://piwik.org/faq/how-to/#faq_187
303
+                extra_tracking_params['new_visit'] = 1
304
+                yield self._paq_push(["deleteCookies"])
305
+            if self.tracker.heartbeat_timer and int(self.tracker.heartbeat_timer) > 0:
306
+                yield self._paq_push(['enableHeartBeatTimer', self.tracker.heartbeat_timer])
307
+            if 'bots' in params and params['bots']:
308
+                extra_tracking_params['bots'] = 1
309
+
310
+            if extra_tracking_params:
311
+                yield self._paq_push(['appendToTrackingUrl', urlencode(extra_tracking_params)])
312
+
313
+        return '\n'.join(_inner())
314
+
315
+    def render(self, params):
316
+        # remove all tracking variables which doesn't do any good when used with the image or javascript
317
+        # tracking api
318
+        for x in ['url', 'ua', 'lang'] + [PARAMETERS[x] for x in AUTH_RESTRICTED_PARAMS]:
319
+            if x in params:
320
+                del params[x]
321
+
322
+        return TrackingCodeBuilder.template.format(tracker_url=self.tracker.php_url,
323
+                            javascript_url=self.tracker.js_url,
324
+                            idsite=self.tracker.idsite,
325
+                            tracking_args=urlencode(params),
326
+                            event_tracking=self._event_tracker(),
327
+                            custom_vars=self._custom_vars(),
328
+                            js_vars=self._common_vars(params),
329
+                )

BIN
pypiwik/tracker.pyo View File


+ 15
- 13
setup.py View File

@@ -3,18 +3,20 @@
3 3
 
4 4
 from setuptools import setup, find_packages
5 5
 
6
+from pypiwik import VERSION
7
+
6 8
 setup(
7
-	name='python-piwik', # The name is 'pypiwik' but it's taken on PyPI by an abandoned project
8
-	version='0.1.5.1',
9
-	description='Python implementation of the Piwik HTTP API',
10
-#	long_description=open('README.md').read(),
11
-	author='Johann Schmitz',
12
-	author_email='johann@j-schmitz.net',
13
-	url='https://ercpe.de/projects/pypiwik',
14
-	download_url='https://code.not-your-server.de/pypiwik.git/tags/',
15
-	packages=find_packages('src'),
16
-	package_dir={'': 'src'},
17
-	include_package_data=True,
18
-	zip_safe=False,
19
-	license='GPL-3',
9
+    name='python-piwik',  # The name is 'pypiwik' but it's taken on PyPI by an abandoned project
10
+    version=VERSION,
11
+    description='Python implementation of the Piwik HTTP API',
12
+    #    long_description=open('README.md').read(),
13
+    author='Johann Schmitz',
14
+    author_email='johann@j-schmitz.net',
15
+    url='https://ercpe.de/projects/pypiwik',
16
+    download_url='https://code.not-your-server.de/pypiwik.git/tags/',
17
+    packages=find_packages('src'),
18
+    package_dir={'': 'src'},
19
+    include_package_data=True,
20
+    zip_safe=False,
21
+    license='GPL-3',
20 22
 )

+ 0
- 28
src/pypiwik/decorators.py View File

@@ -1,28 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from pypiwik.tracker import PiwikTracker
3
-
4
-ON_SUCCESS = 1
5
-ON_ERROR = 2
6
-ALWAYS = 3
7
-
8
-def track_page_view(tracker=None, piwik_url=None, site_id=None, when=ON_SUCCESS, **tracker_kwargs):
9
-	if not ((piwik_url and site_id) or tracker):
10
-		raise ValueError("Either 'tracker' or 'piwik_url' and 'site_id' must be set")
11
-
12
-	def decorator_wrapper(func):
13
-		def inner(*args, **kwargs):
14
-			pt = tracker or PiwikTracker(piwik_url, site_id, **tracker_kwargs)
15
-
16
-			try:
17
-				ret = func(*args, **kwargs)
18
-
19
-				if when in (ON_SUCCESS, ALWAYS):
20
-					pt.track_page_view(**kwargs)
21
-
22
-				return ret
23
-			except:
24
-				if when in (ON_ERROR, ALWAYS):
25
-					pt.track_page_view(**kwargs)
26
-				raise
27
-		return inner
28
-	return decorator_wrapper

+ 0
- 329
src/pypiwik/tracker.py View File

@@ -1,329 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from hashlib import md5
3
-import json
4
-import logging
5
-import os
6
-import time
7
-import datetime
8
-import requests
9
-
10
-try:
11
-	from urllib.parse import urljoin, urlencode
12
-except ImportError:
13
-	from urlparse import urljoin
14
-	from urllib import urlencode
15
-
16
-PARAMETERS = {
17
-	# required parameters
18
-	'url': 'url', # The full URL for the current action.
19
-
20
-	# recommended parameters
21
-	'action_name': 'action_name', # The title of the action being tracked.
22
-	'referer': 'urlref', # The full HTTP Referrer URL.
23
-	'visit_custom_vars': '_cvar', # Visit scope custom variables.
24
-	'visit_count': '_idvc', # The current count of visits for this visitor.
25
-	'view_timestamp': '_viewts', # The UNIX timestamp of this visitor's previous visit.
26
-	'first_visit_timestamp': '_idts', # The UNIX timestamp of this visitor's first visit.
27
-	'campaign_name': '_rcn', # The Campaign name (see Tracking Campaigns).
28
-	'campaign_keywords': '_rck', # The Campaign Keyword (see Tracking Campaigns).
29
-	'resolution': 'res', # The resolution of the device the visitor is using, eg 1280x1024.
30
-	'hour': 'h', # The current hour (local time).
31
-	'minute': 'm', # The current minute (local time).
32
-	'second': 's', # The current second (local time).
33
-	'flash': 'fla', # Flash,
34
-	'java': 'java', # Java
35
-	'director': 'dir', # Director,
36
-	'quicktime': 'qt', # Quicktime,
37
-	'real_player': 'realp', # Real Player,
38
-	'pdf': 'pdf', # PDF
39
-	'wma': 'wma', # Windows Media
40
-	'gears': 'gears', # Gears
41
-	'silverlight': 'ag', # Silverlight
42
-	'cookie': 'cookie', # when set to 1, the visitor's client is known to support cookies.
43
-	'user_agent': 'ua', # An override value for the User-Agent HTTP header field.
44
-	'lang': 'lang', # An override value for the Accept-Language HTTP header field. This value is used to detect the visitor's country if GeoIP is not enabled.
45
-	'user_id': 'uid', # defines the User ID for this request. User ID is any non empty unique string identifying the user (such as an email address or a username).
46
-	'visitor_id': 'cid', # defines the visitor ID for this request.
47
-	'new_visit': 'new_visit', # If set to 1, will force a new visit to be created for this action. This feature is also available in Javascript.
48
-
49
-	# 'Optional Action info (measure Page view, Outlink, Download, Site search)',
50
-	'page_custom_vars': 'cvar', # Page scope custom variables.
51
-	'link': 'link', # An external URL the user has opened. Used for tracking outlink clicks. We recommend to also set the url parameter to this same value.
52
-	'download': 'download', # URL of a file the user has downloaded. Used for tracking downloads. We recommend to also set the url parameter to this same value.
53
-	'search_keyword': 'search', # The Site Search keyword. When specified, the request will not be tracked as a normal pageview but will instead be tracked as a Site Search request.
54
-	'search_category': 'search_cat', # when search is specified, you can optionally specify a search category with this parameter.
55
-	'search_count': 'search_count', # when search is specified, we also recommend to set the search_count to the number of search results displayed on the results page.
56
-
57
-	'goal_id': 'idgoal', # If specified, the tracking request will trigger a conversion for the goal of the website being tracked with this ID.
58
-	'revenue': 'revenue', # A monetary value that was generated as revenue by this goal conversion. Only used if idgoal is specified in the request.
59
-	'gt_ms': 'gt_ms', # The amount of time it took the server to generate this action, in milliseconds.
60
-	'charset': 'cs', # The charset of the page being tracked. Specify the charset if the data you send to Piwik is encoded in a different character set than the default utf-8.
61
-
62
-	# Optional Event Tracking info
63
-	'event_category': 'e_c', # The event category. Must not be empty. (eg. Videos, Music, Games...)
64
-	'event_action': 'e_a', # The event action. Must not be empty. (eg. Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
65
-	'event_name': 'e_n', # The event name. (eg. a Movie name, or Song name, or File name...)
66
-	'event_value': 'e_v', # The event value. Must be a float or integer value (numeric), not a string.
67
-
68
-	# Optional Content Tracking info
69
-	'content_name': 'c_n', # The name of the content. For instance 'Ad Foo Bar'
70
-	'content_piece': 'c_p', # The actual content piece. For instance the path to an image, video, audio, any text
71
-	'content_target': 'c_t', # The target of the content. For instance the URL of a landing page
72
-	'content_interaction': 'c_i', # The name of the interaction with the content. For instance a 'click'
73
-
74
-	# Other parameters (require authentication via token_auth)
75
-	'token_auth': 'token_auth', # 32 character authorization key used to authenticate the API request.
76
-	'client_ip': 'cip', # Override value for the visitor IP (both IPv4 and IPv6 notations supported).
77
-	'client_dt': 'cdt', # Override for the datetime of the request (normally the current time is used).
78
-	'country': 'country', # An override value for the country. Should be set to the two letter country code of the visitor (lowercase), eg fr, de, us.
79
-	'region': 'region', # An override value for the region. Should be set to the two letter region code as defined by MaxMind's GeoIP databases.
80
-	'city': 'city', # An override value for the city. The name of the city the visitor is located in, eg, Tokyo.
81
-	'lat': 'lat', # An override value for the visitor's latitude, eg 22.456.
82
-	'long': 'long', # An override value for the visitor's longitude, eg 22.456.
83
-
84
-	'track_bots': 'bots', # Set to true to track bots
85
-
86
-	'heartbeat_timer': None, # Set to a positive integer to enable the heartbeat timer
87
-}
88
-
89
-AUTH_RESTRICTED_PARAMS = ('token_auth', 'client_ip', 'client_dt', 'country', 'region', 'city', 'lat', 'long')
90
-
91
-class PiwikTracker(object):
92
-	API_VERSION = 1
93
-
94
-	def __init__(self, piwik_url, site_id, request=None, values=None, **kwargs):
95
-		super(PiwikTracker, self).__init__()
96
-		self.piwik_url = piwik_url
97
-		self.idsite = site_id
98
-
99
-		# initialize all tracking variables on this instance
100
-		values = values or {}
101
-		values.update(kwargs)
102
-		self.update(dict((p, values.get(p, None)) for p in PARAMETERS.keys()))
103
-
104
-		self.visit_custom_vars = {}
105
-		self.page_custom_vars = {}
106
-
107
-		self.spoof_request = True
108
-
109
-		# defaults for the requests module
110
-		self.request_headers = {}
111
-		self.requests_arguments = {
112
-			'timeout': 3
113
-		}
114
-
115
-		# default filenames for the tracker file and the js file
116
-		self.piwik_php_file = 'piwik.php'
117
-		self.piwik_js_file = 'piwik.js'
118
-
119
-		self.update_from_request(request)
120
-
121
-	def update(self, values):
122
-		for property_name in PARAMETERS.keys():
123
-			if property_name in values:
124
-				setattr(self, property_name, values[property_name])
125
-
126
-	@property
127
-	def php_url(self):
128
-		return urljoin(self.piwik_url, self.piwik_php_file)
129
-
130
-	@property
131
-	def js_url(self):
132
-		return urljoin(self.piwik_url, self.piwik_js_file)
133
-
134
-	def update_from_request(self, request):
135
-		"""
136
-		Initializes the current tracker instance from a Django-like requests object.
137
-		If the request argument is None or does not have a dict as the META attribute, this function does nothing.
138
-		"""
139
-		if not request:
140
-			return
141
-
142
-		meta = getattr(request, 'META', {})
143
-
144
-		if not isinstance(meta, dict):
145
-			return
146
-
147
-		self.user_agent = meta.get('HTTP_USER_AGENT', None)
148
-		self.referer = meta.get('HTTP_REFERER', None)
149
-		self.lang = meta.get('HTTP_ACCEPT_LANGUAGE', None)
150
-
151
-		if hasattr(request, 'build_absolute_uri'):
152
-			bau = request.build_absolute_uri
153
-			if callable(bau):
154
-				self.url = bau()
155
-
156
-		def _get_client_ip():
157
-			if 'HTTP_X_FORWARDED_FOR' in meta:
158
-				return meta['HTTP_X_FORWARDED_FOR'].split(",")[0]
159
-			else:
160
-				return meta.get('REMOTE_ADDR', None)
161
-
162
-		self.client_ip = _get_client_ip()
163
-
164
-	def _build_cvars(self, value):
165
-		"""
166
-		Converts a custom vars dictionary to it's JSON representation usable for the Piwik API.
167
-		"""
168
-		if not value:
169
-			return None
170
-
171
-		d = {}
172
-
173
-		for i, item in enumerate(value.items(), start=1):
174
-			d[i] = list(item)
175
-
176
-		return json.dumps(d)
177
-
178
-	def _build_parameters(self, **kwargs):
179
-		d = {
180
-			'idsite': self.idsite,
181
-			'rec': '1',
182
-			'apiv': PiwikTracker.API_VERSION,
183
-		}
184
-
185
-		for property_name, parameter_name in PARAMETERS.items():
186
-			if not parameter_name:
187
-				continue
188
-
189
-			value = kwargs.get(property_name, None) or getattr(self, property_name, None)
190
-
191
-			token_auth = kwargs.get('token_auth', None) or getattr(self, 'token_auth', None)
192
-			if value and property_name in AUTH_RESTRICTED_PARAMS and not token_auth:
193
-				logging.info("Skipping %s because token_auth not set" % property_name)
194
-				continue
195
-
196
-			if value is None:
197
-				continue
198
-
199
-			if isinstance(value, bool):
200
-				value = 1 if value else 0
201
-			elif isinstance(value, datetime.datetime):
202
-				if not value.tzinfo:
203
-					logging.warning("Passing a naive datetime may result in wrong data. Make sure you pass a datetime object with UTC timezone")
204
-				value = value.strftime('%Y-%m-%d %H:%M:%S')
205
-
206
-			if property_name in ('page_custom_vars', 'visit_custom_vars'):
207
-				value = self._build_cvars(value)
208
-				if not value:
209
-					continue
210
-
211
-			d[parameter_name] = value
212
-
213
-		return d
214
-
215
-	def build_request_headers(self, params):
216
-		headers = {
217
-			'Accept': '*/*',
218
-			'Accept-Encoding': 'gzip, deflate',
219
-		}
220
-		headers.update(self.request_headers)
221
-
222
-		if self.spoof_request:
223
-			# this is only used for server-to-server calls. By putting the values into the HTTP headers
224
-			# and dropping them from the payload we will transfer less to the server while carrying the same information.
225
-			for p, h in (('ua', 'User-Agent'), ('lang', 'Accept-Language'), ('urlref', 'Referer')):
226
-				if p in params:
227
-					headers[h] = params[p]
228
-					del params[p]
229
-
230
-		return headers
231
-
232
-	def track_page_view(self, **kwargs):
233
-		params = self._build_parameters(**kwargs)
234
-		if '_id' not in params:
235
-			params['_id'] = md5(os.urandom(16)).hexdigest()[:15]
236
-		if '_idts' not in params:
237
-			params['_idts'] = int(time.time())
238
-
239
-		headers = self.build_request_headers(params)
240
-		logging.debug("Tracking variables: %s" % params)
241
-		logging.debug("Tracking headers: %s" % headers)
242
-		try:
243
-			response = requests.post(self.php_url, data=params, headers=headers, **self.requests_arguments)
244
-			logging.debug("Tracking response: %s" % response)
245
-		except:
246
-			logging.exception("Tracking request failed")
247
-
248
-	def tracking_code(self, **kwargs):
249
-		return TrackingCodeBuilder(self).render(self._build_parameters(**kwargs))
250
-
251
-
252
-class TrackingCodeBuilder(object):
253
-	template = """<script type="text/javascript">
254
-  var _paq = _paq || [];
255
-{custom_vars}
256
-{event_tracking}
257
-{js_vars}
258
-  _paq.push(['trackPageView']);
259
-  _paq.push(['enableLinkTracking']);
260
-  (function() {{
261
-    _paq.push(['setTrackerUrl', '{tracker_url}']);
262
-    _paq.push(['setSiteId', {idsite}]);
263
-    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
264
-    g.type='text/javascript'; g.async=true; g.defer=true; g.src='{javascript_url}'; s.parentNode.insertBefore(g,s);
265
-  }})();
266
-</script>
267
-<noscript><p><img src="{tracker_url}?{tracking_args}" style="border:0;" alt="" /></p></noscript>"""
268
-
269
-	def __init__(self, tracker):
270
-		self.tracker = tracker
271
-
272
-	def _paq_push(self, l):
273
-		# python3's filter() does not return a list
274
-		return "_paq.push(%s);" % json.dumps(list(l))
275
-
276
-	def _event_tracker(self):
277
-		if not (self.tracker.event_category and self.tracker.event_action):
278
-			return ""
279
-		l = ['trackEvent', self.tracker.event_category, self.tracker.event_action, self.tracker.event_name, self.tracker.event_value]
280
-		return self._paq_push(filter(lambda x: x, l))
281
-
282
-	def _custom_vars(self):
283
-		def _inner():
284
-			for d, scope in (self.tracker.page_custom_vars, 'page'), (self.tracker.visit_custom_vars, 'visit'):
285
-				for i, item in enumerate(d.items(), start=1):
286
-					if i > 5:
287
-						break
288
-					k, v = item
289
-					l = ['setCustomVariable', i, k, v, scope]
290
-					yield self._paq_push(l)
291
-		return '\n'.join(_inner())
292
-
293
-	def _common_vars(self, params):
294
-		def _inner():
295
-			extra_tracking_params = {}
296
-			if 'url' in params:
297
-				yield self._paq_push(['setCustomUrl', params['url']])
298
-			if 'urlref' in params:
299
-				yield self._paq_push(['setReferrerUrl', params['urlref']])
300
-			if 'action_name' in params:
301
-				yield self._paq_push(['setDocumentTitle', params['action_name']])
302
-			if 'new_visit' in params and params['new_visit']: # http://piwik.org/faq/how-to/#faq_187
303
-				extra_tracking_params['new_visit'] = 1
304
-				yield self._paq_push(["deleteCookies"])
305
-			if self.tracker.heartbeat_timer and int(self.tracker.heartbeat_timer) > 0:
306
-				yield self._paq_push(['enableHeartBeatTimer', self.tracker.heartbeat_timer])
307
-			if 'bots' in params and params['bots']:
308
-				extra_tracking_params['bots'] = 1
309
-
310
-			if extra_tracking_params:
311
-				yield self._paq_push(['appendToTrackingUrl', urlencode(extra_tracking_params)])
312
-
313
-		return '\n'.join(_inner())
314
-
315
-	def render(self, params):
316
-		# remove all tracking variables which doesn't do any good when used with the image or javascript
317
-		# tracking api
318
-		for x in ['url', 'ua', 'lang'] + [PARAMETERS[x] for x in AUTH_RESTRICTED_PARAMS]:
319
-			if x in params:
320
-				del params[x]
321
-
322
-		return TrackingCodeBuilder.template.format(tracker_url=self.tracker.php_url,
323
-							javascript_url=self.tracker.js_url,
324
-							idsite=self.tracker.idsite,
325
-							tracking_args=urlencode(params),
326
-							event_tracking=self._event_tracker(),
327
-							custom_vars=self._custom_vars(),
328
-							js_vars=self._common_vars(params),
329
-				)

+ 107
- 107
tests/builder.py View File

@@ -4,11 +4,11 @@ import difflib
4 4
 import unittest
5 5
 
6 6
 try:
7
-	# py3
8
-	from urllib.parse import urlparse, parse_qs, urljoin, urlunparse, urlencode
7
+    # py3
8
+    from urllib.parse import urlparse, parse_qs, urljoin, urlunparse, urlencode
9 9
 except ImportError:
10
-	from urlparse import urljoin, urlparse, parse_qs, urlunparse
11
-	from urllib import urlencode
10
+    from urlparse import urljoin, urlparse, parse_qs, urlunparse
11
+    from urllib import urlencode
12 12
 
13 13
 from pypiwik.tracker import TrackingCodeBuilder, PiwikTracker
14 14
 import re
@@ -17,48 +17,48 @@ img_src_re = re.compile('img src="(.*?)"', re.IGNORECASE)
17 17
 
18 18
 class TrackingCodeBuilderTest(unittest.TestCase):
19 19
 
20
-	def assertTrackingCodeEquals(self, first, second):
20
+    def assertTrackingCodeEquals(self, first, second):
21 21
 
22
-		def _clean(s):
23
-			m = img_src_re.search(s)
24
-			assert m and len(m.groups()) == 1, "String does not have an image tracker code"
22
+        def _clean(s):
23
+            m = img_src_re.search(s)
24
+            assert m and len(m.groups()) == 1, "String does not have an image tracker code"
25 25
 
26
-			url = m.group(1)
27
-			urlparts = urlparse(url)
28
-			query = parse_qs(urlparts.query)
29
-			d = OrderedDict(sorted(((k, v[0]) for k, v in query.items()), key=lambda x: x[0]))
30
-			new_url = urlunparse((urlparts.scheme, urlparts.netloc, urlparts.path, urlparts.params, urlencode(d), urlparts.fragment))
26
+            url = m.group(1)
27
+            urlparts = urlparse(url)
28
+            query = parse_qs(urlparts.query)
29
+            d = OrderedDict(sorted(((k, v[0]) for k, v in query.items()), key=lambda x: x[0]))
30
+            new_url = urlunparse((urlparts.scheme, urlparts.netloc, urlparts.path, urlparts.params, urlencode(d), urlparts.fragment))
31 31
 
32
-			s2 = img_src_re.sub(new_url, s)
33
-			return [x.strip(' ') for x in s2.splitlines(True) if x.strip(' ') if x.strip(' ') != '\n']
32
+            s2 = img_src_re.sub(new_url, s)
33
+            return [x.strip(' ') for x in s2.splitlines(True) if x.strip(' ') if x.strip(' ') != '\n']
34 34
 
35
-		t1_lines = _clean(first)
36
-		t2_lines = _clean(second)
35
+        t1_lines = _clean(first)
36
+        t2_lines = _clean(second)
37 37
 
38
-		assert difflib.SequenceMatcher(None, t1_lines, t2_lines).ratio() == 1.0, \
39
-				"Tracking codes do not match: %s" % ''.join(difflib.ndiff(t1_lines, t2_lines))
38
+        assert difflib.SequenceMatcher(None, t1_lines, t2_lines).ratio() == 1.0, \
39
+                "Tracking codes do not match: %s" % ''.join(difflib.ndiff(t1_lines, t2_lines))
40 40
 
41
-	def assertImageTrackingVars(self, tracking_code, expected_vars):
42
-		m = img_src_re.search(tracking_code)
43
-		assert m and len(m.groups()) == 1, "String does not have an image tracker code"
41
+    def assertImageTrackingVars(self, tracking_code, expected_vars):
42
+        m = img_src_re.search(tracking_code)
43
+        assert m and len(m.groups()) == 1, "String does not have an image tracker code"
44 44
 
45
-		url = m.group(1)
46
-		urlparts = urlparse(url)
47
-		query = parse_qs(urlparts.query)
45
+        url = m.group(1)
46
+        urlparts = urlparse(url)
47
+        query = parse_qs(urlparts.query)
48 48
 
49
-		for k, v in expected_vars.items():
50
-			assert k in query, "Image tracker query string does not contain '%s'" % k
51
-			assert query[k] == [v], \
52
-				"Image tracker variable %s does not match expected value '%s'. It's '%s'" % (k, v, query[k])
49
+        for k, v in expected_vars.items():
50
+            assert k in query, "Image tracker query string does not contain '%s'" % k
51
+            assert query[k] == [v], \
52
+                "Image tracker variable %s does not match expected value '%s'. It's '%s'" % (k, v, query[k])
53 53
 
54 54
 
55
-	def test_simple_tracking_code(self):
56
-		tracker = PiwikTracker('http://localhost', 1)
57
-		builder = TrackingCodeBuilder(tracker)
55
+    def test_simple_tracking_code(self):
56
+        tracker = PiwikTracker('http://localhost', 1)
57
+        builder = TrackingCodeBuilder(tracker)
58 58
 
59
-		s = builder.render(tracker._build_parameters())
59
+        s = builder.render(tracker._build_parameters())
60 60
 
61
-		self.assertTrackingCodeEquals(s, """<script type="text/javascript">
61
+        self.assertTrackingCodeEquals(s, """<script type="text/javascript">
62 62
 var _paq = _paq || [];
63 63
 _paq.push(['trackPageView']);
64 64
 _paq.push(['enableLinkTracking']);
@@ -71,15 +71,15 @@ _paq.push(['enableLinkTracking']);
71 71
 </script>
72 72
 <noscript><p><img src="http://localhost/piwik.php?idsite=1&rec=1&apiv=1" style="border:0;" alt="" /></p></noscript>""")
73 73
 
74
-	def test_event_tracking_code(self):
75
-		tracker = PiwikTracker('http://localhost', 1)
76
-		tracker.event_category = "event_category"
77
-		tracker.event_action = "event_action"
78
-		builder = TrackingCodeBuilder(tracker)
74
+    def test_event_tracking_code(self):
75
+        tracker = PiwikTracker('http://localhost', 1)
76
+        tracker.event_category = "event_category"
77
+        tracker.event_action = "event_action"
78
+        builder = TrackingCodeBuilder(tracker)
79 79
 
80
-		s = builder.render(tracker._build_parameters())
80
+        s = builder.render(tracker._build_parameters())
81 81
 
82
-		self.assertTrackingCodeEquals(s, """<script type="text/javascript">
82
+        self.assertTrackingCodeEquals(s, """<script type="text/javascript">
83 83
 var _paq = _paq || [];
84 84
 _paq.push(["trackEvent", "event_category", "event_action"]);
85 85
 _paq.push(['trackPageView']);
@@ -93,69 +93,69 @@ _paq.push(['enableLinkTracking']);
93 93
 </script>
94 94
 <noscript><p><img src="http://localhost/piwik.php?idsite=1&rec=1&apiv=1&e_a=event_action&e_c=event_category" style="border:0;" alt="" /></p></noscript>""")
95 95
 
96
-	def test_method_call_generation(self):
97
-		# test new_visit=True
98
-		tracker = PiwikTracker('http://localhost', 1)
99
-		tracker.new_visit = True
100
-		builder = TrackingCodeBuilder(tracker)
101
-
102
-		s = builder.render(tracker._build_parameters())
103
-
104
-		assert '_paq.push(["deleteCookies"]);' in s
105
-		assert '_paq.push(["appendToTrackingUrl", "new_visit=1"]);' in s
106
-		self.assertImageTrackingVars(s, {
107
-			'new_visit': '1'
108
-		})
109
-
110
-		# test track_bots=True
111
-		tracker = PiwikTracker('http://localhost', 1)
112
-		tracker.track_bots = True
113
-		builder = TrackingCodeBuilder(tracker)
114
-
115
-		s = builder.render(tracker._build_parameters())
116
-
117
-		assert '_paq.push(["appendToTrackingUrl", "bots=1"]);' in s
118
-		self.assertImageTrackingVars(s, {
119
-			'bots': '1'
120
-		})
121
-
122
-		# test action_name
123
-		tracker = PiwikTracker('http://localhost', 1)
124
-		tracker.action_name = "Foo / Bar"
125
-		builder = TrackingCodeBuilder(tracker)
126
-		s = builder.render(tracker._build_parameters())
127
-
128
-		assert '_paq.push(["setDocumentTitle", "Foo / Bar"]);' in s
129
-		self.assertImageTrackingVars(s, {
130
-			'action_name': 'Foo / Bar'
131
-		})
132
-
133
-		# test action_name
134
-		tracker = PiwikTracker('http://localhost', 1)
135
-		tracker.referer = 'http://foobar.local'
136
-		builder = TrackingCodeBuilder(tracker)
137
-		s = builder.render(tracker._build_parameters())
138
-
139
-		assert '_paq.push(["setReferrerUrl", "http://foobar.local"]);' in s
140
-		self.assertImageTrackingVars(s, {
141
-			'urlref': 'http://foobar.local'
142
-		})
143
-
144
-	def test_heartbeat_timer(self):
145
-		tracker = PiwikTracker('http://localhost', 1)
146
-		tracker.heartbeat_timer = None
147
-		builder = TrackingCodeBuilder(tracker)
148
-		s = builder.render(tracker._build_parameters())
149
-		assert 'enableHeartBeatTimer' not in s, "Heartbeat timer enabled but set to None"
150
-
151
-		tracker = PiwikTracker('http://localhost', 1)
152
-		tracker.heartbeat_timer = 0
153
-		builder = TrackingCodeBuilder(tracker)
154
-		s = builder.render(tracker._build_parameters())
155
-		assert 'enableHeartBeatTimer' not in s, "Heartbeat timer enabled but set to 0"
156
-
157
-		tracker = PiwikTracker('http://localhost', 1)
158
-		tracker.heartbeat_timer = 10
159
-		builder = TrackingCodeBuilder(tracker)
160
-		s = builder.render(tracker._build_parameters())
161
-		assert '_paq.push(["enableHeartBeatTimer", 10]);' in s, "heartbeat_timer set but not enabled in tracking code"
96
+    def test_method_call_generation(self):
97
+        # test new_visit=True
98
+        tracker = PiwikTracker('http://localhost', 1)
99
+        tracker.new_visit = True
100
+        builder = TrackingCodeBuilder(tracker)
101
+
102
+        s = builder.render(tracker._build_parameters())
103
+
104
+        assert '_paq.push(["deleteCookies"]);' in s
105
+        assert '_paq.push(["appendToTrackingUrl", "new_visit=1"]);' in s
106
+        self.assertImageTrackingVars(s, {
107
+            'new_visit': '1'
108
+        })
109
+
110
+        # test track_bots=True
111
+        tracker = PiwikTracker('http://localhost', 1)
112
+        tracker.track_bots = True
113
+        builder = TrackingCodeBuilder(tracker)
114
+
115
+        s = builder.render(tracker._build_parameters())
116
+
117
+        assert '_paq.push(["appendToTrackingUrl", "bots=1"]);' in s
118
+        self.assertImageTrackingVars(s, {
119
+            'bots': '1'
120
+        })
121
+
122
+        # test action_name
123
+        tracker = PiwikTracker('http://localhost', 1)
124
+        tracker.action_name = "Foo / Bar"
125
+        builder = TrackingCodeBuilder(tracker)
126
+        s = builder.render(tracker._build_parameters())
127
+
128
+        assert '_paq.push(["setDocumentTitle", "Foo / Bar"]);' in s
129
+        self.assertImageTrackingVars(s, {
130
+            'action_name': 'Foo / Bar'
131
+        })
132
+
133
+        # test action_name
134
+        tracker = PiwikTracker('http://localhost', 1)
135
+        tracker.referer = 'http://foobar.local'
136
+        builder = TrackingCodeBuilder(tracker)
137
+        s = builder.render(tracker._build_parameters())
138
+
139
+        assert '_paq.push(["setReferrerUrl", "http://foobar.local"]);' in s
140
+        self.assertImageTrackingVars(s, {
141
+            'urlref': 'http://foobar.local'
142
+        })
143
+
144
+    def test_heartbeat_timer(self):
145
+        tracker = PiwikTracker('http://localhost', 1)
146
+        tracker.heartbeat_timer = None
147
+        builder = TrackingCodeBuilder(tracker)
148
+        s = builder.render(tracker._build_parameters())
149
+        assert 'enableHeartBeatTimer' not in s, "Heartbeat timer enabled but set to None"
150
+
151
+        tracker = PiwikTracker('http://localhost', 1)
152
+        tracker.heartbeat_timer = 0
153
+        builder = TrackingCodeBuilder(tracker)
154
+        s = builder.render(tracker._build_parameters())
155
+        assert 'enableHeartBeatTimer' not in s, "Heartbeat timer enabled but set to 0"
156
+
157
+        tracker = PiwikTracker('http://localhost', 1)
158
+        tracker.heartbeat_timer = 10
159
+        builder = TrackingCodeBuilder(tracker)
160
+        s = builder.render(tracker._build_parameters())
161
+        assert '_paq.push(["enableHeartBeatTimer", 10]);' in s, "heartbeat_timer set but not enabled in tracking code"

+ 51
- 51
tests/decorators.py View File

@@ -7,74 +7,74 @@ from pypiwik.tracker import PiwikTracker
7 7
 
8 8
 class DecoratorTest(unittest.TestCase):
9 9
 
10
-	def test_args(self):
11
-		self.assertRaises(ValueError, track_page_view)
10
+    def test_args(self):
11
+        self.assertRaises(ValueError, track_page_view)
12 12
 
13
-		self.assertRaises(ValueError, track_page_view, piwik_url='test')
13
+        self.assertRaises(ValueError, track_page_view, piwik_url='test')
14 14
 
15
-		self.assertRaises(ValueError, track_page_view, site_id=1)
15
+        self.assertRaises(ValueError, track_page_view, site_id=1)
16 16
 
17
-	@unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
18
-	def test_when_success(self):
19
-		from unittest.mock import MagicMock
20
-		tracker = PiwikTracker('http://localhost', 1)
21
-		tracker.track_page_view = MagicMock()
17
+    @unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
18
+    def test_when_success(self):
19
+        from unittest.mock import MagicMock
20
+        tracker = PiwikTracker('http://localhost', 1)
21
+        tracker.track_page_view = MagicMock()
22 22
 
23
-		@track_page_view(tracker=tracker, when=ON_SUCCESS)
24
-		def my_func():
25
-			pass
26
-		my_func()
23
+        @track_page_view(tracker=tracker, when=ON_SUCCESS)
24
+        def my_func():
25
+            pass
26
+        my_func()
27 27
 
28
-		tracker.track_page_view.assert_called()
28
+        tracker.track_page_view.assert_called()
29 29
 
30
-		tracker.track_page_view.reset_mock()
30
+        tracker.track_page_view.reset_mock()
31 31
 
32
-		@track_page_view(tracker=tracker, when=ON_SUCCESS)
33
-		def my_func2():
34
-			raise Exception("lalala")
32
+        @track_page_view(tracker=tracker, when=ON_SUCCESS)
33
+        def my_func2():
34
+            raise Exception("lalala")
35 35
 
36
-		self.assertRaises(Exception, my_func2)
37
-		assert not tracker.track_page_view.called, "track_page_view() should not be called due to an raised exception"
36
+        self.assertRaises(Exception, my_func2)
37
+        assert not tracker.track_page_view.called, "track_page_view() should not be called due to an raised exception"
38 38
 
39
-	@unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
40
-	def test_when_success(self):
41
-		from unittest.mock import MagicMock
42
-		tracker = PiwikTracker('http://localhost', 1)
43
-		tracker.track_page_view = MagicMock()
39
+    @unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
40
+    def test_when_success(self):
41
+        from unittest.mock import MagicMock
42
+        tracker = PiwikTracker('http://localhost', 1)
43
+        tracker.track_page_view = MagicMock()
44 44
 
45
-		@track_page_view(tracker=tracker, when=ON_ERROR)
46
-		def my_func():
47
-			raise Exception("lalala")
45
+        @track_page_view(tracker=tracker, when=ON_ERROR)
46
+        def my_func():
47
+            raise Exception("lalala")
48 48
 
49
-		self.assertRaises(Exception, my_func)
50
-		tracker.track_page_view.assert_called()
49
+        self.assertRaises(Exception, my_func)
50
+        tracker.track_page_view.assert_called()
51 51
 
52
-		tracker.track_page_view.reset_mock()
52
+        tracker.track_page_view.reset_mock()
53 53
 
54
-		@track_page_view(tracker=tracker, when=ON_ERROR)
55
-		def my_func2():
56
-			pass
54
+        @track_page_view(tracker=tracker, when=ON_ERROR)
55
+        def my_func2():
56
+            pass
57 57
 
58
-		assert not tracker.track_page_view.called, "track_page_view() should not be called because no exception was raised"
58
+        assert not tracker.track_page_view.called, "track_page_view() should not be called because no exception was raised"
59 59
 
60
-	@unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
61
-	def test_when_success(self):
62
-		from unittest.mock import MagicMock
63
-		tracker = PiwikTracker('http://localhost', 1)
64
-		tracker.track_page_view = MagicMock()
60
+    @unittest.skipIf(sys.version_info < (3,0), "Mock not available in python 2.x")
61
+    def test_when_success(self):
62
+        from unittest.mock import MagicMock
63
+        tracker = PiwikTracker('http://localhost', 1)
64
+        tracker.track_page_view = MagicMock()
65 65
 
66
-		@track_page_view(tracker=tracker, when=ALWAYS)
67
-		def my_func():
68
-			pass
69
-		my_func()
66
+        @track_page_view(tracker=tracker, when=ALWAYS)
67
+        def my_func():
68
+            pass
69
+        my_func()
70 70
 
71
-		tracker.track_page_view.assert_called()
71
+        tracker.track_page_view.assert_called()
72 72
 
73
-		tracker.track_page_view.reset_mock()
73
+        tracker.track_page_view.reset_mock()
74 74
 
75
-		@track_page_view(tracker=tracker, when=ALWAYS)
76
-		def my_func2():
77
-			raise Exception("lalala")
75
+        @track_page_view(tracker=tracker, when=ALWAYS)
76
+        def my_func2():
77
+            raise Exception("lalala")
78 78
 
79
-		self.assertRaises(Exception, my_func2)
80
-		tracker.track_page_view.assert_called()
79
+        self.assertRaises(Exception, my_func2)
80
+        tracker.track_page_view.assert_called()

+ 154
- 154
tests/tracker.py View File

@@ -8,167 +8,167 @@ from pypiwik.tracker import PiwikTracker, PARAMETERS, AUTH_RESTRICTED_PARAMS
8 8
 
9 9
 class TrackerTest(unittest.TestCase):
10 10
 
11
-	def test_init(self):
12
-		tracker = PiwikTracker('http://localhost', 1)
11
+    def test_init(self):
12
+        tracker = PiwikTracker('http://localhost', 1)
13 13
 
14
-		for property_name in PARAMETERS.keys():
15
-			assert hasattr(tracker, property_name), "Missing property on %s: %s" % (tracker, property_name)
14
+        for property_name in PARAMETERS.keys():
15
+            assert hasattr(tracker, property_name), "Missing property on %s: %s" % (tracker, property_name)
16 16
 
17
-	def test_from_request(self):
18
-		class FakeRequest(object):
19
-			META = {
20
-				'HTTP_USER_AGENT': 'ua',
21
-				'HTTP_REFERER': 'ref',
22
-				'HTTP_ACCEPT_LANGUAGE': 'lang'
23
-			}
17
+    def test_from_request(self):
18
+        class FakeRequest(object):
19
+            META = {
20
+                'HTTP_USER_AGENT': 'ua',
21
+                'HTTP_REFERER': 'ref',
22
+                'HTTP_ACCEPT_LANGUAGE': 'lang'
23
+            }
24 24
 
25
-		tracker = PiwikTracker('http://localhost', 1, FakeRequest())
26
-		assert tracker.user_agent == 'ua'
27
-		assert tracker.referer == 'ref'
28
-		assert tracker.lang == 'lang'
29
-		assert tracker.url is None
30
-		assert tracker.client_ip is None
25
+        tracker = PiwikTracker('http://localhost', 1, FakeRequest())
26
+        assert tracker.user_agent == 'ua'
27
+        assert tracker.referer == 'ref'
28
+        assert tracker.lang == 'lang'
29
+        assert tracker.url is None
30
+        assert tracker.client_ip is None
31 31
 
32
-	def test_from_request_with_url(self):
33
-		class FakeRequest(object):
34
-			def build_absolute_uri(self):
35
-				return "http://foobar.local/test123"
32
+    def test_from_request_with_url(self):
33
+        class FakeRequest(object):
34
+            def build_absolute_uri(self):
35
+                return "http://foobar.local/test123"
36 36
 
37
-		tracker = PiwikTracker('http://localhost', 1, FakeRequest())
38
-		assert tracker.url == "http://foobar.local/test123"
39
-
40
-	def test_from_request_client_ip(self):
41
-		class FakeRequest(object):
42
-			META = { 'HTTP_X_FORWARDED_FOR': '127.1.2.3' }
43
-
44
-		tracker = PiwikTracker('http://localhost', 1, FakeRequest())
45
-		assert tracker.client_ip == '127.1.2.3'
46
-
47
-	def test_from_bad_request(self):
48
-		class FakeRequest(object):
49
-			pass
50
-
51
-		tracker = PiwikTracker('http://localhost', 1, FakeRequest())
52
-		self.assertDictEqual({'apiv': PiwikTracker.API_VERSION, 'idsite': 1, 'rec': '1'}, tracker._build_parameters())
53
-
54
-		class FakeRequest2(object):
55
-			META = "test"
56
-
57
-		tracker = PiwikTracker('http://localhost', 1, FakeRequest2())
58
-		self.assertDictEqual({'apiv': PiwikTracker.API_VERSION, 'idsite': 1, 'rec': '1'}, tracker._build_parameters())
59
-
60
-
61
-	def test_parameters(self):
62
-		site_id = 123
63
-		tracker = PiwikTracker('http://localhost', site_id)
64
-
65
-		for property_name in PARAMETERS.keys():
66
-			if property_name in ('page_custom_vars', 'visit_custom_vars'):
67
-				continue
68
-			setattr(tracker, property_name, 'test')
69
-
70
-		params = tracker._build_parameters()
71
-		assert 'apiv' in params and params['apiv'] == PiwikTracker.API_VERSION, "Wrong or missing API version"
72
-		assert 'idsite' in params and params['idsite'] == site_id, 'Wrong or missing site id'
73
-		assert 'rec' in params and params['rec'] == '1', 'Wrong or missing "rec" parameter'
74
-
75
-		for property_name, parameter_name in PARAMETERS.items():
76
-			if not parameter_name:
77
-				continue
78
-			if property_name in ('page_custom_vars', 'visit_custom_vars'):
79
-				continue
80
-			assert parameter_name in params, "Parameter %s not found in dict" % parameter_name
81
-			assert params[parameter_name] == getattr(tracker, property_name)
82
-
83
-	def test_custom_vars_conversion(self):
84
-		tracker = PiwikTracker('http://localhost', 1)
85
-		tracker.page_custom_vars['foo'] = 'bar'
86
-
87
-		params = tracker._build_parameters()
88
-
89
-		cvars = json.loads(params['cvar'])
90
-		self.assertDictEqual(cvars, {
91
-			'1': ['foo', 'bar']
92
-		})
93
-
94
-	def test_boolean_parameter_conversion(self):
95
-		tracker = PiwikTracker('http://localhost', 1)
96
-		tracker.track_bots = True
97
-
98
-		params = tracker._build_parameters()
99
-		assert params['bots'] == 1, "Boolean conversion from True to 1 failed"
100
-
101
-	def test_datetime_parameter_conversion(self):
102
-		tracker = PiwikTracker('http://localhost', 1)
103
-		tracker.token_auth = 'foobar'
104
-		tracker.client_dt = datetime.datetime(2015, 7, 12, 11, 22, 33, tzinfo=pytz.UTC)
105
-
106
-		params = tracker._build_parameters()
107
-		assert params['cdt'] == '2015-07-12 11:22:33', "DateTime conversion to string failed"
108
-
109
-	def test_skip_auth_params_on_unauth_request(self):
110
-		tracker = PiwikTracker('http://localhost', 1)
111
-
112
-		for p in AUTH_RESTRICTED_PARAMS:
113
-			if p == "token_auth":
114
-				continue
115
-
116
-			setattr(tracker, p, 'test')
117
-
118
-		params = tracker._build_parameters()
119
-		assert not any((k in AUTH_RESTRICTED_PARAMS for k in params.keys())),\
120
-			"Tracking variables with authentication required should be filtered out if token_auth is not set"
121
-
122
-	def test_request_headers_with_spoof(self):
123
-		tracker = PiwikTracker('http://localhost', 1)
37
+        tracker = PiwikTracker('http://localhost', 1, FakeRequest())
38
+        assert tracker.url == "http://foobar.local/test123"
39
+
40
+    def test_from_request_client_ip(self):
41
+        class FakeRequest(object):
42
+            META = { 'HTTP_X_FORWARDED_FOR': '127.1.2.3' }
43
+
44
+        tracker = PiwikTracker('http://localhost', 1, FakeRequest())
45
+        assert tracker.client_ip == '127.1.2.3'
46
+
47
+    def test_from_bad_request(self):
48
+        class FakeRequest(object):
49
+            pass
50
+
51
+        tracker = PiwikTracker('http://localhost', 1, FakeRequest())
52
+        self.assertDictEqual({'apiv': PiwikTracker.API_VERSION, 'idsite': 1, 'rec': '1'}, tracker._build_parameters())
53
+
54
+        class FakeRequest2(object):
55
+            META = "test"
56
+
57
+        tracker = PiwikTracker('http://localhost', 1, FakeRequest2())
58
+        self.assertDictEqual({'apiv': PiwikTracker.API_VERSION, 'idsite': 1, 'rec': '1'}, tracker._build_parameters())
59
+
60
+
61
+    def test_parameters(self):
62
+        site_id = 123
63
+        tracker = PiwikTracker('http://localhost', site_id)
64
+
65
+        for property_name in PARAMETERS.keys():
66
+            if property_name in ('page_custom_vars', 'visit_custom_vars'):
67
+                continue
68
+            setattr(tracker, property_name, 'test')
69
+
70
+        params = tracker._build_parameters()
71
+        assert 'apiv' in params and params['apiv'] == PiwikTracker.API_VERSION, "Wrong or missing API version"
72
+        assert 'idsite' in params and params['idsite'] == site_id, 'Wrong or missing site id'
73
+        assert 'rec' in params and params['rec'] == '1', 'Wrong or missing "rec" parameter'
74
+
75
+        for property_name, parameter_name in PARAMETERS.items():
76
+            if not parameter_name:
77
+                continue
78
+            if property_name in ('page_custom_vars', 'visit_custom_vars'):
79
+                continue
80
+            assert parameter_name in params, "Parameter %s not found in dict" % parameter_name
81
+            assert params[parameter_name] == getattr(tracker, property_name)
82
+
83
+    def test_custom_vars_conversion(self):
84
+        tracker = PiwikTracker('http://localhost', 1)
85
+        tracker.page_custom_vars['foo'] = 'bar'
86
+
87
+        params = tracker._build_parameters()
88
+
89
+        cvars = json.loads(params['cvar'])
90
+        self.assertDictEqual(cvars, {
91
+            '1': ['foo', 'bar']
92
+        })
93
+
94
+    def test_boolean_parameter_conversion(self):
95
+        tracker = PiwikTracker('http://localhost', 1)
96
+        tracker.track_bots = True
97
+
98
+        params = tracker._build_parameters()
99
+        assert params['bots'] == 1, "Boolean conversion from True to 1 failed"
100
+
101
+    def test_datetime_parameter_conversion(self):
102
+        tracker = PiwikTracker('http://localhost', 1)
103
+        tracker.token_auth = 'foobar'
104
+        tracker.client_dt = datetime.datetime(2015, 7, 12, 11, 22, 33, tzinfo=pytz.UTC)
105
+
106
+        params = tracker._build_parameters()
107
+        assert params['cdt'] == '2015-07-12 11:22:33', "DateTime conversion to string failed"
108
+
109
+    def test_skip_auth_params_on_unauth_request(self):
110
+        tracker = PiwikTracker('http://localhost', 1)
111
+
112
+        for p in AUTH_RESTRICTED_PARAMS:
113
+            if p == "token_auth":
114
+                continue
115
+
116
+            setattr(tracker, p, 'test')
117
+
118
+        params = tracker._build_parameters()
119
+        assert not any((k in AUTH_RESTRICTED_PARAMS for k in params.keys())),\
120
+            "Tracking variables with authentication required should be filtered out if token_auth is not set"
121
+
122
+    def test_request_headers_with_spoof(self):
123
+        tracker = PiwikTracker('http://localhost', 1)
124 124
 
125
-		headers = tracker.build_request_headers({})
126
-		self.assertDictEqual(headers, {
127
-			'Accept': '*/*',
128
-			'Accept-Encoding': 'gzip, deflate',
129
-		})
125
+        headers = tracker.build_request_headers({})
126
+        self.assertDictEqual(headers, {
127
+            'Accept': '*/*',
128
+            'Accept-Encoding': 'gzip, deflate',
129
+        })
130 130
 
131
-	def test_request_headers_with_spoof_params(self):
132
-		tracker = PiwikTracker('http://localhost', 1)
131
+    def test_request_headers_with_spoof_params(self):
132
+        tracker = PiwikTracker('http://localhost', 1)
133 133
 
134
-		headers = tracker.build_request_headers({'ua': 'fake user agent'})
135
-		self.assertDictEqual(headers, {
136
-			'Accept': '*/*',
137
-			'Accept-Encoding': 'gzip, deflate',
138
-			'User-Agent': 'fake user agent'
139
-		})
134
+        headers = tracker.build_request_headers({'ua': 'fake user agent'})
135
+        self.assertDictEqual(headers, {
136
+            'Accept': '*/*',
137
+            'Accept-Encoding': 'gzip, deflate',
138
+            'User-Agent': 'fake user agent'
139
+        })
140 140
 
141
-	def test_request_headers_with_spoof_headers(self):
142
-		tracker = PiwikTracker('http://localhost', 1)
143
-		tracker.request_headers['foo'] = 'bar'
141
+    def test_request_headers_with_spoof_headers(self):
142
+        tracker = PiwikTracker('http://localhost', 1)
143
+        tracker.request_headers['foo'] = 'bar'
144 144
 
145
-		headers = tracker.build_request_headers({})
146
-		self.assertDictEqual(headers, {
147
-			'Accept': '*/*',
148
-			'Accept-Encoding': 'gzip, deflate',
149
-			'foo': 'bar'
150
-		})
151
-
152
-	def test_request_headers_without_spoof(self):
153
-		tracker = PiwikTracker('http://localhost', 1)
154
-		tracker.spoof_request = False
155
-
156
-		headers = tracker.build_request_headers({'user_agent': 'fake user agent'})
157
-		self.assertDictEqual(headers, {
158
-			'Accept': '*/*',
159
-			'Accept-Encoding': 'gzip, deflate',
160
-		})
161
-
162
-	def test_tracker_url_builder(self):
163
-		tracker = PiwikTracker('http://localhost', 1)
164
-		assert tracker.php_url == "http://localhost/piwik.php"
165
-
166
-		tracker.piwik_php_file = 'test123.php'
167
-		assert tracker.php_url == "http://localhost/test123.php"
168
-
169
-	def test_js_url_builder(self):
170
-		tracker = PiwikTracker('http://localhost', 1)
171
-		assert tracker.js_url == "http://localhost/piwik.js"
172
-
173
-		tracker.piwik_js_file = 'test123.js'
174
-		assert tracker.js_url == "http://localhost/test123.js"
145
+        headers = tracker.build_request_headers({})
146
+        self.assertDictEqual(headers, {
147
+            'Accept': '*/*',
148
+            'Accept-Encoding': 'gzip, deflate',
149
+            'foo': 'bar'
150
+        })
151
+
152
+    def test_request_headers_without_spoof(self):
153
+        tracker = PiwikTracker('http://localhost', 1)
154
+        tracker.spoof_request = False
155
+
156
+        headers = tracker.build_request_headers({'user_agent': 'fake user agent'})
157
+        self.assertDictEqual(headers, {
158
+            'Accept': '*/*',
159
+            'Accept-Encoding': 'gzip, deflate',
160
+        })
161
+
162
+    def test_tracker_url_builder(self):
163
+        tracker = PiwikTracker('http://localhost', 1)
164
+        assert tracker.php_url == "http://localhost/piwik.php"
165
+
166
+        tracker.piwik_php_file = 'test123.php'
167
+        assert tracker.php_url == "http://localhost/test123.php"
168
+
169
+    def test_js_url_builder(self):
170
+        tracker = PiwikTracker('http://localhost', 1)
171
+        assert tracker.js_url == "http://localhost/piwik.js"
172
+
173
+        tracker.piwik_js_file = 'test123.js'
174
+        assert tracker.js_url == "http://localhost/test123.js"

Loading…
Cancel
Save