diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..886028b --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/.landscape.yaml b/.landscape.yaml new file mode 100644 index 0000000..bba38db --- /dev/null +++ b/.landscape.yaml @@ -0,0 +1,4 @@ +ignore-paths: + - south_migrations + - migrations + - example diff --git a/LICENSE b/LICENSE index b862916..5bea623 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,7 @@ -Copyright (c) 2008-2009 Josh VanderLinden +The MIT License (MIT) + +Copyright (c) 2008-2015 Josh VanderLinden +Copyright (c) 2015 Basil Shubin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -7,13 +10,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst index 621fdb5..e550ce5 100644 --- a/README.rst +++ b/README.rst @@ -1,21 +1,34 @@ -``django-tracking`` is a simple attempt at keeping track of visitors to -Django-powered Web sites. It also offers basic blacklisting capabilities. +``django-tracking`` is a simple attempt at keeping track of visitors +to django-powered Web sites. It also offers basic blacklisting +capabilities. -The offial repository for ``django-tracking`` is at -http://bitbucket.org/codekoala/django-tracking. Please file any tickets there. +Authored by `Josh VanderLinden `_, and some great +`contributors `_. + +.. 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: