├── .gitignore ├── .travis.yml ├── AUTHORS.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── django_user_agents ├── __init__.py ├── middleware.py ├── models.py ├── templatetags │ ├── __init__.py │ └── user_agents.py ├── tests │ ├── __init__.py │ ├── settings.py │ ├── templates │ │ ├── test.html │ │ └── test_filters.html │ ├── tests.py │ ├── urls.py │ └── views.py └── utils.py ├── manage.py ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS* 3 | TAGS 4 | dist 5 | cabal-dev 6 | *.pyc 7 | build/ 8 | py/ua_parser.egg-info/ 9 | *idea/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: xenial 3 | sudo: true 4 | python: 5 | - "2.7" 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | - "3.7" 10 | install: 11 | - pip install user-agents 12 | - pip install tox tox-travis 13 | script: tox 14 | jobs: 15 | include: 16 | - stage: lint 17 | python: "3.6" 18 | script: tox -e flake8 19 | - stage: coverage 20 | python: "3.6" 21 | script: tox -e coverage 22 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Authors: 2 | 3 | * Selwin Ong (https://github.com/selwin) 4 | - Daniel Hawkins (https://github.com/hwkns) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Selwin Ong 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.rst 3 | recursive-include django_user_agents *.py -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django User Agents 2 | ================== 3 | 4 | A django package that allows easy identification of visitor's browser, OS and device information, 5 | including whether the visitor uses a mobile phone, tablet or a touch capable device. Under the hood, 6 | it uses `user-agents `_. 7 | 8 | 9 | Installation 10 | ============ 11 | 12 | 1. Install ``django-user-agents``, you'll have to make sure that `user-agents`_ is installed first:: 13 | 14 | pip install pyyaml ua-parser user-agents 15 | pip install django-user-agents 16 | 17 | 2. Configure ``settings.py``: 18 | 19 | .. code-block:: python 20 | 21 | INSTALLED_APPS = ( 22 | # Other apps... 23 | 'django_user_agents', 24 | ) 25 | 26 | # Cache backend is optional, but recommended to speed up user agent parsing 27 | CACHES = { 28 | 'default': { 29 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 30 | 'LOCATION': '127.0.0.1:11211', 31 | } 32 | } 33 | 34 | # Name of cache backend to cache user agents. If it not specified default 35 | # cache alias will be used. Set to `None` to disable caching. 36 | USER_AGENTS_CACHE = 'default' 37 | 38 | Usage 39 | ===== 40 | 41 | Middleware 42 | ---------- 43 | 44 | Add ``UserAgentMiddleware`` in ``settings.py``: 45 | 46 | .. code-block:: python 47 | 48 | MIDDLEWARE_CLASSES = ( 49 | # other middlewares... 50 | 'django_user_agents.middleware.UserAgentMiddleware', 51 | ) 52 | 53 | A ``user_agent`` attribute will now be added to ``request``, which you can use 54 | in ``views.py``: 55 | 56 | .. code-block:: python 57 | 58 | def my_view(request): 59 | 60 | # Let's assume that the visitor uses an iPhone... 61 | request.user_agent.is_mobile # returns True 62 | request.user_agent.is_tablet # returns False 63 | request.user_agent.is_touch_capable # returns True 64 | request.user_agent.is_pc # returns False 65 | request.user_agent.is_bot # returns False 66 | 67 | # Accessing user agent's browser attributes 68 | request.user_agent.browser # returns Browser(family=u'Mobile Safari', version=(5, 1), version_string='5.1') 69 | request.user_agent.browser.family # returns 'Mobile Safari' 70 | request.user_agent.browser.version # returns (5, 1) 71 | request.user_agent.browser.version_string # returns '5.1' 72 | 73 | # Operating System properties 74 | request.user_agent.os # returns OperatingSystem(family=u'iOS', version=(5, 1), version_string='5.1') 75 | request.user_agent.os.family # returns 'iOS' 76 | request.user_agent.os.version # returns (5, 1) 77 | request.user_agent.os.version_string # returns '5.1' 78 | 79 | # Device properties 80 | request.user_agent.device # returns Device(family='iPhone') 81 | request.user_agent.device.family # returns 'iPhone' 82 | 83 | If you have ``django.core.context_processors.request`` enabled, ``user_agent`` 84 | will also be available in template through ``request``: 85 | 86 | .. code-block:: html+django 87 | 88 | {% if request.user_agent.is_mobile %} 89 | Do stuff here... 90 | {% endif %} 91 | 92 | 93 | View Usage 94 | ---------- 95 | 96 | ``django-user_agents`` comes with ``get_user_agent`` which takes a single 97 | ``request`` argument and returns a ``UserAgent`` instance. Example usage: 98 | 99 | .. code-block:: python 100 | 101 | from django_user_agents.utils import get_user_agent 102 | 103 | def my_view(request): 104 | user_agent = get_user_agent(request) 105 | if user_agent.is_mobile: 106 | # Do stuff here... 107 | elif user_agent.is_tablet: 108 | # Do other stuff... 109 | 110 | 111 | Template Usage 112 | -------------- 113 | 114 | ``django-user_agents`` comes with a few template filters: 115 | 116 | * ``is_mobile`` 117 | * ``is_tablet`` 118 | * ``is_touch_capable`` 119 | * ``is_pc`` 120 | * ``is_bot`` 121 | 122 | You can use all of these like any other django template filters: 123 | 124 | .. code-block:: html+django 125 | 126 | {% load user_agents %} 127 | 128 | {% if request|is_mobile %} 129 | Mobile device stuff... 130 | {% endif %} 131 | 132 | {% if request|is_tablet %} 133 | Tablet stuff... 134 | {% endif %} 135 | 136 | {% if request|is_pc %} 137 | PC stuff... 138 | {% endif %} 139 | 140 | {% if request|is_touch_capable %} 141 | Touch capable device stuff... 142 | {% endif %} 143 | 144 | {% if request|is_bot %} 145 | Bot stuff... 146 | {% endif %} 147 | 148 | 149 | You can find out more about user agent attributes at `here `_. 150 | 151 | 152 | Running Tests 153 | ============= 154 | 155 | .. code-block:: console 156 | 157 | `which django-admin.py` test django_user_agents --settings=django_user_agents.tests.settings --pythonpath=. 158 | 159 | 160 | Changelog 161 | ========= 162 | 163 | 0.4.0 164 | ----- 165 | * Add support for Django 2.0 to 2.2. Thanks @adamchainz and @joehybird! 166 | 167 | 0.3.1 168 | ----- 169 | * Fixed a bug when request have no META attribute 170 | 171 | 0.3.0 172 | ----- 173 | * Python 3, thanks to @hwkns! 174 | 175 | 0.2.2 176 | ----- 177 | * Fixed a bug that causes cache set/read to fail when user agent is longer than 250 characters 178 | 179 | 0.2.1 180 | ----- 181 | * Fixed packaging 182 | 183 | 0.2.0 184 | ----- 185 | * Added template filters 186 | * Added ``get_user_agent`` function in utils.py 187 | 188 | 0.1.1 189 | ----- 190 | * Fixed a ``KeyError`` exception in the case of empty ``HTTP_USER_AGENT`` 191 | 192 | 0.1 193 | --- 194 | * Initial release 195 | -------------------------------------------------------------------------------- /django_user_agents/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 4, 0) 2 | -------------------------------------------------------------------------------- /django_user_agents/middleware.py: -------------------------------------------------------------------------------- 1 | from django.utils.functional import SimpleLazyObject 2 | 3 | from .utils import get_user_agent 4 | 5 | 6 | class UserAgentMiddleware(object): 7 | 8 | def __init__(self, get_response=None): 9 | if get_response is not None: 10 | self.get_response = get_response 11 | 12 | def __call__(self, request): 13 | self.process_request(request) 14 | return self.get_response(request) 15 | 16 | def process_request(self, request): 17 | request.user_agent = SimpleLazyObject(lambda: get_user_agent(request)) 18 | -------------------------------------------------------------------------------- /django_user_agents/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selwin/django-user_agents/b1fa72ee9f9a2ebff74abfb33b7ec6c7fd224554/django_user_agents/models.py -------------------------------------------------------------------------------- /django_user_agents/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selwin/django-user_agents/b1fa72ee9f9a2ebff74abfb33b7ec6c7fd224554/django_user_agents/templatetags/__init__.py -------------------------------------------------------------------------------- /django_user_agents/templatetags/user_agents.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from ..utils import get_and_set_user_agent 4 | 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter() 10 | def is_mobile(request): 11 | return get_and_set_user_agent(request).is_mobile 12 | 13 | 14 | @register.filter() 15 | def is_pc(request): 16 | return get_and_set_user_agent(request).is_pc 17 | 18 | 19 | @register.filter() 20 | def is_tablet(request): 21 | return get_and_set_user_agent(request).is_tablet 22 | 23 | 24 | @register.filter() 25 | def is_bot(request): 26 | return get_and_set_user_agent(request).is_bot 27 | 28 | 29 | @register.filter() 30 | def is_touch_capable(request): 31 | return get_and_set_user_agent(request).is_touch_capable 32 | -------------------------------------------------------------------------------- /django_user_agents/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .tests import MiddlewareTest 2 | 3 | __all__ = ['MiddlewareTest'] 4 | -------------------------------------------------------------------------------- /django_user_agents/tests/settings.py: -------------------------------------------------------------------------------- 1 | import django 2 | from os import path 3 | 4 | 5 | DATABASES = { 6 | 'default': { 7 | 'ENGINE': 'django.db.backends.sqlite3', 8 | 'NAME': ':memory:', 9 | }, 10 | } 11 | 12 | INSTALLED_APPS = ['django_user_agents'] 13 | 14 | if django.VERSION >= (1, 10): 15 | MIDDLEWARE = [ 16 | 'django_user_agents.middleware.UserAgentMiddleware', 17 | ] 18 | else: 19 | MIDDLEWARE_CLASSES = [ 20 | 'django_user_agents.middleware.UserAgentMiddleware', 21 | ] 22 | 23 | ROOT_URLCONF = 'django_user_agents.tests.urls' 24 | 25 | CACHES = { 26 | 'default': { 27 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 28 | 'TIMEOUT': 60, 29 | 'LOCATION': 'default-location', 30 | }, 31 | 'test': { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'TIMEOUT': 60, 34 | 'LOCATION': 'test-location', 35 | }, 36 | } 37 | 38 | if django.VERSION >= (1, 8): 39 | TEMPLATES = [ 40 | { 41 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 42 | 'DIRS': [ 43 | path.join(path.dirname(__file__), "templates"), 44 | ], 45 | }, 46 | ] 47 | else: 48 | TEMPLATE_DIRS = ( 49 | path.join(path.dirname(__file__), "templates"), 50 | ) 51 | 52 | SECRET_KEY = 'foobarbaz' 53 | -------------------------------------------------------------------------------- /django_user_agents/tests/templates/test.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selwin/django-user_agents/b1fa72ee9f9a2ebff74abfb33b7ec6c7fd224554/django_user_agents/tests/templates/test.html -------------------------------------------------------------------------------- /django_user_agents/tests/templates/test_filters.html: -------------------------------------------------------------------------------- 1 | {% load user_agents %} 2 | 3 | {% if request|is_mobile or request|is_pc or request|is_tablet or request|is_bot or request|is_touch_capable %} 4 | Just making sure all the filters can be used without errors 5 | {% endif %} -------------------------------------------------------------------------------- /django_user_agents/tests/tests.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.core.cache import cache 3 | from django.test.client import Client, RequestFactory 4 | from django.test.utils import override_settings 5 | from django.test import SimpleTestCase 6 | 7 | from user_agents.parsers import UserAgent 8 | from django_user_agents import utils 9 | from django_user_agents.utils import get_cache_key, get_user_agent, get_and_set_user_agent 10 | from django_user_agents.templatetags import user_agents 11 | 12 | try: 13 | # The reload() function has been moved from imp to importlib since python 3.4 14 | from importlib import reload as reload_module 15 | except ImportError: 16 | try: 17 | from imp import reload as reload_module 18 | except ImportError: 19 | reload_module = reload # python 2.x 20 | 21 | try: 22 | from django.urls import reverse # django 1.10+ 23 | except ImportError: 24 | from django.core.urlresolvers import reverse 25 | 26 | 27 | iphone_ua_string = 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3' 28 | ipad_ua_string = 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10' 29 | long_ua_string = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.3; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E)' 30 | 31 | 32 | class MiddlewareTest(SimpleTestCase): 33 | 34 | def tearDown(self): 35 | for ua in [iphone_ua_string, ipad_ua_string, long_ua_string]: 36 | cache.delete(get_cache_key(ua)) 37 | 38 | def test_middleware_assigns_user_agent(self): 39 | client = Client(HTTP_USER_AGENT=ipad_ua_string) 40 | response = client.get(reverse('user_agent_test')) 41 | self.assertIsInstance(response.context['user_agent'], UserAgent) 42 | 43 | def test_cache_is_set(self): 44 | request = RequestFactory(HTTP_USER_AGENT=iphone_ua_string).get('') 45 | user_agent = get_user_agent(request) 46 | self.assertIsInstance(user_agent, UserAgent) 47 | self.assertIsInstance(cache.get(get_cache_key(iphone_ua_string)), UserAgent) 48 | 49 | def test_empty_user_agent_does_not_cause_error(self): 50 | request = RequestFactory().get('') 51 | user_agent = get_user_agent(request) 52 | self.assertIsInstance(user_agent, UserAgent) 53 | 54 | def test_get_and_set_user_agent(self): 55 | # Test that get_and_set_user_agent attaches ``user_agent`` to request 56 | request = RequestFactory().get('') 57 | get_and_set_user_agent(request) 58 | self.assertIsInstance(request.user_agent, UserAgent) 59 | 60 | def test_filters_can_be_loaded_in_template(self): 61 | client = Client(HTTP_USER_AGENT=ipad_ua_string) 62 | response = client.get(reverse('user_agent_test_filters')) 63 | self.assertEqual(response.status_code, 200) 64 | self.assertContains( 65 | response, 66 | 'Just making sure all the filters can be used without errors') 67 | 68 | def test_filters(self): 69 | request = RequestFactory(HTTP_USER_AGENT=iphone_ua_string).get('') 70 | self.assertTrue(user_agents.is_mobile(request)) 71 | self.assertTrue(user_agents.is_touch_capable(request)) 72 | self.assertFalse(user_agents.is_tablet(request)) 73 | self.assertFalse(user_agents.is_pc(request)) 74 | self.assertFalse(user_agents.is_bot(request)) 75 | 76 | def test_get_cache_key(self): 77 | self.assertEqual( 78 | get_cache_key(long_ua_string), 79 | 'django_user_agents.c226ec488bae76c60dd68ad58f03d729', 80 | ) 81 | self.assertEqual( 82 | get_cache_key(iphone_ua_string), 83 | 'django_user_agents.00705b9375a0e46e966515fe90f111da', 84 | ) 85 | 86 | @override_settings(USER_AGENTS_CACHE=None) 87 | def test_disabled_cache(self): 88 | reload_module(utils) # re-import with patched settings 89 | 90 | request = RequestFactory(HTTP_USER_AGENT=iphone_ua_string).get('') 91 | user_agent = get_user_agent(request) 92 | self.assertIsInstance(user_agent, UserAgent) 93 | self.assertIsNone(cache.get(get_cache_key(iphone_ua_string))) 94 | 95 | @override_settings(USER_AGENTS_CACHE='test') 96 | def test_custom_cache(self): 97 | reload_module(utils) # re-import with patched settings 98 | 99 | request = RequestFactory(HTTP_USER_AGENT=iphone_ua_string).get('') 100 | user_agent = get_user_agent(request) 101 | self.assertIsInstance(user_agent, UserAgent) 102 | self.assertIsNone(cache.get(get_cache_key(iphone_ua_string))) 103 | self.assertIsInstance(utils.cache.get(get_cache_key(iphone_ua_string)), UserAgent) 104 | 105 | 106 | unicode_ua_string = 'Mozilla/5.0 (Linux; Android 4.4.2; X325 – Locked to Life Wireless Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36' 107 | 108 | class UtilsTest(SimpleTestCase): 109 | 110 | @override_settings(USER_AGENTS_CACHE=None) 111 | def test_unicode_ua_string(self): 112 | reload_module(utils) # re-import with patched settings 113 | 114 | request = RequestFactory(HTTP_USER_AGENT=unicode_ua_string).get('') 115 | 116 | self.assertTrue(user_agents.is_mobile(request)) 117 | -------------------------------------------------------------------------------- /django_user_agents/tests/urls.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.conf.urls import url 3 | from django_user_agents.tests import views 4 | 5 | 6 | if django.VERSION >= (1, 8): 7 | urlpatterns = [ 8 | url(r'^user-agents/', views.test, name='user_agent_test'), 9 | url(r'^filters/', views.test_filters, name='user_agent_test_filters'), 10 | ] 11 | else: 12 | from django.conf.urls import patterns 13 | urlpatterns = patterns( 14 | 'django_user_agents.tests.views', 15 | url(r'^user-agents/', 'test', name='user_agent_test'), 16 | url(r'^filters/', 'test_filters', name='user_agent_test_filters'), 17 | ) 18 | -------------------------------------------------------------------------------- /django_user_agents/tests/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def test(request): 5 | return render(request, "test.html", {'user_agent': request.user_agent}) 6 | 7 | 8 | def test_filters(request): 9 | return render(request, "test_filters.html", {'request': request}) 10 | -------------------------------------------------------------------------------- /django_user_agents/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from hashlib import md5 3 | 4 | from django.conf import settings 5 | from django.core.cache import DEFAULT_CACHE_ALIAS 6 | from user_agents import parse 7 | 8 | 9 | # `get_cache` function has been deprecated since Django 1.7 in favor of `caches`. 10 | try: 11 | from django.core.cache import caches 12 | 13 | def get_cache(backend, **kwargs): 14 | return caches[backend] 15 | except ImportError: 16 | from django.core.cache import get_cache 17 | 18 | 19 | # Small snippet from the `six` library to help with Python 3 compatibility 20 | if sys.version_info[0] == 3: 21 | text_type = str 22 | else: 23 | text_type = unicode 24 | 25 | 26 | USER_AGENTS_CACHE = getattr(settings, 'USER_AGENTS_CACHE', DEFAULT_CACHE_ALIAS) 27 | 28 | if USER_AGENTS_CACHE: 29 | cache = get_cache(USER_AGENTS_CACHE) 30 | else: 31 | cache = None 32 | 33 | 34 | def get_cache_key(ua_string): 35 | # Some user agent strings are longer than 250 characters so we use its MD5 36 | if isinstance(ua_string, text_type): 37 | ua_string = ua_string.encode('utf-8') 38 | return ''.join(['django_user_agents.', md5(ua_string).hexdigest()]) 39 | 40 | 41 | def get_user_agent(request): 42 | # Tries to get UserAgent objects from cache before constructing a UserAgent 43 | # from scratch because parsing regexes.yaml/json (ua-parser) is slow 44 | if not hasattr(request, 'META'): 45 | return '' 46 | 47 | ua_string = request.META.get('HTTP_USER_AGENT', '') 48 | 49 | if not isinstance(ua_string, text_type): 50 | ua_string = ua_string.decode('utf-8', 'ignore') 51 | 52 | if cache: 53 | key = get_cache_key(ua_string) 54 | user_agent = cache.get(key) 55 | if user_agent is None: 56 | user_agent = parse(ua_string) 57 | cache.set(key, user_agent) 58 | else: 59 | user_agent = parse(ua_string) 60 | return user_agent 61 | 62 | 63 | def get_and_set_user_agent(request): 64 | # If request already has ``user_agent``, it will return that, otherwise 65 | # call get_user_agent and attach it to request so it can be reused 66 | if hasattr(request, 'user_agent'): 67 | return request.user_agent 68 | 69 | if not request: 70 | return parse('') 71 | 72 | request.user_agent = get_user_agent(request) 73 | return request.user_agent 74 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_user_agents.tests.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup 3 | 4 | 5 | description = ("A django package that allows easy identification of visitors' " 6 | "browser, operating system and device information (mobile " 7 | "phone, tablet or has touch capabilities).") 8 | 9 | setup( 10 | name='django-user_agents', 11 | version='0.4.0', 12 | author='Selwin Ong', 13 | author_email='selwin.ong@gmail.com', 14 | packages=['django_user_agents'], 15 | url='https://github.com/selwin/django-user_agents', 16 | license='MIT', 17 | description=description, 18 | long_description=open('README.rst').read(), 19 | zip_safe=False, 20 | include_package_data=True, 21 | package_data={'': ['README.rst']}, 22 | install_requires=['django', 'user-agents'], 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Environment :: Web Environment', 26 | 'Framework :: Django', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 2', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | 'Programming Language :: Python :: 3.4', 35 | 'Programming Language :: Python :: 3.5', 36 | 'Programming Language :: Python :: 3.6', 37 | 'Programming Language :: Python :: 3.7', 38 | 'Programming Language :: Python :: Implementation :: CPython', 39 | 'Programming Language :: Python :: Implementation :: PyPy', 40 | 'Topic :: Internet :: WWW/HTTP', 41 | 'Topic :: Software Development :: Libraries :: Python Modules', 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = flake8, 3 | py{27,34,35,36}-django{18,19,110,111}, 4 | py{34}-django{20}, 5 | py{35,36,37}-django{20,21,22} 6 | py{36,37}-djangomaster, 7 | coverage 8 | 9 | install_command = pip install {opts} {packages} 10 | 11 | [testenv] 12 | deps = 13 | -e. 14 | flake8: flake8 15 | django18: Django>=1.8,<1.9 16 | django19: Django>=1.9,<1.10 17 | django110: Django>=1.10,<1.11 18 | django111: Django>=1.11,<2.0 19 | django20: Django>=2.0,<2.1 20 | django21: Django>=2.1,<2.2 21 | django22: Django>=2.2,<3.0 22 | djangomaster: https://github.com/django/django/archive/master.tar.gz 23 | commands = 24 | python -Wd manage.py test {posargs} 25 | 26 | [tox:travis] 27 | 2.7 = py27 28 | 3.4 = py34 29 | 3.5 = py35 30 | 3.6 = py36 31 | 3.7 = py37 32 | 33 | [travis:env] 34 | DJANGO = 35 | 1.8: django18 36 | 1.9: django19 37 | 1.10: django110 38 | 1.11: django111 39 | 2.0: django20 40 | 2.1: django21 41 | 2.2: django22 42 | master: djangomaster 43 | 44 | [testenv:coverage] 45 | basepython = python3.6 46 | deps = 47 | coverage 48 | Django>=2.2,<3.0 49 | commands = 50 | python -Wd {envbindir}/coverage run --source=django_user_agents --omit=*/tests/* manage.py test {posargs} 51 | coverage report -m 52 | 53 | [testenv:py36-djangomaster] 54 | ignore_outcome=true 55 | 56 | [testenv:py37-djangomaster] 57 | ignore_outcome=true 58 | 59 | [testenv:flake8] 60 | basepython = python2.7 61 | deps = 62 | flake8 63 | commands = flake8 django_user_agents --ignore=E501 64 | --------------------------------------------------------------------------------