├── tests
├── __init__.py
├── test_app
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ └── tests.py
└── run_tests.py
├── active_users
├── __init__.py
├── api
│ ├── urls.py
│ ├── views.py
│ └── __init__.py
├── middleware.py
├── settings.py
└── keys.py
├── REQUIREMENTS
├── Dockerfile
├── .gitignore
├── MANIFEST.in
├── docker-compose.yml
├── .github
└── workflows
│ ├── publish_to_pypi.yaml
│ └── test_on_push.yaml
├── CHANGELOG.md
├── tox.ini
├── LICENSE
├── setup.py
└── README.rst
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/active_users/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/test_app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/test_app/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/REQUIREMENTS:
--------------------------------------------------------------------------------
1 | django>=1.11
2 | django-redis>=4.9.0
3 | six
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python
2 |
3 | RUN pip install tox
4 | COPY . /src
5 |
6 | VOLUME /src
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | dist/*
3 | build/*
4 | *.egg-info
5 | *.pyc
6 | .tox/
7 | .venv
8 | venv
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft active_users
2 | recursive-exclude active_users *.pyc
3 | include REQUIREMENTS
4 | include README.rst
5 | include LICENSE
6 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | redis:
2 | image: redis
3 | ports:
4 | - "6379:6379"
5 |
6 | app:
7 | build: .
8 | working_dir: /src
9 | restart: always
10 | links:
11 | - redis
12 | environment:
13 | - REDIS_HOST=redis://redis:6379/0
14 | command: tox
15 |
--------------------------------------------------------------------------------
/active_users/api/urls.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from django.urls import re_path
3 |
4 | from . import views
5 |
6 |
7 | _patterns = [
8 | re_path(r'^active-users-info/$', views.active_users_info_view)
9 | ]
10 |
11 | try:
12 | from django.conf.urls import patterns
13 |
14 | urlpatterns = patterns('', *_patterns)
15 | except ImportError:
16 | urlpatterns = _patterns
17 |
--------------------------------------------------------------------------------
/active_users/api/views.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import json
3 |
4 | from django.http import HttpResponse
5 |
6 | from . import get_active_users
7 |
8 |
9 | def active_users_info_view(request):
10 | """ View for info about active users """
11 | data = get_active_users()
12 | # Compatible with Django 1.5, 1.6
13 | return HttpResponse(
14 | json.dumps({'data': data, 'count': len(data)}, ensure_ascii=False),
15 | content_type='application/json',
16 | )
17 |
--------------------------------------------------------------------------------
/.github/workflows/publish_to_pypi.yaml:
--------------------------------------------------------------------------------
1 | name: Publish to PyPI
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | build_publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v1
13 | - uses: actions/setup-python@v1
14 | with:
15 | python-version: 3.7
16 | - name: Install wheel
17 | run: pip install wheel
18 | - name: Build package
19 | run: python setup.py bdist_wheel --universal
20 | - name: Publish to PyPI
21 | uses: pypa/gh-action-pypi-publish@release/v1
22 | with:
23 | password: ${{ secrets.pypi_token }}
24 |
--------------------------------------------------------------------------------
/tests/test_app/views.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from django.conf.urls import include
3 | from django.http import HttpResponse
4 | from django.urls import re_path
5 |
6 |
7 | def active_view(request):
8 | return HttpResponse('
Test view has responded')
9 |
10 |
11 | def excluded_view(request):
12 | return HttpResponse('Excluded view has responded')
13 |
14 |
15 | _patterns = [
16 | re_path(r'^active-users/', include('active_users.api.urls')),
17 | re_path(r'^excluded/$', excluded_view),
18 | re_path(r'^$', active_view),
19 | ]
20 |
21 | try:
22 | from django.conf.urls import patterns
23 | urlpatterns = patterns('', *_patterns)
24 | except ImportError:
25 | urlpatterns = _patterns
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## v0.4.1
4 |
5 | - Pycharm autoformat;
6 | - `ActiveUsersSessionMiddleware` using `pk` as a universal attribute instead of `id`;
7 |
8 | ## v0.4.0
9 |
10 | - Added support for Python 3.9, 3.10, 3.11
11 | - Added support for Django 4
12 | - Dropped support for Python 2.7, 3.4, 3.5
13 | - Dropped support for Django 1.x
14 |
15 | ## v0.3.0
16 |
17 | - Added support for Python 3.8
18 | - Added support for Django 3.0
19 | - Dropped support for Python 3.3
20 | - Dropped support for Django 1.5 and 1.6
21 | - Fixed documentation
22 |
23 | ## v0.2.2
24 |
25 | - Added support for latest version of django-redis package
26 |
27 | ## v0.2.1
28 |
29 | - Fixed bug with missing api folder in a wheel package
30 |
31 | ## v0.2
32 |
33 | - Added support for Django 2.0
34 |
35 | ## v0.1
36 |
37 | Initial release
38 |
--------------------------------------------------------------------------------
/active_users/api/__init__.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | try:
3 | from django.utils.encoding import force_str
4 | except ImportError:
5 | # FIXME: Django 2 compatibility
6 | from django.utils.encoding import force_text as force_str
7 |
8 | from django_redis import get_redis_connection
9 |
10 | from active_users.settings import active_users_settings as settings
11 |
12 |
13 | def get_active_users_keys(pattern='au:*'):
14 | """ Get all keys from redis """
15 | return [force_str(key) for key in get_redis_connection().keys(pattern)]
16 |
17 |
18 | def get_active_users_count():
19 | """ Get redis keys count """
20 | return len(get_active_users_keys())
21 |
22 |
23 | def get_active_users():
24 | """ Get list of all active users """
25 | return [
26 | settings.KEY_CLASS.key_to_dict(key) for key in get_active_users_keys()
27 | ]
28 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{36}-django{20,21,22,30,31,32}
3 | py{37}-django{20,21,22,30,31,32}
4 | py{38}-django{20,21,22,30,31,32,40,41,42}
5 | py{39}-django{20,21,22,30,31,32,40,41,42}
6 | py{310}-django{32,40,41,42}
7 | py{311}-django{41,42}
8 | skip_missing_interpreters = True
9 |
10 | [testenv]
11 | passenv = *
12 |
13 | basepython =
14 | py35: python3.5
15 | py36: python3.6
16 | py37: python3.7
17 | py38: python3.8
18 | py39: python3.9
19 | py310: python3.10
20 | py311: python3.11
21 |
22 | commands = python tests/run_tests.py
23 |
24 | deps =
25 | django20: Django>=2.0,<2.1
26 | django21: Django>=2.1,<2.2
27 | django22: Django>=2.2,<2.3
28 | django30: Django>=3.0,<3.1
29 | django31: Django>=3.1,<3.2
30 | django32: Django>=3.2,<3.3
31 | django40: Django>=4.0,<4.1
32 | django41: Django>=4.1,<4.2
33 | django42: Django>=4.2,<4.3
34 |
--------------------------------------------------------------------------------
/active_users/middleware.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | import re
3 |
4 | from django.utils.functional import cached_property
5 | from django_redis import get_redis_connection
6 |
7 | from active_users.settings import active_users_settings as settings
8 |
9 | try:
10 | from django.utils.deprecation import MiddlewareMixin
11 | except ImportError:
12 | parent_class = object
13 | else:
14 | parent_class = MiddlewareMixin
15 |
16 |
17 | class ActiveUsersSessionMiddleware(parent_class):
18 |
19 | @cached_property
20 | def redis_client(self):
21 | return get_redis_connection()
22 |
23 | def process_request(self, request):
24 | if settings.EXCLUDE_URL_PATTERNS:
25 | if any(re.search(pat, request.path)
26 | for pat in settings.EXCLUDE_URL_PATTERNS):
27 | return
28 | if request.user.pk is not None:
29 | key = settings.KEY_CLASS.create_from_request(request)
30 | self.redis_client.setex(
31 | name=key.dump(),
32 | value=0,
33 | time=settings.KEY_EXPIRE
34 | )
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2018 Nikita Ekaterinchuk
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='django-active-users',
5 | version='0.4.1',
6 | packages=['active_users', 'active_users.api'],
7 | url='https://github.com/n-elloco/django-active-users',
8 | license='MIT',
9 | author='Nikita Ekaterinchuk',
10 | author_email='en.elloco@gmail.com',
11 | description='Monitoring of active users in Django using Redis',
12 | long_description=open('README.rst').read(),
13 | install_requires=open('REQUIREMENTS').read(),
14 | classifiers=[
15 | 'Development Status :: 5 - Production/Stable',
16 | 'Environment :: Web Environment',
17 | 'Intended Audience :: Developers',
18 | 'License :: OSI Approved :: MIT License',
19 | 'Operating System :: OS Independent',
20 | 'Programming Language :: Python',
21 | 'Programming Language :: Python :: 3.6',
22 | 'Programming Language :: Python :: 3.7',
23 | 'Programming Language :: Python :: 3.8',
24 | 'Programming Language :: Python :: 3.9',
25 | 'Programming Language :: Python :: 3.10',
26 | 'Programming Language :: Python :: 3.11',
27 | ],
28 | )
29 |
--------------------------------------------------------------------------------
/active_users/settings.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from django.conf import settings
3 | from django.test.signals import setting_changed
4 |
5 | try:
6 | from django.utils.module_loading import import_string
7 | except ImportError:
8 | from django.utils.module_loading import import_by_path as import_string
9 |
10 | from active_users.keys import AbstractActiveUserEntry
11 |
12 | PREFIX = 'ACTIVE_USERS'
13 |
14 | DEFAULTS = {
15 | 'KEY_EXPIRE': 20,
16 | 'KEY_CLASS': 'active_users.keys.ActiveUserEntry',
17 | 'EXCLUDE_URL_PATTERNS': []
18 | }
19 |
20 |
21 | class ActiveUsersSettings(object):
22 |
23 | def __init__(self):
24 | for key, default in DEFAULTS.items():
25 | value = getattr(settings, '{0}_{1}'.format(PREFIX, key), default)
26 | self.set_setting(key, value)
27 |
28 | assert issubclass(self.KEY_CLASS, AbstractActiveUserEntry)
29 |
30 | def set_setting(self, key, value):
31 | setattr(
32 | self, key, import_string(value) if key == 'KEY_CLASS' else value)
33 |
34 |
35 | active_users_settings = ActiveUsersSettings()
36 |
37 |
38 | def reload_settings(*args, **kwargs):
39 | if kwargs['setting'].startswith(PREFIX):
40 | key = kwargs['setting'].replace(PREFIX + '_', '')
41 | if key in DEFAULTS:
42 | active_users_settings.set_setting(
43 | key,
44 | kwargs['value'] or DEFAULTS[key]
45 | )
46 |
47 |
48 | setting_changed.connect(reload_settings)
49 |
--------------------------------------------------------------------------------
/.github/workflows/test_on_push.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | tags:
6 | - '!refs/tags/*'
7 | branches:
8 | - '*'
9 |
10 | jobs:
11 | run_tests:
12 | runs-on: [ubuntu-20.04]
13 |
14 | services:
15 | redis:
16 | image: redis
17 | ports:
18 | - 6379:6379
19 |
20 | strategy:
21 | max-parallel: 4
22 | matrix:
23 | python-version: [3.7, 3.8, 3.9]
24 | django-version: ["2.0", 2.1, 2.2, "3.0", 3.1, 3.2, "4.0", 4.1, 4.2]
25 | exclude:
26 | - python-version: 3.7
27 | django-version: "4.0"
28 | - python-version: 3.7
29 | django-version: 4.1
30 | - python-version: 3.7
31 | django-version: 4.2
32 |
33 | include:
34 | - python-version: "3.10"
35 | django-version: 3.2
36 | - python-version: "3.10"
37 | django-version: "4.0"
38 | - python-version: "3.10"
39 | django-version: 4.1
40 | - python-version: "3.10"
41 | django-version: 4.2
42 |
43 | - python-version: 3.11
44 | django-version: 4.1
45 | - python-version: 3.11
46 | django-version: 4.2
47 | steps:
48 | - uses: actions/checkout@v1
49 | - uses: actions/setup-python@v1
50 | with:
51 | python-version: ${{ matrix.python-version }}
52 | - name: Install requirements
53 | run: |
54 | pip install -r REQUIREMENTS
55 | pip install -U Django~=${{ matrix.django-version }}
56 | - name: Tests
57 | run: python tests/run_tests.py
58 |
--------------------------------------------------------------------------------
/tests/run_tests.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import os
3 | import sys
4 |
5 | from django.conf import settings
6 |
7 |
8 | MIDDLEWARE = (
9 | 'django.middleware.common.CommonMiddleware',
10 | 'django.contrib.sessions.middleware.SessionMiddleware',
11 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
12 | 'active_users.middleware.ActiveUsersSessionMiddleware',
13 | )
14 |
15 |
16 | def main():
17 | current_dir = os.path.dirname(os.path.abspath(__file__))
18 | sys.path.insert(0, os.path.join(current_dir, '..'))
19 |
20 | conf_kwargs = dict(
21 | CACHES={
22 | 'default': {
23 | 'BACKEND': 'django_redis.cache.RedisCache',
24 | 'LOCATION': os.environ.get(
25 | 'REDIS_HOST', 'redis://127.0.0.1:6379/0'),
26 | }
27 | },
28 | DATABASES={
29 | 'default': {
30 | 'ENGINE': 'django.db.backends.sqlite3',
31 | 'NAME': 'test.db',
32 | 'TEST_NAME': 'test.db'
33 | }
34 | },
35 | SITE_ID=1,
36 | MIDDLEWARE=MIDDLEWARE,
37 | INSTALLED_APPS=(
38 | 'django.contrib.auth',
39 | 'django.contrib.contenttypes',
40 | 'django.contrib.sessions',
41 | 'django.contrib.sites',
42 | 'test_app'
43 | ),
44 | ROOT_URLCONF='test_app.views',
45 | SECRET_KEY='secret',
46 | )
47 |
48 | settings.configure(**conf_kwargs)
49 |
50 | try:
51 | # For django>=1.7
52 | from django import setup
53 | except ImportError:
54 | pass
55 | else:
56 | setup()
57 |
58 | from django.test.utils import get_runner
59 | runner = get_runner(settings)()
60 | return runner.run_tests(('test_app',))
61 |
62 |
63 | if __name__ == '__main__':
64 | failures = main()
65 | sys.exit(failures)
66 |
--------------------------------------------------------------------------------
/tests/test_app/tests.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import json
3 | import time
4 |
5 | from django.contrib.auth.models import User
6 | from django.test import TestCase, Client
7 | from django.test.utils import override_settings
8 | from django_redis import get_redis_connection
9 |
10 | from active_users.api import get_active_users_count, get_active_users
11 | from active_users.settings import active_users_settings
12 |
13 |
14 | class ActiveUsersTest(TestCase):
15 |
16 | username = 'test'
17 | user_password = 'secret'
18 |
19 | @classmethod
20 | def setUpClass(cls):
21 | super(ActiveUsersTest, cls).setUpClass()
22 | cls.redis_client = get_redis_connection()
23 |
24 | cls.key_class = active_users_settings.KEY_CLASS
25 |
26 | cls.client = Client()
27 | cls.user = User.objects.create_user(
28 | username=cls.username,
29 | email='test@test.com',
30 | password=cls.user_password
31 | )
32 |
33 | def setUp(self):
34 | self.client.login(username=self.username, password=self.user_password)
35 |
36 | def tearDown(self):
37 | self.redis_client.flushdb()
38 |
39 | def test_view(self):
40 | self.url_request('/')
41 | self.assertEqual(get_active_users_count(), 1)
42 |
43 | users = get_active_users()
44 | user = users.pop()
45 |
46 | self.assertEqual(user['user_id'], str(self.user.id))
47 | self.assertEqual(user['username'], self.username)
48 |
49 | @override_settings(ACTIVE_USERS_KEY_EXPIRE=3)
50 | def test_expire_setting(self):
51 | self.url_request('/')
52 | time.sleep(3)
53 | self.assertEqual(get_active_users_count(), 0)
54 |
55 | @override_settings(ACTIVE_USERS_EXCLUDE_URL_PATTERNS=['/excluded'])
56 | def test_excluded_view(self):
57 | self.url_request('/excluded/')
58 | self.assertEqual(get_active_users_count(), 0)
59 |
60 | def test_api_view(self):
61 | self.url_request('/')
62 | response = self.url_request('/active-users/active-users-info/')
63 |
64 | data = json.loads(response.content.decode())
65 | self.assertIn('data', data)
66 | self.assertIn('count', data)
67 |
68 | def url_request(self, url):
69 | response = self.client.get(url)
70 | self.assertEqual(response.status_code, 200)
71 |
72 | return response
73 |
--------------------------------------------------------------------------------
/active_users/keys.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from six import text_type
3 |
4 |
5 | class AbstractActiveUserEntry(object):
6 | """ Abstract class for redis key instance"""
7 |
8 | fields = tuple()
9 | key_prefix = 'au'
10 |
11 | def __init__(self):
12 | for field in self.fields:
13 | setattr(self, field, u'')
14 |
15 | @classmethod
16 | def create_from_request(cls, request):
17 | """ Fill object from request """
18 | raise NotImplementedError
19 |
20 | @classmethod
21 | def create_from_key(cls, key):
22 | """ Load object from redis key """
23 | key_items = key.split(u':')
24 | instance = cls()
25 | # First item is prefix
26 | for i, field in enumerate(cls.fields, start=1):
27 | setattr(instance, field, key_items.get(i, u''))
28 | return instance
29 |
30 | def dump(self):
31 | """ Dump object to redis key """
32 | key_items = [self.key_prefix]
33 | for field in self.fields:
34 | key_items.append(getattr(self, field, u''))
35 | return u':'.join(map(text_type, key_items))
36 |
37 | @classmethod
38 | def key_to_dict(cls, key):
39 | """ Convert redis key to dict directly """
40 | key_items = key.split(u':')[1:] # First item is prefix
41 | return dict(zip(cls.fields, key_items))
42 |
43 | def to_dict(self):
44 | """ Serialize object to dict """
45 | return {field: getattr(self, field) for field in self.fields}
46 |
47 |
48 | class ActiveUserEntry(AbstractActiveUserEntry):
49 | """ Default key class"""
50 | fields = ('user_id', 'session_id', 'ip', 'username')
51 |
52 | @classmethod
53 | def create_from_request(cls, request):
54 | instance = cls()
55 | if request.user.id is not None:
56 | instance.user_id = request.user.id
57 | instance.username = request.user.username
58 | instance.ip = cls.get_client_ip(request)
59 | if request.session:
60 | instance.session_id = request.session.session_key
61 | return instance
62 |
63 | @staticmethod
64 | def get_client_ip(request):
65 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
66 | if x_forwarded_for:
67 | ip = x_forwarded_for.split(',')[0]
68 | else:
69 | ip = request.META.get('REMOTE_ADDR', '127.0.0.1')
70 | return ip
71 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | MONITORING OF DJANGO ACTIVE USERS
2 | =================================
3 |
4 | .. image:: https://img.shields.io/pypi/v/django-active-users.svg
5 | :target: https://pypi.python.org/pypi/django-active-users
6 |
7 | .. image:: https://github.com/n-elloco/django-active-users/workflows/Tests/badge.svg?branch=master
8 | :target: https://github.com/n-elloco/django-active-users/actions?query=workflow%3A%22Tests%22
9 |
10 |
11 | *Online monitoring of active users in Django using Redis*
12 |
13 | Collecting information about active users in the Django application
14 | for last specified time interval, using Redis cache.
15 |
16 |
17 | Requirements
18 | ------------
19 |
20 | - Python: 3.6+
21 | - Django: 2.0+
22 | - Django-redis:
23 | 4.9.0
24 |
25 | 4.11.0 (for Django 3.0+)
26 |
27 |
28 | Install
29 | -------
30 |
31 | .. code-block:: bash
32 |
33 | pip install django-active-users
34 |
35 |
36 | Setup
37 | -----
38 |
39 | Your django application should have a Redis cache setting.
40 | See more in ``django-redis`` `official documentation `_.
41 |
42 | Add ``active_users.middleware.ActiveUsersSessionMiddleware`` to your project's
43 | ``MIDDLEWARE`` after the ``AuthenticationMiddleware``.
44 |
45 | .. code-block:: python
46 |
47 | MIDDLEWARE = (
48 | ...
49 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
50 | 'active_users.middleware.ActiveUsersSessionMiddleware',
51 | ...
52 | )
53 |
54 |
55 | Settings
56 | --------
57 |
58 | ``ACTIVE_USERS_KEY_EXPIRE`` - Time of key expire (interval after the last request during which the visitor is considered as active) in seconds. Default is 20.
59 |
60 | ``ACTIVE_USERS_EXCLUDE_URL_PATTERNS`` - List of regular expressions for excluded URLs. If they are matched, the visitor (and pageview) key will not be create.
61 |
62 | ``ACTIVE_USERS_KEY_CLASS`` - Class of visitor key entry. It should be a descendant of ``active_users.keys.AbstractActiveUserEntry``.
63 | Default ``active_users.keys.ActiveUserEntry``. See more in `Custom dimensions in the keys`_
64 |
65 |
66 | API
67 | ---
68 |
69 | You can use API for getting information about active users.
70 | You can also call methods from ``active_users.api`` module directly.
71 |
72 | **Methods:**
73 |
74 | - ``get_active_users_count`` - returns count of active users (users, who visited site for the last time interval,
75 | which is set by option ``ACTIVE_USERS_KEY_EXPIRE``)
76 |
77 | - ``get_active_users`` - returns list of dictionaries with information about active users;
78 | keys of dictionaries are set from field ``fields`` of entry class, which is set by option ``ACTIVE_USERS_KEY_CLASS``
79 |
80 |
81 | Also, you can include special view in your Django application, adding the URL pattern to your ``urls.py`` file
82 |
83 |
84 | .. code-block:: python
85 |
86 | urlpatterns = [
87 | ...
88 | url(r'^active-users/', include('active_users.api.urls')),
89 | ...
90 | ]
91 |
92 |
93 | Custom dimensions in the keys
94 | -----------------------------
95 |
96 | By default, 4 dimensions are saved in the keys (``user_id``, ``session_id``, ``IP``, ``username``).
97 | This is provided by class ``ActiveUserEntry``, which inherits from abstract class ``AbstractActiveUserEntry``.
98 | You can use your dimensions, defined in your own class, which should be a descendant of class ``AbstractActiveUserEntry`` and
99 | you need to define the logic of using these dimensions in the class method ``create_from_request``.
100 |
101 | For example, we need to save information about service, which makes request, and this information we can take
102 | from request header. Also, we want use all dimensions from class ``ActiveUserEntry``.
103 |
104 |
105 | .. code-block:: python
106 |
107 | from active_users.keys import ActiveUserEntry
108 |
109 | class OurActiveUserEntry(ActiveUserEntry):
110 |
111 | fields = ('service_id',) + ActiveUserEntry.fields
112 |
113 | @classmethod
114 | def create_from_request(cls, request):
115 | instance = super(OurActiveUserEntry, cls).create_from_request(request)
116 | instance.service_id = request.META.get('HTTP_SERVICE_ID', u'')
117 | return instance
118 |
119 |
120 | At the end, we need to specify option ``ACTIVE_USERS_KEY_CLASS`` in the ``settings.py``.
121 |
122 |
123 | .. code-block:: python
124 |
125 | ACTIVE_USERS_KEY_CLASS = 'my_app.keys.OurActiveUserEntry'
126 |
--------------------------------------------------------------------------------