├── .coveragerc ├── .github └── workflows │ └── python-package.yml ├── AUTHORS ├── CHANGELOG ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile └── source │ ├── conf.py │ ├── customization.rst │ ├── index.rst │ └── quickstart.rst ├── pytest.ini ├── setup.cfg ├── setup.py ├── sitemetrics ├── __init__.py ├── admin.py ├── locale │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── providers.py ├── settings.py ├── templates │ └── sitemetrics │ │ ├── google.html │ │ ├── openstat.html │ │ ├── sitemetrics.tpl │ │ └── yandex.html ├── templatetags │ ├── __init__.py │ └── sitemetrics.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_basic.py │ └── testapp │ │ ├── __init__.py │ │ └── providers.py └── utils.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = sitemetrics/* 3 | omit = sitemetrics/migrations/*, sitemetrics/tests/*, sitemetrics/admin.py, sitemetrics/config.py 4 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] 18 | django-version: [2.0, 2.1, 2.2, 3.0, 3.1, 3.2, 4.0] 19 | 20 | exclude: 21 | 22 | - python-version: 3.7 23 | django-version: 4.0 24 | 25 | - python-version: 3.6 26 | django-version: 4.0 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Python ${{ matrix.python-version }} & Django ${{ matrix.django-version }} 31 | uses: actions/setup-python@v2 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install deps 35 | run: | 36 | python -m pip install pytest coverage coveralls "Django~=${{ matrix.django-version }}.0" 37 | - name: Run tests 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.github_token }} 40 | run: | 41 | coverage run --source=sitemetrics setup.py test 42 | coveralls --service=github 43 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | django-sitemetrics Authors 2 | ========================== 3 | 4 | Created by Igor 'idle sign' Starikov. 5 | 6 | 7 | Contributors 8 | ------------ 9 | 10 | andresriancho 11 | Dmitry Slepichev 12 | Jeans Real 13 | Basil Shubin 14 | Denis Borisov 15 | Sergey Zherevchuk 16 | 17 | 18 | Translators 19 | ----------- 20 | 21 | Russian: Igor Starikov 22 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | django-sitemetrics changelog 2 | ============================ 3 | 4 | 5 | v1.2.0 [2020-05-29] 6 | ------------------- 7 | ! Dropped support for Django<2.0. 8 | ! Dropped support for Python 2 and 3.5. 9 | 10 | 11 | v1.1.0 [2019-12-07] 12 | ------------------- 13 | ! Dropped QA for Django 1.7. 14 | ! Dropped QA for Python 2. 15 | + Add Django 3.0 compatibility. 16 | 17 | 18 | v1.0.0 19 | ------ 20 | ! Dropped QA for Python 3.4. 21 | * No functional changes. Celebrating 1.0.0. 22 | 23 | 24 | v0.7.0 25 | ------ 26 | + Django 2.0 basic compatibility. 27 | * Dropped support for Python<3.4 and Django<1.7. 28 | 29 | 30 | v0.6.1 31 | ------ 32 | * Package distribution fix. 33 | * RemovedInDjango110Warning silenced. 34 | 35 | 36 | v0.6.0 37 | ------ 38 | + Implemented Openstat counter. 39 | + IMPORTANT: Added `SITEMETRICS_ON_DEBUG` setting. Default: False. 40 | * Django 1.10 compatibility ensured. 41 | 42 | 43 | v0.5.2 44 | ---------- 45 | * Django 1.9 compatibility improvements. 46 | 47 | 48 | v0.5.1 49 | ---------- 50 | * Fixed "UnicodeDecodeError: 'ascii' codec..." in setup.py 51 | 52 | 53 | v0.5.0 54 | ------ 55 | * IMPORTANT: Google Analytics JS code is switched to Universal Analytics. 56 | * IMPORTANT: Added migrations. South users should use `migrate sitemetrics --fake` (see #7). 57 | * Fixed cache invalidation issue. 58 | 59 | 60 | v0.4.2 61 | ------ 62 | * Fixed `kcode_data is not a dict` error (see #3). 63 | * Updated Google Analytics counter code (closes #4). 64 | 65 | 66 | v0.4.1 67 | ------ 68 | * Fixed setup.py PIP related issue (see #2). 69 | 70 | 71 | v0.4.0 72 | ------ 73 | + Remastered API. 74 | + Added parameterization support for metrics counters. 75 | + Implemented all known parameters for Yandex Metrika. 76 | * Now uses built-in Django Cache framework. 77 | 78 | 79 | v0.3.0 80 | ------ 81 | + Added non-blocking js for Yandex Metrika. 82 | 83 | 84 | v0.2.0 85 | ------ 86 | + Added pypi support. 87 | + Added keycodes caching. 88 | + Added click map and external links tracking functionality for Yandex Metrika. 89 | 90 | 91 | v0.1.0 92 | ------ 93 | + Basic functionality. -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | django-sitemetrics installation 2 | =============================== 3 | 4 | 5 | Python ``pip`` package is required to install ``django-sitemetrics``. 6 | 7 | 8 | From sources 9 | ------------ 10 | 11 | Use the following command line to install ``django-sitemetrics`` from sources directory (containing setup.py): 12 | 13 | pip install . 14 | 15 | or 16 | 17 | python setup.py install 18 | 19 | 20 | From PyPI 21 | --------- 22 | 23 | Alternatively you can install ``django-sitemetrics`` from PyPI: 24 | 25 | pip install django-sitemetrics 26 | 27 | 28 | Use `-U` flag for upgrade: 29 | 30 | pip install -U django-sitemetrics 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2022, Igor 'idle sign' Starikov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the django-sitemetrics nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include CHANGELOG 4 | include AUTHORS 5 | 6 | include docs/Makefile 7 | recursive-include docs *.rst 8 | recursive-include docs *.py 9 | 10 | recursive-include sitemetrics/locale * 11 | recursive-include sitemetrics/migrations *.py 12 | recursive-include sitemetrics/south_migrations *.py 13 | recursive-include sitemetrics/templates * 14 | recursive-include sitemetrics/templatetags * 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-sitemetrics 2 | ================== 3 | http://github.com/idlesign/django-sitemetrics 4 | 5 | .. image:: https://img.shields.io/pypi/v/django-sitemetrics.svg 6 | :target: https://pypi.python.org/pypi/django-sitemetrics 7 | 8 | .. image:: https://img.shields.io/pypi/l/django-sitemetrics.svg 9 | :target: https://pypi.python.org/pypi/django-sitemetrics 10 | 11 | .. image:: https://img.shields.io/coveralls/idlesign/django-sitemetrics/master.svg 12 | :target: https://coveralls.io/r/idlesign/django-sitemetrics 13 | 14 | 15 | What's that 16 | ----------- 17 | 18 | Reusable application for Django providing easy means to integrate site metrics counters into your sites 19 | 20 | 21 | 22 | Built-in site metrics providers 23 | ------------------------------- 24 | 25 | 1. **Yandex Metrika** - http://metrika.yandex.ru/ 26 | 27 | Provider alias: `yandex` 28 | 29 | 2. **Google Analytics** - http://www.google.com/analytics/ 30 | 31 | Provider alias: `google` 32 | 33 | 3. **Openstat** - https://www.openstat.com/ 34 | 35 | Provider alias: `openstat` 36 | 37 | 38 | How to use 39 | ---------- 40 | 41 | 1. Add the ``sitemetrics`` application to ``INSTALLED_APPS`` in your settings file (usually ``settings.py``) 42 | 2. Add ``{% load sitemetrics %}`` tag to the top of a template (usually base template, e.g. ``_base.html``) 43 | 44 | Then you have two options to add metrics counter code to your page: 45 | 46 | * Use ``four arguments`` sitemetrics tag notation: 47 | 48 | .. code-block:: html+django 49 | 50 | {% sitemetrics by google for "UA-000000-0" %} 51 | 52 | 53 | Here: ``google`` is a metrics provider alias; ``UA-000000-0`` - metrics counter keycode. 54 | 55 | That is how you put Google Analytics counter code (with ``UA-000000-0`` keycode) into page. 56 | 57 | 58 | * Use ``no arguments`` sitemetrics tag notation: 59 | 60 | .. code-block:: html+django 61 | 62 | {% sitemetrics %} 63 | 64 | 65 | That is how you put all counter codes registered and active for the current site. 66 | 67 | Client codes are registered with sites through Django Admin site interface. 68 | 69 | **Admin site** and **Sites** from Django contribs **are required** for this approach to work. 70 | 71 | Use ``./manage.py migrate`` to install sitemetrics tables into your database. 72 | 73 | 74 | 75 | Documentation 76 | ------------- 77 | 78 | http://django-sitemetrics.readthedocs.org/ 79 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-sitemetrics.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-sitemetrics.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-sitemetrics" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-sitemetrics" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-sitemetrics documentation build configuration file, created by 4 | # sphinx-quickstart on Sun May 12 22:39:45 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('../../')) 20 | from sitemetrics import VERSION 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'django-sitemetrics' 45 | copyright = u'2010-2022, Igor \'idle sign\' Starikov' 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = '.'.join(map(str, VERSION)) 53 | # The full version, including alpha/beta/rc tags. 54 | release = '.'.join(map(str, VERSION)) 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | #language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | #today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | #today_fmt = '%B %d, %Y' 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns = [] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all documents. 71 | #default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | #add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | #add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | #show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | #modindex_common_prefix = [] 89 | 90 | 91 | # -- Options for HTML output --------------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. See the documentation for 94 | # a list of builtin themes. 95 | html_theme = 'default' 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | #html_theme_options = {} 101 | 102 | # Add any paths that contain custom themes here, relative to this directory. 103 | #html_theme_path = [] 104 | 105 | # The name for this set of Sphinx documents. If None, it defaults to 106 | # " v documentation". 107 | #html_title = None 108 | 109 | # A shorter title for the navigation bar. Default is the same as html_title. 110 | #html_short_title = None 111 | 112 | # The name of an image file (relative to this directory) to place at the top 113 | # of the sidebar. 114 | #html_logo = None 115 | 116 | # The name of an image file (within the static path) to use as favicon of the 117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 118 | # pixels large. 119 | #html_favicon = None 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | 126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 127 | # using the given strftime format. 128 | #html_last_updated_fmt = '%b %d, %Y' 129 | 130 | # If true, SmartyPants will be used to convert quotes and dashes to 131 | # typographically correct entities. 132 | #html_use_smartypants = True 133 | 134 | # Custom sidebar templates, maps document names to template names. 135 | #html_sidebars = {} 136 | 137 | # Additional templates that should be rendered to pages, maps page names to 138 | # template names. 139 | #html_additional_pages = {} 140 | 141 | # If false, no module index is generated. 142 | #html_domain_indices = True 143 | 144 | # If false, no index is generated. 145 | #html_use_index = True 146 | 147 | # If true, the index is split into individual pages for each letter. 148 | #html_split_index = False 149 | 150 | # If true, links to the reST sources are added to the pages. 151 | #html_show_sourcelink = True 152 | 153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 154 | #html_show_sphinx = True 155 | 156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 157 | #html_show_copyright = True 158 | 159 | # If true, an OpenSearch description file will be output, and all pages will 160 | # contain a tag referring to it. The value of this option must be the 161 | # base URL from which the finished HTML is served. 162 | #html_use_opensearch = '' 163 | 164 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 165 | #html_file_suffix = None 166 | 167 | # Output file base name for HTML help builder. 168 | htmlhelp_basename = 'django-sitemetricsdoc' 169 | 170 | 171 | # -- Options for LaTeX output -------------------------------------------------- 172 | 173 | latex_elements = { 174 | # The paper size ('letterpaper' or 'a4paper'). 175 | #'papersize': 'letterpaper', 176 | 177 | # The font size ('10pt', '11pt' or '12pt'). 178 | #'pointsize': '10pt', 179 | 180 | # Additional stuff for the LaTeX preamble. 181 | #'preamble': '', 182 | } 183 | 184 | # Grouping the document tree into LaTeX files. List of tuples 185 | # (source start file, target name, title, author, documentclass [howto/manual]). 186 | latex_documents = [ 187 | ('index', 'django-sitemetrics.tex', u'django-sitemetrics Documentation', 188 | u'Igor \'idle sign\' Starikov', 'manual'), 189 | ] 190 | 191 | # The name of an image file (relative to this directory) to place at the top of 192 | # the title page. 193 | #latex_logo = None 194 | 195 | # For "manual" documents, if this is true, then toplevel headings are parts, 196 | # not chapters. 197 | #latex_use_parts = False 198 | 199 | # If true, show page references after internal links. 200 | #latex_show_pagerefs = False 201 | 202 | # If true, show URL addresses after external links. 203 | #latex_show_urls = False 204 | 205 | # Documents to append as an appendix to all manuals. 206 | #latex_appendices = [] 207 | 208 | # If false, no module index is generated. 209 | #latex_domain_indices = True 210 | 211 | 212 | # -- Options for manual page output -------------------------------------------- 213 | 214 | # One entry per manual page. List of tuples 215 | # (source start file, name, description, authors, manual section). 216 | man_pages = [ 217 | ('index', 'django-sitemetrics', u'django-sitemetrics Documentation', 218 | [u'Igor \'idle sign\' Starikov'], 1) 219 | ] 220 | 221 | # If true, show URL addresses after external links. 222 | #man_show_urls = False 223 | 224 | 225 | # -- Options for Texinfo output ------------------------------------------------ 226 | 227 | # Grouping the document tree into Texinfo files. List of tuples 228 | # (source start file, target name, title, author, 229 | # dir menu entry, description, category) 230 | texinfo_documents = [ 231 | ('index', 'django-sitemetrics', u'django-sitemetrics Documentation', 232 | u'Igor \'idle sign\' Starikov', 'django-sitemetrics', 'One line description of project.', 233 | 'Miscellaneous'), 234 | ] 235 | 236 | # Documents to append as an appendix to all manuals. 237 | #texinfo_appendices = [] 238 | 239 | # If false, no module index is generated. 240 | #texinfo_domain_indices = True 241 | 242 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 243 | #texinfo_show_urls = 'footnote' 244 | -------------------------------------------------------------------------------- /docs/source/customization.rst: -------------------------------------------------------------------------------- 1 | Customizing sitemetrics 2 | ======================= 3 | 4 | Here we'll talk on how you can customize **sitemetrics** to your needs. 5 | 6 | 7 | Customizing metric counters 8 | --------------------------- 9 | 10 | Some metrics providers allows you to adjust metrics settings, thus changing counters code. 11 | 12 | In **sitemetrics** counters are described in classes, so you can adjust counters 13 | functionality by customizing class attributes values. 14 | 15 | Let' try and customize the built-in `Yandex Metrics` counter: 16 | 17 | 1. First, we define our new customized class somewhere within your project, 18 | (let's say, `my_counters.py` inside `my_app` application): 19 | 20 | .. code-block:: python 21 | 22 | from sitemetrics.providers import Yandex 23 | 24 | # We inherit from the built-in Yandex Metrics counter class. 25 | class MyYandexProvider(Yandex): 26 | 27 | title = 'My Yandex Metrika' # This is to differentiate from parent. 28 | 29 | def __init__(self): 30 | # Parent class has `webvisor` counter param set to True, 31 | # bu we don't want that functionality and disable it. 32 | self.params['webvisor'] = False 33 | 34 | 35 | 2. Second, we introduce our class to Django, putting `SITEMETRICS_PROVIDERS` tuple 36 | into projects' `settings.py`: 37 | 38 | .. code-block:: python 39 | 40 | # Below is a tuple with classes paths to your metrics counters classes. 41 | # We have just one. 42 | SITEMETRICS_PROVIDERS = ('my_app.my_counters.MyYandexProvider',) 43 | 44 | 45 | Implementing new metrics providers 46 | ---------------------------------- 47 | 48 | 1. Implement a class describing counter aspects, somewhere within your project, 49 | (let's say, `my_metrics.py` inside `my_app` application): 50 | 51 | .. code-block:: python 52 | 53 | from sitemetrics.providers import MetricsProvider 54 | 55 | # We inherit from the built-in MetricsProvider counter class. 56 | class MyMetrics(MetricsProvider): 57 | 58 | title = 'My Metrics' # Human-friendly title. 59 | alias = 'my_metrics' # Alias to address counter from templates. 60 | 61 | # And here are counter params, which are passed into counter template. 62 | params = { 63 | 'my_param_1': True, 64 | 'my_param_2': 30, 65 | } 66 | 67 | 2. Create a counter template (**sitemetrics** will search for it in`{your_app}/templates/sitemetrics/{provider_alias}.html`). 68 | 69 | `keycode` variable will be available within this template. `keycode.keycode` will contain counter identifier: 70 | 71 | .. code-block:: html 72 | 73 | keycode: {{ keycode.keycode }} 74 | 75 | {% if keycode.my_param_1 %} 76 | my_param_1 set to True, 77 | my_param_2 is {{ keycode.my_param_2 }} 78 | {% endif %} 79 | 80 | 81 | The code above is of course not a real counter code, yet it can give you an idea on how to create a real one. 82 | 83 | 3. Now if you want to see your counter built into the **sitemetrics** fire an issue or a pull request at https://github.com/idlesign/django-sitemetrics/ or if you want to keep it private use `SITEMETRICS_PROVIDERS` definition approach (described in the previous section) to introduce your class to your Django project. 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | django-sitemetrics documentation 2 | ================================ 3 | 4 | http://github.com/idlesign/django-sitemetrics 5 | 6 | *Reusable application for Django providing easy means to integrate site metrics counters into your sites* 7 | 8 | 9 | Requirements 10 | ------------ 11 | 12 | 1. Python 3.6+ 13 | 2. Django 2.0+ 14 | 3. Django Sites contrib enabled (optional) 15 | 4. Django Admin contrib enabled (optional) 16 | 17 | 18 | Table of Contents 19 | ----------------- 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | quickstart 25 | customization 26 | 27 | 28 | Get involved into django-sitemetrics 29 | ------------------------------------ 30 | 31 | **Submit issues.** If you spotted something weird in application behavior or want to propose a feature you can do that at https://github.com/idlesign/django-sitemetrics/issues 32 | 33 | **Write code.** If you are eager to participate in application development, fork it at https://github.com/idlesign/django-sitemetrics, write your code, whether it should be a bugfix or a feature implementation, and make a pull request right from the forked project page. 34 | 35 | **Translate.** If want to translate the application into your native language use Transifex: https://www.transifex.com/projects/p/django-sitemetrics/. 36 | 37 | **Spread the word.** If you have some tips and tricks or any other words in mind that you think might be of interest for the others — publish them. 38 | 39 | 40 | Also 41 | ---- 42 | 43 | If the application is not what you want for site metrics, you might be interested in considering the other choices — https://www.djangopackages.com/grids/g/analytics/ 44 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | * Add the ``sitemetrics`` application to ``INSTALLED_APPS`` in your settings file (usually ``settings.py``). 5 | * Add ``{% load sitemetrics %}`` tag to the top of a template (usually base template, e.g. ``_base.html``). 6 | * Use ``./manage.py migrate`` to install sitemetrics tables into your database. 7 | 8 | 9 | Quick example 10 | ------------- 11 | 12 | You have two options to add metrics counters code to your pages: 13 | 14 | 15 | 1. Let's add Google Analytics counter without Admin Contrib involvement add 16 | a ``four arguments`` sitemetrics tag into your template:: 17 | 18 | {% sitemetrics by google for "UA-000000-0" %} 19 | 20 | 21 | Here: ``google`` is a metrics provider alias; ``UA-000000-0`` - metrics counter keycode. 22 | 23 | 24 | 2. Now let's use ``no arguments`` sitemetrics tag notation to place metrics counter 25 | code associated through the Admin Contrib with the current site (if any):: 26 | 27 | {% sitemetrics %} 28 | 29 | You're done. 30 | 31 | 32 | .. note:: Since v0.6.0 counter code in DEBUG mode is replaced with 33 | ```` to keep stats clean. 34 | If you wish to see counter code in DEBUG set ``SITEMETRICS_ON_DEBUG`` to ``True`` in project settings file. 35 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --pyargs 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = clean --all sdist bdist_wheel upload 3 | test = pytest 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import sys 5 | 6 | from setuptools import setup, find_packages 7 | 8 | PATH_BASE = os.path.dirname(__file__) 9 | 10 | 11 | def read_file(fpath): 12 | """Reads a file within package directories.""" 13 | with io.open(os.path.join(PATH_BASE, fpath)) as f: 14 | return f.read() 15 | 16 | 17 | def get_version(): 18 | """Returns version number, without module import (which can lead to ImportError 19 | if some dependencies are unavailable before install.""" 20 | contents = read_file(os.path.join('sitemetrics', '__init__.py')) 21 | version = re.search('VERSION = \(([^)]+)\)', contents) 22 | version = version.group(1).replace(', ', '.').strip() 23 | return version 24 | 25 | 26 | setup( 27 | name='django-sitemetrics', 28 | version=get_version(), 29 | url='http://github.com/idlesign/django-sitemetrics', 30 | 31 | description='Reusable application for Django providing easy means to integrate site metrics counters into your sites', 32 | long_description=read_file('README.rst'), 33 | license='BSD 3-Clause License', 34 | 35 | author='Igor `idle sign` Starikov', 36 | author_email='idlesign@yandex.ru', 37 | 38 | packages=find_packages(), 39 | include_package_data=True, 40 | zip_safe=False, 41 | 42 | setup_requires=[] + (['pytest-runner'] if 'test' in sys.argv else []), 43 | 44 | test_suite='tests', 45 | tests_require=[ 46 | 'pytest', 47 | 'pytest-djangoapp>=0.15.1', 48 | ], 49 | 50 | classifiers=[ 51 | 'Development Status :: 5 - Production/Stable', 52 | 'Environment :: Web Environment', 53 | 'Framework :: Django', 54 | 'Intended Audience :: Developers', 55 | 'License :: OSI Approved :: BSD License', 56 | 'Operating System :: OS Independent', 57 | 'Programming Language :: Python', 58 | 'Programming Language :: Python :: 3', 59 | 'Programming Language :: Python :: 3.6', 60 | 'Programming Language :: Python :: 3.7', 61 | 'Programming Language :: Python :: 3.8', 62 | 'Programming Language :: Python :: 3.9', 63 | 'Programming Language :: Python :: 3.10', 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /sitemetrics/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (1, 2, 0) -------------------------------------------------------------------------------- /sitemetrics/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Keycode 4 | 5 | 6 | @admin.register(Keycode) 7 | class KeycodeAdmin(admin.ModelAdmin): 8 | 9 | list_display = ( 10 | 'site', 11 | 'provider', 12 | 'keycode', 13 | 'active', 14 | ) 15 | 16 | list_editable = ( 17 | 'active', 18 | ) 19 | 20 | search_fields = [ 21 | 'keycode', 22 | ] 23 | 24 | list_filter = [ 25 | 'site', 26 | 'provider', 27 | 'active', 28 | ] 29 | 30 | ordering = ['site'] 31 | -------------------------------------------------------------------------------- /sitemetrics/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/django-sitemetrics/272c3bc64fc0e97fd5e09c3c315cd51e058752de/sitemetrics/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /sitemetrics/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SiteMetrics reusable Django application russian translation file. 2 | # Copyright (C) 2010, Igor Starikov aka idle sign 3 | # This file is distributed under the same license as the Django package. 4 | # idle sign , 2010. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: SiteMetrics\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2010-05-12 11:49+0700\n" 11 | "PO-Revision-Date: 2010-05-12 11:50+0700\n" 12 | "Last-Translator: Igor 'idle sign' Starikov \n" 13 | "Language-Team: \n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 18 | "X-Poedit-Language: Russian\n" 19 | "X-Poedit-Country: RUSSIAN FEDERATION\n" 20 | "X-Poedit-SourceCharset: utf-8\n" 21 | 22 | #: models.py:6 23 | msgid "Site" 24 | msgstr "Сайт" 25 | 26 | #: models.py:6 27 | msgid "Site for which metrics keycode is registered." 28 | msgstr "Cайт, на который зарегистрирован ключ." 29 | 30 | #: models.py:7 31 | msgid "Provider" 32 | msgstr "Сервис" 33 | 34 | #: models.py:7 35 | msgid "Metrics service provider name." 36 | msgstr "Сервис, предоставляющий метрику." 37 | 38 | #: models.py:8 39 | #: models.py:11 40 | msgid "Keycode" 41 | msgstr "Ключ" 42 | 43 | #: models.py:8 44 | msgid "Keycode or identifier given by metrics service provider for site(s)." 45 | msgstr "Ключ (идентификатор) для сайта, выданный сервисом метрики." 46 | 47 | #: models.py:9 48 | msgid "Active" 49 | msgstr "Используется" 50 | 51 | #: models.py:9 52 | msgid "Whether this keycode is available to use." 53 | msgstr "Следует ли использовать ключ." 54 | 55 | #: models.py:12 56 | msgid "Keycodes" 57 | msgstr "Ключи" 58 | 59 | -------------------------------------------------------------------------------- /sitemetrics/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('sites', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Keycode', 16 | fields=[ 17 | ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), 18 | ('provider', models.CharField(max_length=50, verbose_name='Provider', choices=[('yandex', 'Yandex Metrika'), ('google', 'Google Analytics'), ('openstat', 'Openstat')], help_text='Metrics service provider name.')), 19 | ('keycode', models.CharField(max_length=80, verbose_name='Keycode', help_text='Keycode or identifier given by metrics service provider for site(s).')), 20 | ('active', models.BooleanField(default=True, verbose_name='Active', help_text='Whether this keycode is available to use.')), 21 | ('site', models.ForeignKey(verbose_name='Site', help_text='Site for which metrics keycode is registered.', to='sites.Site', on_delete=models.CASCADE)), 22 | ], 23 | options={ 24 | 'verbose_name': 'Keycode', 25 | 'verbose_name_plural': 'Keycodes', 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /sitemetrics/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/django-sitemetrics/272c3bc64fc0e97fd5e09c3c315cd51e058752de/sitemetrics/migrations/__init__.py -------------------------------------------------------------------------------- /sitemetrics/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.sites.models import Site 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | from .utils import get_provider_choices 6 | 7 | 8 | class Keycode(models.Model): 9 | 10 | site = models.ForeignKey( 11 | Site, verbose_name=_('Site'), help_text=_('Site for which metrics keycode is registered.'), 12 | on_delete=models.CASCADE) 13 | 14 | provider = models.CharField( 15 | _('Provider'), max_length=50, choices=get_provider_choices(), 16 | help_text=_('Metrics service provider name.')) 17 | 18 | keycode = models.CharField( 19 | _('Keycode'), max_length=80, 20 | help_text=_('Keycode or identifier given by metrics service provider for site(s).')) 21 | 22 | active = models.BooleanField(_('Active'), default=True, help_text=_('Whether this keycode is available to use.')) 23 | 24 | class Meta: 25 | verbose_name = _('Keycode') 26 | verbose_name_plural = _('Keycodes') 27 | 28 | def __str__(self): 29 | return f'Keycode by {self.provider}: {self.keycode}' 30 | -------------------------------------------------------------------------------- /sitemetrics/providers.py: -------------------------------------------------------------------------------- 1 | from typing import List, Type 2 | 3 | from django.conf import settings 4 | from django.utils.module_loading import import_module 5 | from django.utils.translation import gettext_lazy as _ 6 | 7 | 8 | class MetricsProvider: 9 | """Base class for metrics providers.""" 10 | 11 | alias: str = 'generic' 12 | title: str = 'Generic Provider' 13 | 14 | # This can be a dictionary with metrics counter parameters. 15 | # Those parameters will be passed into counter template file - 16 | # templates/sitemetrics/{alias}.html (where `alias` is a provider alias, see above). 17 | params: dict = None 18 | 19 | @classmethod 20 | def get_template_name(cls) -> str: 21 | """Returns js counter code template path.""" 22 | return f'sitemetrics/{cls.alias}.html' 23 | 24 | @classmethod 25 | def get_params(cls) -> dict: 26 | """Returns counter parameters dictionary.""" 27 | return cls.params or {} 28 | 29 | 30 | class Yandex(MetricsProvider): 31 | """Yandex Metrika - http://metrika.yandex.ru/""" 32 | 33 | alias: str = 'yandex' 34 | title: str = _('Yandex Metrika') 35 | 36 | params: dict = { 37 | 'webvisor': True, 38 | 'clickmap': True, 39 | 'track_links': True, 40 | 'accurate_bounce': True, 41 | 'no_index': False, 42 | 'track_hash': True, 43 | 'xml': False, 44 | 'user_params': False, 45 | } 46 | 47 | 48 | class Openstat(MetricsProvider): 49 | """Openstat - https://www.openstat.com/""" 50 | 51 | alias: str = 'openstat' 52 | title: str = _('Openstat') 53 | 54 | params: dict = { 55 | 'image': None, 56 | 'color': None, 57 | 'next': 'openstat', 58 | } 59 | 60 | 61 | class Google(MetricsProvider): 62 | """Google Analytics - http://www.google.com/analytics/""" 63 | 64 | alias: str = 'google' 65 | title: str = _('Google Analytics') 66 | 67 | 68 | def get_custom_providers() -> List[Type[MetricsProvider]]: 69 | """Imports providers classes by paths given in SITEMETRICS_PROVIDERS setting.""" 70 | 71 | providers = getattr(settings, 'SITEMETRICS_PROVIDERS', False) 72 | 73 | if not providers: 74 | return [] 75 | 76 | p_clss = [] 77 | for provider_path in providers: 78 | path_splitted = provider_path.split('.') 79 | mod = import_module('.'.join(path_splitted[:-1])) 80 | p_cls = getattr(mod, path_splitted[-1]) 81 | p_clss.append(p_cls) 82 | 83 | return p_clss 84 | 85 | 86 | BUILTIN_PROVIDERS = ( 87 | Yandex, 88 | Google, 89 | Openstat, 90 | ) 91 | 92 | METRICS_PROVIDERS = get_custom_providers() or BUILTIN_PROVIDERS 93 | -------------------------------------------------------------------------------- /sitemetrics/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | ON_DEBUG: bool = getattr(settings, 'SITEMETRICS_ON_DEBUG', False) 5 | """Whether to put metrics counter code in DEBUG mode.""" 6 | 7 | 8 | CACHE_TIMEOUT: int = 31536000 9 | """sitemetrics keykodes are stored in Django cache for a year (60 * 60 * 24 * 365 = 31536000 sec). 10 | Cache is only invalidated on sitemetrics keycode change. 11 | 12 | """ 13 | -------------------------------------------------------------------------------- /sitemetrics/templates/sitemetrics/google.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /sitemetrics/templates/sitemetrics/openstat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /sitemetrics/templates/sitemetrics/sitemetrics.tpl: -------------------------------------------------------------------------------- 1 | {% for keycode in keycodes %} 2 | {% include keycode.tpl with keycode=keycode %} 3 | {% endfor %} -------------------------------------------------------------------------------- /sitemetrics/templates/sitemetrics/yandex.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | {% if not keycode.xml %}{% endif %} 15 | -------------------------------------------------------------------------------- /sitemetrics/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/django-sitemetrics/272c3bc64fc0e97fd5e09c3c315cd51e058752de/sitemetrics/templatetags/__init__.py -------------------------------------------------------------------------------- /sitemetrics/templatetags/sitemetrics.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.contrib.sites.models import Site 4 | from django.core.cache import cache 5 | from django.db.models import signals 6 | from django.template.base import Parser, Token 7 | 8 | from ..models import Keycode 9 | from ..settings import ON_DEBUG, CACHE_TIMEOUT 10 | from ..utils import get_providers_by_alias 11 | 12 | PROVIDERS_BY_ALIAS = get_providers_by_alias() 13 | 14 | signals.post_save.connect(lambda **kwargs: cache.delete('sitemetrics'), sender=Keycode, weak=False) 15 | signals.post_delete.connect(lambda **kwargs: cache.delete('sitemetrics'), sender=Keycode, weak=False) 16 | 17 | register = template.Library() 18 | 19 | 20 | @register.tag 21 | def sitemetrics(parser: Parser, token: Token): 22 | """Renders sitemetrics counter. 23 | 24 | Two notation types are possible: 25 | 26 | 1. No arguments: 27 | {% sitemetrics %} 28 | Used to render all metrics counters registered and active for the current site. 29 | This requires 'Admin site' and 'Sites' Django contribs. 30 | 31 | 2. Four arguments: 32 | {% sitemetrics by yandex for "138500" %} 33 | Used to render custom metrics counter by definite counter id. 34 | This is a simple template tag with no special requirements. 35 | 36 | """ 37 | if settings.DEBUG and not ON_DEBUG: 38 | return sitemetricsDummyNode() 39 | 40 | tokens = token.split_contents() 41 | tokens_num = len(tokens) 42 | 43 | if tokens_num == 1: 44 | # Notation Type 1 45 | current_site = Site.objects.get_current() 46 | 47 | cached = cache.get('sitemetrics') 48 | if not cached or current_site.id not in cached['keycodes']: 49 | kcodes = current_site.keycode_set.filter(active=True).values() 50 | cache.set('sitemetrics', {'keycodes': {current_site.id: kcodes}}, CACHE_TIMEOUT) 51 | else: 52 | kcodes = cached['keycodes'][current_site.id] 53 | 54 | elif tokens_num == 5: 55 | # Notation Type 2 56 | if tokens[1] == 'by' and tokens[3] == 'for': 57 | kcodes = [{'provider': tokens[2], 'keycode': tokens[4].strip('"')}] 58 | else: 59 | raise template.TemplateSyntaxError( 60 | 'Four arguments `sitemetrics` tag notation should look like ' 61 | '{%% sitemetrics by yandex for "138500" %%}.') 62 | else: 63 | raise template.TemplateSyntaxError( 64 | '`sitemetrics` tag requires four or no arguments. ' 65 | 'E.g. {%% sitemetrics by yandex for "138500" %%} or {%% sitemetrics %%}.') 66 | 67 | _kcodes = [] 68 | for kcode_data in kcodes: 69 | if kcode_data['provider'] in PROVIDERS_BY_ALIAS: 70 | p_cls = PROVIDERS_BY_ALIAS[kcode_data['provider']] 71 | kcode_data['tpl'] = p_cls.get_template_name() 72 | # Get counter parameters. 73 | kcode_data.update(p_cls.get_params()) 74 | _kcodes.append(kcode_data) 75 | 76 | return sitemetricsNode(_kcodes) 77 | 78 | 79 | class sitemetricsDummyNode(template.Node): 80 | """Dummy node used on DEBUG to keep stats clean.""" 81 | 82 | def render(self, context): 83 | return '' 84 | 85 | 86 | class sitemetricsNode(template.Node): 87 | """Renders specified site metrics counter from template.""" 88 | 89 | def __init__(self, keycodes): 90 | self.keycodes = keycodes 91 | self.template = template.loader.get_template('sitemetrics/sitemetrics.tpl') 92 | 93 | def render(self, context): 94 | return self.template.render({'keycodes': self.keycodes}) 95 | -------------------------------------------------------------------------------- /sitemetrics/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/django-sitemetrics/272c3bc64fc0e97fd5e09c3c315cd51e058752de/sitemetrics/tests/__init__.py -------------------------------------------------------------------------------- /sitemetrics/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pytest_djangoapp import configure_djangoapp_plugin 2 | 3 | 4 | pytest_plugins = configure_djangoapp_plugin({ 5 | 'SITEMETRICS_PROVIDERS': [ 6 | 'sitemetrics.tests.testapp.providers.CustomizedProvider', 7 | 'sitemetrics.providers.Google', 8 | ], 9 | 'INSTALLED_APPS': ['django.contrib.sites'], 10 | }) 11 | -------------------------------------------------------------------------------- /sitemetrics/tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from django.contrib.sites.models import Site 4 | from django.template.base import TemplateSyntaxError 5 | 6 | from sitemetrics.models import Keycode 7 | from sitemetrics.providers import MetricsProvider, get_custom_providers 8 | from sitemetrics.utils import get_provider_choices, get_providers_by_alias 9 | from sitemetrics import settings as sm_settings 10 | 11 | 12 | class TestUtils: 13 | 14 | def test_provider_choices(self): 15 | choices = get_provider_choices() 16 | 17 | assert len(choices) > 0 18 | assert isinstance(choices[0], tuple) 19 | assert len(choices[0]) == 2 20 | 21 | def test_provider_by_alias(self): 22 | by_alias = get_providers_by_alias() 23 | 24 | assert len(by_alias) > 0 25 | assert isinstance(by_alias, dict) 26 | 27 | for alias, p_cls in by_alias.items(): 28 | assert issubclass(p_cls, MetricsProvider) 29 | 30 | 31 | class TestProviders: 32 | 33 | def test_customized(self): 34 | from .testapp.providers import CustomizedProvider 35 | 36 | providers = get_custom_providers() 37 | assert len(providers) == 2 38 | assert providers[0].get_params() == CustomizedProvider.params 39 | 40 | 41 | class TestTemplateTagsDummy: 42 | 43 | def test_counter_removed(self, settings, template_render_tag): 44 | settings.DEBUG = True 45 | assert 'counter removed' in template_render_tag('sitemetrics', 'sitemetrics by yandex for "138500"') 46 | 47 | def test_on_debug(self, settings, template_render_tag): 48 | sm_settings.ON_DEBUG = True 49 | assert '138500' in template_render_tag('sitemetrics', 'sitemetrics by yandex for "138500"') 50 | 51 | 52 | class TestTemplateTags: 53 | 54 | def test_simple(self, template_render_tag): 55 | assert '138500' in template_render_tag('sitemetrics', 'sitemetrics by yandex for "138500"') 56 | 57 | with pytest.raises(TemplateSyntaxError): 58 | template_render_tag('sitemetrics', 'sitemetrics some') 59 | 60 | with pytest.raises(TemplateSyntaxError): 61 | template_render_tag('sitemetrics', 'sitemetrics a b c d') 62 | 63 | def test_sites_contrib(self, settings, template_render_tag): 64 | 65 | def render(): 66 | return template_render_tag('sitemetrics', 'sitemetrics') 67 | 68 | site_one = Site.objects.get(pk=1) 69 | site_two = Site(domain='some.com', name='some.com') 70 | site_two.save() 71 | 72 | site_two_code = Keycode(site=site_two, provider='google', keycode='222') 73 | site_two_code.save() 74 | 75 | assert '222' in '%s' % site_two_code 76 | 77 | tpl = '{% load sitemetrics %}{% sitemetrics %}' 78 | 79 | settings.SITE_ID = 1 80 | 81 | assert render() == '' # Test none code for current site 82 | 83 | site_one_code = Keycode(site=site_one, provider='yandex', keycode='111', active=False) 84 | site_one_code.save() 85 | 86 | assert render() == '' # Test inactive 87 | 88 | site_one_code.active = True 89 | site_one_code.save() 90 | 91 | assert '111' in render() # Test active 92 | assert '111' in render() # Test cached hit 93 | 94 | settings.SITE_ID = 2 95 | 96 | assert '222' in render() 97 | -------------------------------------------------------------------------------- /sitemetrics/tests/testapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/django-sitemetrics/272c3bc64fc0e97fd5e09c3c315cd51e058752de/sitemetrics/tests/testapp/__init__.py -------------------------------------------------------------------------------- /sitemetrics/tests/testapp/providers.py: -------------------------------------------------------------------------------- 1 | from sitemetrics.providers import Yandex 2 | 3 | 4 | class CustomizedProvider(Yandex): 5 | 6 | params = {'some_param': 'some_value'} 7 | -------------------------------------------------------------------------------- /sitemetrics/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple, Dict 2 | 3 | from .providers import METRICS_PROVIDERS, MetricsProvider 4 | 5 | 6 | def get_provider_choices() -> List[Tuple[str, str]]: 7 | """Returns a list of currently available metrics providers 8 | suitable for use as model fields choices. 9 | 10 | """ 11 | choices = [] 12 | 13 | for provider in METRICS_PROVIDERS: 14 | choices.append((provider.alias, provider.title)) 15 | 16 | return choices 17 | 18 | 19 | def get_providers_by_alias() -> Dict[str, MetricsProvider]: 20 | """Returns a dictionary with currently available metrics providers 21 | classes indexed by their aliases. 22 | 23 | """ 24 | providers = {} 25 | 26 | for provider in METRICS_PROVIDERS: 27 | providers[provider.alias] = provider 28 | 29 | return providers 30 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{36}-django{20,21,22,30,31,32} 4 | py{37,38,39,310}-django{20,21,22,30,31,32,40} 5 | 6 | install_command = pip install {opts} {packages} 7 | skip_missing_interpreters = True 8 | 9 | [testenv] 10 | commands = python setup.py test 11 | 12 | deps = 13 | django20: Django>=2.0,<2.1 14 | django21: Django>=2.1,<2.2 15 | django22: Django>=2.2,<2.3 16 | django30: Django>=3.0,<3.1 17 | django31: Django>=3.1,<3.2 18 | django32: Django>=3.2,<3.3 19 | django40: Django>=4.0,<4.1 20 | --------------------------------------------------------------------------------