django-viewtracker 1.2
Object view tracker for Django
License: BSD

django-viewtracker
==================

Copyright 2010 - 2013 `Caramel <http://www.caramel.com.au/>`_ (formerly
uAnywhere). Released under the 3-clause BSD license.

``django-viewtracker`` is a simple Django application to allow you to do
view tracking on objects. The primary purpose of the module is so that a
user can determine what objects they have already seen.

It contains some additional "smarts" for dealing with updates as well -
so if you haven't read it since an update, then you haven't read it at
all.

The module is generic, and can be applied to any model you like.

Unlike some other view tracking modules for Django, this one stores it's
data in a ``Model`` (instead of the session), and thus only works for
users who are registered. Unregistered users won't have any view
tracking at all, and all objects will appear as read for them. The
advantage of doing this is that view tracking works across all
devices/browsers that someone uses.

Installation
------------

Install the module with the normal ``sudo ./setup.py install``.

Add ``viewtracker`` to your ``INSTALLED_APPS`` in the ``settings.py`` of
your application.

You'll need to ``./manage.py syncdb`` so that viewtracker's modules can
be installed.

Now you're ready to use it.

Design
------

The system has three levels of view tracking. It will check each of
these in order to determine if an object has been viewed.

The first level is "all view" tracking. Your application may allow a
user to mark all objects as read. This is checked first against the
object's modification time. When you mark everything as viewed, all
other records of views for the user are deleted to save space.

The second level is "model view" tracking. Your application may allow a
user to mark all objects of a particular type as read. This the second
thing to be checked against the object's modification time. When you
mark all of a model as viewed, all other records of individual item
views are deleted to save space.

The third level is "instance view" tracking. A user views an object, you
would use this to mark it as having being viewed.

This means to determine if an object has been viewed, it goes through
three levels of checks, each increasing in complexity. The viewtracker
tables may become quite large.

Using
-----

First, track views of your object in your view (normally object detail
view):

::

    from viewtracker.models import ViewTracker
    from .models import Car


    class CarDetailView(View):
        def get(self, request, object_id):
            # Get the instance of the object to view
            # (Though in reality you'd probably use the Django Generic CBVs instead
            # of this.)
            car = get_object_or_404(Car, id=object_id)

            # Create an instance of the tracker for this user
            # If they are anonymous, then everything is viewed.
            tracking = ViewTracker(request.user)

            # Mark the instance viewed by this user
            tracking.mark_instance_viewed(car)

            # Render the template to display the object.
            return render(request, 'my_app/car_detail.html', dict(car=car, user=user))

You can then look up if an object has some changed data. You could show
this in a list:

::

    from django_globals import globals as django_globals


    class Car(models.Model):
        # ... other fields ..

        # Use the name "last_updated" as the app will automatically to use this
        # field name
        last_updated = models.DateTimeField(auto_now=True)

        def has_viewed(self):
            tracker = ViewTracker(django_globals.request.user)
            
            # Here we've manually supplied the updated field name.
            return tracking.has_viewed(self, updated_field='last_updated')

Then in the template, you would have something like:

::

    {% for car in car_list %}
        {# ... #}
        <td>
            <a href="{{ car.get_absolute_url }}">Listing #{{ car.id }}</a>
            {% if not car.has_viewed %}
                <img src="{{ STATIC_URL }}img/new.png" alt="New!" />
            {% endif %}
        </td>
        {# ... #}
    {% endfor %}

There are also some built-in views, which you can use for marking all
objects read, or all instances of a model as read:

::

    url(
        r'^cars/mark_all_read/$',
        login_required()(viewtracker.views.mark_model_as_viewed),
        dict(model=Car),
        name='mark_all_cars_as_viewed'
    ),

Then call it in your template with something like:

::

    <form method="post" action="{% url 'mark_all_cars_as_viewed' %}">
        {% csrf_token %}
        <input type="submit" value="Mark all cars as viewed" />
    </form>

Note that by default all objects will be "unviewed", so when you first
roll the application out, you may wish to set everyone as having viewed
all objects up to a particular point in time.