`_.
+
+.. image:: https://img.shields.io/pypi/v/django-tracking.svg
+ :target: https://pypi.python.org/pypi/django-tracking/
+
+.. image:: https://img.shields.io/pypi/dm/django-tracking.svg
+ :target: https://pypi.python.org/pypi/django-tracking/
+
+.. image:: https://img.shields.io/github/license/bashu/django-tracking.svg
+ :target: https://pypi.python.org/pypi/django-tracking/
+
+.. image:: https://landscape.io/github/bashu/django-tracking/develop/landscape.svg?style=flat
+ :target: https://landscape.io/github/bashu/django-tracking/develop
Features
-========
+--------
* Tracks the following information about your visitors:
- * Session key
- * IP address
- * User agent
- * Whether or not they are a registered user and logged in
- * Where they came from (http-referer)
- * What page on your site they last visited
- * How many pages on your site they have visited
+ * Session key
+ * IP address
+ * User agent
+ * Whether or not they are a registered user and logged in
+ * Where they came from (HTTP-REFERER)
+ * What page on your site they last visited
+ * How many pages on your site they have visited
* Allows user-agent filtering for visitor tracking
* Automatic clean-up of old visitor records
@@ -24,8 +37,8 @@ Features
* The ability to have a live feed of active users on your website
* Template tags to:
- * display how many active users there are on your site
- * determine how many active users are on the same page within your site
+ * display how many active users there are on your site
+ * determine how many active users are on the same page within your site
* Optional "Active Visitors Map" to see where visitors are in the world
@@ -200,7 +213,7 @@ If you also want to show how many people are looking at the same page::
{% visitors_on_page as same_page %}
{{ same_page }} of {{ visitors }} active user{{ visitors|pluralize }}
- {{ same_page|pluralize:"is,are" }} reading this page
+ {% ifequal same_page 1 %}is{% else %}are{% endifequal %} reading this page
If you don't want particular areas of your site to be tracked, you may define a
@@ -223,5 +236,3 @@ default. If you would like to override that setting, set
``TRACKING_CLEANUP_TIMEOUT`` to however many hours you want in your
``settings.py``.
-Good luck! Please contact me with any questions or concerns you have with the
-project!
diff --git a/setup.py b/setup.py
index e9bd326..b78231c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,23 +1,30 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
+import os
from setuptools import setup, find_packages
-import sys, os
-import tracking
+
+README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read()
+
+# allow setup.py to be run from any path
+os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
+
+from tracking import __version__
setup(
name='django-tracking',
- version=tracking.get_version(),
+ version=__version__,
+ packages=find_packages(exclude=['example']),
+ include_package_data=True,
+ license='MIT License',
description="Basic visitor tracking and blacklisting for Django",
- long_description=open('README.rst', 'r').read(),
+ long_description=README,
keywords='django, tracking, visitors',
+ url='http://github.com/bashu/django-tracking/',
author='Josh VanderLinden',
- author_email='codekoala at gmail dot com',
- url='http://bitbucket.org/codekoala/django-tracking',
- license='MIT',
- package_dir={'tracking': 'tracking'},
- include_package_data=True,
- packages=find_packages(),
+ author_email='codekoala@gmail.com',
+ maintainer='Basil Shubin',
+ maintainer_email='basil.shubin@gmail.com',
+ install_requires=[
+ 'django>=1.4',
+ ],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -30,6 +37,7 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet :: Log Analysis",
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
"Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
"Topic :: Security",
diff --git a/tracking/__init__.py b/tracking/__init__.py
index 0c0d993..b8df223 100644
--- a/tracking/__init__.py
+++ b/tracking/__init__.py
@@ -1,8 +1,4 @@
-import listeners
+VERSION = (0, 4, 1)
-VERSION = (0, 4, 0)
-
-def get_version():
- "Returns the version as a human-format string."
- return '.'.join([str(i) for i in VERSION])
+__version__ = '.'.join([str(n) for n in VERSION])
diff --git a/tracking/admin.py b/tracking/admin.py
index f92994f..31c2906 100644
--- a/tracking/admin.py
+++ b/tracking/admin.py
@@ -1,5 +1,8 @@
from django.contrib import admin
-from tracking.models import BannedIP, UntrackedUserAgent
+from tracking.models import Visitor, VisitorManager, BannedIP, UntrackedUserAgent
+class VisitorAdmin(admin.ModelAdmin):
+ search_fields = ['username']
+admin.site.register(Visitor, VisitorAdmin)
admin.site.register(BannedIP)
-admin.site.register(UntrackedUserAgent)
\ No newline at end of file
+admin.site.register(UntrackedUserAgent)
diff --git a/tracking/listeners.py b/tracking/listeners.py
index ece1367..601b12f 100644
--- a/tracking/listeners.py
+++ b/tracking/listeners.py
@@ -2,12 +2,21 @@
log = logging.getLogger('tracking.listeners')
+importing_exceptions = (ImportError, )
+#Try to have the ImproperlyConfigured error available to catch that error during package installation
+#...looks like newer versions of Django might have broken only receiving an ImportError
+try:
+ from django.core.exceptions import ImproperlyConfigured
+ importing_exceptions = (ImportError, ImproperlyConfigured)
+except ImportError:
+ pass
+
try:
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
from tracking.models import UntrackedUserAgent, BannedIP
-except ImportError:
+except importing_exceptions:
pass
else:
diff --git a/tracking/middleware.py b/tracking/middleware.py
index ab35308..968bca3 100644
--- a/tracking/middleware.py
+++ b/tracking/middleware.py
@@ -1,4 +1,5 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
+from django.utils import timezone
import logging
import re
import traceback
@@ -6,9 +7,10 @@
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache
-from django.core.urlresolvers import reverse, NoReverseMatch
-from django.db.utils import DatabaseError
+from django.urls import reverse, NoReverseMatch
+from django.db.utils import DatabaseError, IntegrityError
from django.http import Http404
+from django.db import transaction
from tracking import utils
from tracking.models import Visitor, UntrackedUserAgent, BannedIP
@@ -16,7 +18,13 @@
title_re = re.compile('(.*?)')
log = logging.getLogger('tracking.middleware')
-class VisitorTrackingMiddleware(object):
+try:
+ from django.utils.deprecation import MiddlewareMixin
+except ImportError: # Django < 1.10
+ MiddlewareMixin = object
+
+
+class VisitorTrackingMiddleware(MiddlewareMixin):
"""
Keeps track of your active users. Anytime a visitor accesses a valid URL,
their unique record will be updated with the page they're on and the last
@@ -55,13 +63,12 @@ def prefixes(self):
return self._prefixes
def process_request(self, request):
- # don't process AJAX requests
- if request.is_ajax(): return
-
# create some useful variables
ip_address = utils.get_ip(request)
- user_agent = unicode(request.META.get('HTTP_USER_AGENT', '')[:255], errors='ignore')
-
+ try:
+ user_agent = unicode(request.META.get('HTTP_USER_AGENT', '')[:255], errors='ignore')
+ except NameError:
+ user_agent = (request.META.get('HTTP_USER_AGENT', '')[:255]).encode().decode("utf-8", "ignore")
# retrieve untracked user agents from cache
ua_key = '_tracking_untracked_uas'
untracked = cache.get(ua_key)
@@ -77,13 +84,9 @@ def process_request(self, request):
log.debug('Not tracking UA "%s" because of keyword: %s' % (user_agent, ua.keyword))
return
- if hasattr(request, 'session') and request.session.session_key:
- # use the current session key if we can
- session_key = request.session.session_key
- else:
- # otherwise just fake a session key
- session_key = '%s:%s' % (ip_address, user_agent)
- session_key = session_key[:40]
+ if not request.session.session_key:
+ request.session.save()
+ session_key = request.session.session_key
# ensure that the request.path does not begin with any of the prefixes
for prefix in self.prefixes:
@@ -93,7 +96,7 @@ def process_request(self, request):
# if we get here, the URL needs to be tracked
# determine what time it is
- now = datetime.now()
+ now=timezone.localtime(timezone.now())
attrs = {
'session_key': session_key,
@@ -118,9 +121,9 @@ def process_request(self, request):
visitor.session_key = session_key
log.debug('Using existing visitor for IP %s / UA %s: %s' % (ip_address, user_agent, visitor.id))
else:
- # it's probably safe to assume that the visitor is brand new
- visitor = Visitor(**attrs)
- log.debug('Created a new visitor: %s' % attrs)
+ visitor, created = Visitor.objects.get_or_create(**attrs)
+ if created:
+ log.debug('Created a new visitor: %s' % attrs)
except:
return
@@ -147,11 +150,15 @@ def process_request(self, request):
visitor.page_views += 1
visitor.last_update = now
try:
+ sid = transaction.savepoint()
visitor.save()
+ transaction.savepoint_commit(sid)
+ except IntegrityError:
+ transaction.savepoint_rollback(sid)
except DatabaseError:
log.error('There was a problem saving visitor information:\n%s\n\n%s' % (traceback.format_exc(), locals()))
-class VisitorCleanUpMiddleware:
+class VisitorCleanUpMiddleware(MiddlewareMixin):
"""Clean up old visitor tracking records in the database"""
def process_request(self, request):
@@ -159,10 +166,10 @@ def process_request(self, request):
if str(timeout).isdigit():
log.debug('Cleaning up visitors older than %s hours' % timeout)
- timeout = datetime.now() - timedelta(hours=int(timeout))
+ timeout = timezone.localtime(timezone.now()) - timedelta(hours=int(timeout))
Visitor.objects.filter(last_update__lte=timeout).delete()
-class BannedIPMiddleware:
+class BannedIPMiddleware(MiddlewareMixin):
"""
Raises an Http404 error for any page request from a banned IP. IP addresses
may be added to the list of banned IPs via the Django admin.
diff --git a/tracking/migrations/0001_initial.py b/tracking/migrations/0001_initial.py
new file mode 100644
index 0000000..e865e8e
--- /dev/null
+++ b/tracking/migrations/0001_initial.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='BannedIP',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('ip_address', models.IPAddressField(help_text='The IP address that should be banned', verbose_name=b'IP Address')),
+ ],
+ options={
+ 'ordering': ('ip_address',),
+ 'verbose_name': 'Banned IP',
+ 'verbose_name_plural': 'Banned IPs',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='UntrackedUserAgent',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('keyword', models.CharField(help_text='Part or all of a user-agent string. For example, "Googlebot" here will be found in "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" and that visitor will not be tracked.', max_length=100, verbose_name='keyword')),
+ ],
+ options={
+ 'ordering': ('keyword',),
+ 'verbose_name': 'Untracked User-Agent',
+ 'verbose_name_plural': 'Untracked User-Agents',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Visitor',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('session_key', models.CharField(max_length=40)),
+ ('ip_address', models.CharField(max_length=20)),
+ ('user_agent', models.CharField(max_length=255)),
+ ('referrer', models.CharField(max_length=255)),
+ ('url', models.CharField(max_length=255)),
+ ('page_views', models.PositiveIntegerField(default=0)),
+ ('session_start', models.DateTimeField()),
+ ('last_update', models.DateTimeField()),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)),
+ ],
+ options={
+ 'ordering': ('-last_update',),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AlterUniqueTogether(
+ name='visitor',
+ unique_together=set([('session_key', 'ip_address')]),
+ ),
+ ]
diff --git a/tracking/migrations/0002_auto_20141023_0732.py b/tracking/migrations/0002_auto_20141023_0732.py
new file mode 100644
index 0000000..825b110
--- /dev/null
+++ b/tracking/migrations/0002_auto_20141023_0732.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import datetime
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tracking', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='visitor',
+ name='last_update',
+ field=models.DateTimeField(default=datetime.datetime(2014, 10, 23, 7, 32, 14, 787217)),
+ ),
+ migrations.AlterField(
+ model_name='visitor',
+ name='session_start',
+ field=models.DateTimeField(default=datetime.datetime(2014, 10, 23, 7, 32, 14, 787119)),
+ ),
+ ]
diff --git a/tracking/migrations/0003_auto_20141023_0834.py b/tracking/migrations/0003_auto_20141023_0834.py
new file mode 100644
index 0000000..4dd6a35
--- /dev/null
+++ b/tracking/migrations/0003_auto_20141023_0834.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tracking', '0002_auto_20141023_0732'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='visitor',
+ name='last_update',
+ field=models.DateTimeField(),
+ ),
+ migrations.AlterField(
+ model_name='visitor',
+ name='session_start',
+ field=models.DateTimeField(),
+ ),
+ ]
diff --git a/tracking/migrations/__init__.py b/tracking/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tracking/models.py b/tracking/models.py
index 1c69735..23949e0 100644
--- a/tracking/models.py
+++ b/tracking/models.py
@@ -1,14 +1,15 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
+from django.utils import timezone
import logging
import traceback
-from django.contrib.gis.utils import HAS_GEOIP
-
-if HAS_GEOIP:
- from django.contrib.gis.utils import GeoIP, GeoIPException
-
-from django.conf import settings
-from django.contrib.auth.models import User
+from django.contrib.gis.geoip2 import GeoIP2 as GeoIP
+try:
+ from django.conf import settings
+ User = settings.AUTH_USER_MODEL
+except AttributeError:
+ from django.conf import settings
+ from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import ugettext, ugettext_lazy as _
from tracking import utils
@@ -27,15 +28,16 @@ def active(self, timeout=None):
if not timeout:
timeout = utils.get_timeout()
- now = datetime.now()
+ now = timezone.now()
cutoff = now - timedelta(minutes=timeout)
- return self.get_query_set().filter(last_update__gte=cutoff)
+ return self.get_queryset().filter(last_update__gte=cutoff)
+
class Visitor(models.Model):
session_key = models.CharField(max_length=40)
ip_address = models.CharField(max_length=20)
- user = models.ForeignKey(User, null=True)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
user_agent = models.CharField(max_length=255)
referrer = models.CharField(max_length=255)
url = models.CharField(max_length=255)
@@ -45,6 +47,11 @@ class Visitor(models.Model):
objects = VisitorManager()
+ def __init__(self, *args, **kwargs):
+ super(Visitor, self).__init__(*args, **kwargs)
+ self.session_start = timezone.now()
+ self.last_update = timezone.now()
+
def _time_on_site(self):
"""
Attempts to determine the amount of time a visitor has spent on the
@@ -78,7 +85,7 @@ def _get_geoip_data(self):
try:
gip = GeoIP(cache=CACHE_TYPE)
self._geoip_data = gip.city(self.ip_address)
- except GeoIPException:
+ except Exception:
# don't even bother...
log.error('Error getting GeoIP data for IP "%s": %s' % (self.ip_address, traceback.format_exc()))
@@ -87,7 +94,7 @@ def _get_geoip_data(self):
geoip_data = property(_get_geoip_data)
def _get_geoip_data_json(self):
- """
+ """
Cleans out any dirty unicode characters to make the geoip data safe for
JSON encoding.
"""
@@ -99,6 +106,12 @@ def _get_geoip_data_json(self):
return clean
geoip_data_json = property(_get_geoip_data_json)
+ def __str__(self):
+ return u'{0} at {1} '.format(
+ self.user.username if self.user else 'AnonymousUser',
+ self.ip_address
+ )
+
class Meta:
ordering = ('-last_update',)
@@ -107,7 +120,7 @@ class Meta:
class UntrackedUserAgent(models.Model):
keyword = models.CharField(_('keyword'), max_length=100, help_text=_('Part or all of a user-agent string. For example, "Googlebot" here will be found in "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" and that visitor will not be tracked.'))
- def __unicode__(self):
+ def __str__(self):
return self.keyword
class Meta:
@@ -116,9 +129,9 @@ class Meta:
verbose_name_plural = _('Untracked User-Agents')
class BannedIP(models.Model):
- ip_address = models.IPAddressField('IP Address', help_text=_('The IP address that should be banned'))
+ ip_address = models.GenericIPAddressField('IP Address', help_text=_('The IP address that should be banned'))
- def __unicode__(self):
+ def __str__(self):
return self.ip_address
class Meta:
diff --git a/tracking/south_migrations/0001_initial.py b/tracking/south_migrations/0001_initial.py
new file mode 100644
index 0000000..0054cee
--- /dev/null
+++ b/tracking/south_migrations/0001_initial.py
@@ -0,0 +1,121 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Visitor'
+ db.create_table('tracking_visitor', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('session_key', self.gf('django.db.models.fields.CharField')(max_length=40)),
+ ('ip_address', self.gf('django.db.models.fields.CharField')(max_length=20)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
+ ('user_agent', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('referrer', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('url', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('page_views', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
+ ('session_start', self.gf('django.db.models.fields.DateTimeField')()),
+ ('last_update', self.gf('django.db.models.fields.DateTimeField')()),
+ ))
+ db.send_create_signal('tracking', ['Visitor'])
+
+ # Adding unique constraint on 'Visitor', fields ['session_key', 'ip_address']
+ db.create_unique('tracking_visitor', ['session_key', 'ip_address'])
+
+ # Adding model 'UntrackedUserAgent'
+ db.create_table('tracking_untrackeduseragent', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('keyword', self.gf('django.db.models.fields.CharField')(max_length=100)),
+ ))
+ db.send_create_signal('tracking', ['UntrackedUserAgent'])
+
+ # Adding model 'BannedIP'
+ db.create_table('tracking_bannedip', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15)),
+ ))
+ db.send_create_signal('tracking', ['BannedIP'])
+
+
+ def backwards(self, orm):
+
+ # Removing unique constraint on 'Visitor', fields ['session_key', 'ip_address']
+ db.delete_unique('tracking_visitor', ['session_key', 'ip_address'])
+
+ # Deleting model 'Visitor'
+ db.delete_table('tracking_visitor')
+
+ # Deleting model 'UntrackedUserAgent'
+ db.delete_table('tracking_untrackeduseragent')
+
+ # Deleting model 'BannedIP'
+ db.delete_table('tracking_bannedip')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'tracking.bannedip': {
+ 'Meta': {'ordering': "('ip_address',)", 'object_name': 'BannedIP'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'})
+ },
+ 'tracking.untrackeduseragent': {
+ 'Meta': {'ordering': "('keyword',)", 'object_name': 'UntrackedUserAgent'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'keyword': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'tracking.visitor': {
+ 'Meta': {'ordering': "('-last_update',)", 'unique_together': "(('session_key', 'ip_address'),)", 'object_name': 'Visitor'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'last_update': ('django.db.models.fields.DateTimeField', [], {}),
+ 'page_views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'referrer': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'session_start': ('django.db.models.fields.DateTimeField', [], {}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'user_agent': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['tracking']
diff --git a/tracking/south_migrations/__init__.py b/tracking/south_migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tracking/urls.py b/tracking/urls.py
index 08fce6d..d5ded8e 100644
--- a/tracking/urls.py
+++ b/tracking/urls.py
@@ -1,4 +1,7 @@
-from django.conf.urls.defaults import *
+try:
+ from django.conf.urls import patterns, url, include
+except ImportError: # Pre-Django 1.4 version
+ from django.conf.urls.defaults import patterns, url, include
from django.conf import settings
from tracking import views
diff --git a/tracking/views.py b/tracking/views.py
index 3e7b2ef..7b40fc5 100644
--- a/tracking/views.py
+++ b/tracking/views.py
@@ -1,4 +1,5 @@
from datetime import datetime
+from django.utils import timezone
import logging
import traceback
@@ -6,7 +7,10 @@
from django.http import Http404, HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext, Context, loader
-from django.utils.simplejson import JSONEncoder
+try:
+ from json import JSONEncoder #Python 2.6
+except ImportError:
+ from django.utils.simplejson import JSONEncoder #Older Django
from django.utils.translation import ungettext
from django.views.decorators.cache import never_cache
from tracking.models import Visitor
@@ -49,7 +53,7 @@ def get_active_users(request):
"""
if request.is_ajax():
active = Visitor.objects.active().reverse()
- now = datetime.now()
+ now = timezone.now()
# we don't put the session key or IP address here for security reasons
try: