added hitcount for staff
This commit is contained in:
parent
38a25437b6
commit
d773cba908
@ -1,5 +1,7 @@
|
||||
{% extends 'base.html' %}{% block content %}
|
||||
{% load hitcount_tags %}
|
||||
<div class="post">
|
||||
|
||||
{% if post.published_date %}
|
||||
<div class="date">
|
||||
{{ post.published_date }}
|
||||
@ -17,6 +19,7 @@
|
||||
<h1>{{ post.title }}</h1>
|
||||
<p>{{ post.text|linebreaksbr }}</p>
|
||||
<p>
|
||||
Tags:
|
||||
{% for tag in post.tags.all %}
|
||||
<a href="{% url 'blog_search_list_view' %}">{{ tag.name }}, </a>
|
||||
{% endfor %}
|
||||
@ -24,5 +27,10 @@
|
||||
<p>
|
||||
Autor: {{ post.author }}
|
||||
</p>
|
||||
{% if user.is_staff %}
|
||||
<p>
|
||||
{% get_hit_count for post %} Benutzer haben diesen Post bereits gelesen!
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -16,6 +16,8 @@ from django.contrib import messages
|
||||
from post_office.models import EmailTemplate
|
||||
from post_office import mail
|
||||
|
||||
from hitcount.models import HitCount
|
||||
from hitcount.views import HitCountMixin
|
||||
|
||||
|
||||
import logging
|
||||
@ -61,7 +63,9 @@ def post_list(request, slug=None):
|
||||
@login_required
|
||||
def post_detail(request, pk):
|
||||
post = get_object_or_404(Post, pk=pk)
|
||||
return render(request, 'post_detail.html', {'post': post})
|
||||
hit_count = HitCount.objects.get_for_object(post)
|
||||
hit_count_response = HitCountMixin.hit_count(request, hit_count)
|
||||
return render(request, 'post_detail.html', locals())
|
||||
|
||||
|
||||
@login_required
|
||||
@ -182,17 +186,10 @@ def blog_search_list_view(request):
|
||||
def tag_cloud(request):
|
||||
return render(request, 'tag_cloud.html', {})
|
||||
|
||||
|
||||
EmailTemplate.objects.create(
|
||||
name='weekly-update',
|
||||
subject='Hi' + User.objects.get(username=request.user) + '!',
|
||||
content='How are you feeling today?' + Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date'),
|
||||
html_content='Hi <strong>{{ name }}</strong>, how are you feeling today?',
|
||||
)
|
||||
|
||||
mail.send(
|
||||
'kleinhenz.e@gmail.com', # List of email addresses also accepted
|
||||
'esther.kleinhenz@web.de',
|
||||
template='weekly-update',
|
||||
context={'name': 'alice'},
|
||||
subject='My email',
|
||||
message='Hi there!',
|
||||
html_message='Hi <strong>there</strong>!',
|
||||
)
|
||||
|
@ -48,6 +48,7 @@ INSTALLED_APPS = [
|
||||
'taggit_templatetags2',
|
||||
'kombu.transport.django',
|
||||
'post_office',
|
||||
'hitcount',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -0,0 +1,38 @@
|
||||
django-hitcount
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/thornomad/django-hitcount.svg?branch=master
|
||||
:target: https://travis-ci.org/thornomad/django-hitcount
|
||||
.. image:: https://coveralls.io/repos/thornomad/django-hitcount/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/thornomad/django-hitcount?branch=master
|
||||
.. image:: https://badge.fury.io/py/django-hitcount.svg
|
||||
:target: http://badge.fury.io/py/django-hitcount
|
||||
.. image:: https://requires.io/github/thornomad/django-hitcount/requirements.svg?branch=develop
|
||||
:target: https://requires.io/github/thornomad/django-hitcount/requirements/?branch=develop
|
||||
:alt: Requirements Status
|
||||
|
||||
Basic app that allows you to track the number of hits/views for a particular object.
|
||||
|
||||
Documentation:
|
||||
--------------
|
||||
|
||||
`<http://django-hitcount.rtfd.org>`_
|
||||
|
||||
Source Code:
|
||||
------------
|
||||
|
||||
`<https://github.com/thornomad/django-hitcount>`_
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Use the GitHub `issue tracker`_ for django-hitcount to submit bugs, issues, and feature requests.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
`<http://django-hitcount.readthedocs.org/en/latest/changelog.html>`_
|
||||
|
||||
.. _issue tracker: https://github.com/thornomad/django-hitcount/issues
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,61 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: django-hitcount
|
||||
Version: 1.3.0
|
||||
Summary: Hit counting application for Django.
|
||||
Home-page: http://github.com/thornomad/django-hitcount
|
||||
Author: Damon Timm
|
||||
Author-email: damontimm@gmail.com
|
||||
License: BSD
|
||||
Description-Content-Type: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Environment :: Plugins
|
||||
Classifier: Framework :: Django
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
|
||||
django-hitcount
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/thornomad/django-hitcount.svg?branch=master
|
||||
:target: https://travis-ci.org/thornomad/django-hitcount
|
||||
.. image:: https://coveralls.io/repos/thornomad/django-hitcount/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/thornomad/django-hitcount?branch=master
|
||||
.. image:: https://badge.fury.io/py/django-hitcount.svg
|
||||
:target: http://badge.fury.io/py/django-hitcount
|
||||
.. image:: https://requires.io/github/thornomad/django-hitcount/requirements.svg?branch=develop
|
||||
:target: https://requires.io/github/thornomad/django-hitcount/requirements/?branch=develop
|
||||
:alt: Requirements Status
|
||||
|
||||
Basic app that allows you to track the number of hits/views for a particular object.
|
||||
|
||||
Documentation:
|
||||
--------------
|
||||
|
||||
`<http://django-hitcount.rtfd.org>`_
|
||||
|
||||
Source Code:
|
||||
------------
|
||||
|
||||
`<https://github.com/thornomad/django-hitcount>`_
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Use the GitHub `issue tracker`_ for django-hitcount to submit bugs, issues, and feature requests.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
`<http://django-hitcount.readthedocs.org/en/latest/changelog.html>`_
|
||||
|
||||
.. _issue tracker: https://github.com/thornomad/django-hitcount/issues
|
||||
|
||||
|
@ -0,0 +1,73 @@
|
||||
django_hitcount-1.3.0.dist-info/DESCRIPTION.rst,sha256=qYNOokuJW5VaV917eTzyWlAD7GUFNfdb1Yuy39nxZnc,1179
|
||||
django_hitcount-1.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
django_hitcount-1.3.0.dist-info/METADATA,sha256=ZfeWwRDcS89ApGq5n4Z0RkxfmHpNW7KbPI_qOVaVYGc,2029
|
||||
django_hitcount-1.3.0.dist-info/RECORD,,
|
||||
django_hitcount-1.3.0.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
|
||||
django_hitcount-1.3.0.dist-info/metadata.json,sha256=nX26lMZ4TA-8fsrnzAHYW8HW6tJeIZiRkj-e1_qXHbM,950
|
||||
django_hitcount-1.3.0.dist-info/top_level.txt,sha256=ciQsjrRx8k2LuKG2acan8SxCWV8ktTSJOo2s0eCnxOs,9
|
||||
hitcount/__init__.py,sha256=l4S7L5a9RF9ngsnCumXsQgnhLbUDrLv75rVI1f37428,110
|
||||
hitcount/__pycache__/__init__.cpython-36.pyc,,
|
||||
hitcount/__pycache__/admin.cpython-36.pyc,,
|
||||
hitcount/__pycache__/managers.cpython-36.pyc,,
|
||||
hitcount/__pycache__/models.cpython-36.pyc,,
|
||||
hitcount/__pycache__/signals.cpython-36.pyc,,
|
||||
hitcount/__pycache__/urls.cpython-36.pyc,,
|
||||
hitcount/__pycache__/utils.cpython-36.pyc,,
|
||||
hitcount/__pycache__/views.cpython-36.pyc,,
|
||||
hitcount/admin.py,sha256=YXinJSVGFwqDuVk0NSwu_PR4JNAkPpZrVvPqvp8Vir8,3517
|
||||
hitcount/locale/ru/LC_MESSAGES/django.mo,sha256=4P3YSrZbBqm5vKywHU8OP1Yqi_uOr4_0k3ms9hNob3s,922
|
||||
hitcount/locale/ru/LC_MESSAGES/django.po,sha256=fwKKdutWtaNjshRBuBHgQZS1jfPJ0wLlZPiIQ6l4BoU,1232
|
||||
hitcount/management/.DS_Store,sha256=eJxOsIPnGpj5wPicqQp2GJotnEXWORC08JwewB8Z7zo,6148
|
||||
hitcount/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
hitcount/management/__init__.pyc,sha256=ci0qG4gwAh6InRZr7SC0Atn4Ncy4s69lxGTJ24xWL7Q,140
|
||||
hitcount/management/__pycache__/__init__.cpython-34.pyc,sha256=xt5VdojOPPpEYLlZgrcNAUHpN-0LC2CVx6rqLxd14eo,153
|
||||
hitcount/management/__pycache__/__init__.cpython-35.pyc,sha256=cJguE581coUsB_2F2LpCEr9P_UXhXydL56y-bKKXNhA,153
|
||||
hitcount/management/__pycache__/__init__.cpython-36.pyc,sha256=yDorYsjqscWXfGjrO4Z0PBqSzyLzAaHhmc8CN4is9kM,153
|
||||
hitcount/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
hitcount/management/commands/__init__.pyc,sha256=bAJKmaMoifVBrisekW5ftymMLNpJugWfEkJ2668SIi0,166
|
||||
hitcount/management/commands/__pycache__/__init__.cpython-34.pyc,sha256=mV0vGG856aEl-nuEL04INYA7iaRteb86yEjsilvwpSA,162
|
||||
hitcount/management/commands/__pycache__/__init__.cpython-35.pyc,sha256=r4FrwdSELdLgB6nORVThqfr3ZyLXy0azSF5vbW_z6y4,162
|
||||
hitcount/management/commands/__pycache__/__init__.cpython-36.pyc,sha256=M3hF87z_jcSVV9RHgAjVu977KGlnbtn8kS5o-SFySJ8,162
|
||||
hitcount/management/commands/__pycache__/hitcount_cleanup.cpython-34-PYTEST.pyc,sha256=CVoedgQixzMUBq0ITb2Y977_rQhfyUuRn8ROB10UwTA,1717
|
||||
hitcount/management/commands/__pycache__/hitcount_cleanup.cpython-34.pyc,sha256=hdrg5KZxFSk9OQ2XU97TIhoInj_IK5Fo_D4FLVM9Ct0,1600
|
||||
hitcount/management/commands/__pycache__/hitcount_cleanup.cpython-35.pyc,sha256=ijfeqfAu_bTSjocAkhZiDU53zeVqtPa9U2A2-nGYXh0,1601
|
||||
hitcount/management/commands/__pycache__/hitcount_cleanup.cpython-36.pyc,sha256=9DALGofFvZXxuReAdWHq-sCFmMrlvFfhGjECRnXl89k,1531
|
||||
hitcount/management/commands/hitcount_cleanup.py,sha256=W8ubDD60KW-gDRDCqTiErhLB-VmvEIY69JlvZA60sCs,1004
|
||||
hitcount/management/commands/hitcount_cleanup.pyc,sha256=kJ-jE3silVaQ5ThKJXq_8IH9cCXHV7wtvNgWEpFJd88,1458
|
||||
hitcount/managers.py,sha256=sO7pNHNm3vbHmev7abW1ZHH8LG4swVXCWJhWRCD40S8,1609
|
||||
hitcount/migrations/.DS_Store,sha256=knz5wSK6xyaXxFPjuov5ofdbkY45OUT1i5qHp4Hpb_0,6148
|
||||
hitcount/migrations/0001_initial.py,sha256=Wi4WAnkCqoPZNC9K_mIQCBn7CRYUbWxZsCFe1oyl6ds,3808
|
||||
hitcount/migrations/0001_initial.pyc,sha256=zUnc6TwI8t3lCBpYSS7PAqdGAEvIA4YlXu8X3tJIq8I,2886
|
||||
hitcount/migrations/0002_index_ip_and_session.py,sha256=SD4cHYoj-kcOjfR8bOpzq6ZGGn75IAK-KFHNHHsUqNQ,778
|
||||
hitcount/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
hitcount/migrations/__init__.pyc,sha256=dYHNxrJCOUO4t3iyb8-1dHX51UIqEaEs5F1i6Xqk2eo,157
|
||||
hitcount/migrations/__pycache__/0001_initial.cpython-34-PYTEST.pyc,sha256=pJYZWfZn294xkgGXKzynGghNM89LiAxsrBitOb8n_zU,2610
|
||||
hitcount/migrations/__pycache__/0001_initial.cpython-34.pyc,sha256=pod5I9AJU1n_uiCo3af_g1Cr98ymvXWdQwdhee1WxzE,2504
|
||||
hitcount/migrations/__pycache__/0001_initial.cpython-35.pyc,sha256=zz6vGM0RLLKWeE84UPIE4yGbZqdomjjl3ndZ8usRaH8,2525
|
||||
hitcount/migrations/__pycache__/0001_initial.cpython-36.pyc,sha256=iyHZAAMqXfJ_bkROAoxUVmrWHMSdGSZ97qfc52OxE9I,2195
|
||||
hitcount/migrations/__pycache__/0002_auto_20151002_0107.cpython-34.pyc,sha256=d9InaWAdfgzbNAmmehZuyjaUxewfXtmAiVJmzB15EE4,700
|
||||
hitcount/migrations/__pycache__/0002_index_ip_and_session.cpython-35.pyc,sha256=GEFNCYxH7l2IJbq6aaFfpdXnmfZzymOLau-smEIP38E,888
|
||||
hitcount/migrations/__pycache__/0002_index_ip_and_session.cpython-36.pyc,sha256=XJ0N1dFeDVmwR8CpSCAMOXtbQgGbSDypaV_cblq_9QM,787
|
||||
hitcount/migrations/__pycache__/__init__.cpython-34.pyc,sha256=QnlwKINXZCxvEcxGbo6uz9hyeOP7_Sf22MWffdlz7zQ,153
|
||||
hitcount/migrations/__pycache__/__init__.cpython-35.pyc,sha256=w-PkM7qV8_k4woZyC6AFVVHH0138x3RI5x73MgJ5QgY,153
|
||||
hitcount/migrations/__pycache__/__init__.cpython-36.pyc,sha256=YZT2H038AusB1l8488oQ_iNqxUjPqBGetXivid8wTAA,153
|
||||
hitcount/models.py,sha256=TCVZH8n2_7wV6rhJU0w4pj9xEGkqjl-Ho1bRW9feU1g,6452
|
||||
hitcount/signals.py,sha256=dSAeXBqrfS3alXUyKaT_fTnLKwgpEYo8kSDrUitDYQY,160
|
||||
hitcount/static/.DS_Store,sha256=rF6vsjXKJhA9tkZOK4dp1e3yiHhIpGTqeG_rErx9KOw,6148
|
||||
hitcount/static/hitcount/hitcount-jquery.js,sha256=iuT92zgd8cZf3JPIOceugKRw7w8BVvMAb_o3896gnHY,1849
|
||||
hitcount/static/hitcount/jquery.postcsrf.js,sha256=M7VJf_EO8XXUd9U07gUDndMcOL0a1ZL4DZdukdpTX6c,1696
|
||||
hitcount/templatetags/.DS_Store,sha256=_bLXwPKHomfjzOqZXKLcl0Df7OVkpZwGLJEuHu46UVg,6148
|
||||
hitcount/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
hitcount/templatetags/__init__.pyc,sha256=lAeXmAsrGHVMuUKhPyjK_8583A_YDfDD-ameEtV0v4k,142
|
||||
hitcount/templatetags/__pycache__/__init__.cpython-34.pyc,sha256=L-M9CXK_mJU_claLKAoKdLoyjl_9eykxvX29vZ1xjtU,138
|
||||
hitcount/templatetags/__pycache__/__init__.cpython-35.pyc,sha256=RsqgNxtyYvbxiFcoZHhuMFBSRLcwud4a7D2xpk7n4rI,155
|
||||
hitcount/templatetags/__pycache__/__init__.cpython-36.pyc,sha256=DhVijvXN2ym14mkK5ZquL_9FdSfKZA2Yr4z-30aT4_E,155
|
||||
hitcount/templatetags/__pycache__/hitcount_tags.cpython-34-PYTEST.pyc,sha256=cGwj0P3A_4jOigoaG0BQPAYA8JXTOsaWG-jTzJHuJSg,10515
|
||||
hitcount/templatetags/__pycache__/hitcount_tags.cpython-34.pyc,sha256=9PnFQoTv1PCwBBcqFaEOarhI62WNke2rFiEsYsUjCHo,10394
|
||||
hitcount/templatetags/__pycache__/hitcount_tags.cpython-35.pyc,sha256=P3AcxUap_1zp_7I4plKRtth07vyiB3k5gQuIRSc4FW0,10448
|
||||
hitcount/templatetags/__pycache__/hitcount_tags.cpython-36.pyc,sha256=JaI5iyvmXClChPx2pYnxXxvP-QoOB9ri6UekyZvOiqI,9869
|
||||
hitcount/templatetags/hitcount_tags.py,sha256=ZhkDhw8ZNvXeRQ8oTb17C0-MTvxJISQ_k3jPup3zLlI,10322
|
||||
hitcount/templatetags/hitcount_tags.pyc,sha256=lr1N24UpaEcgrOcrmU3RSL9sly4r7nkX8zlpmNY-nn4,9619
|
||||
hitcount/urls.py,sha256=xBMnJP03XR9bEwhRB6PQmqhSV4rkoiqLs6wG6MIWhJA,255
|
||||
hitcount/utils.py,sha256=6J-uHYKc7Orweo2ESVSoZqxy7Kvoc2Cr5ZjFTFWzGQs,1610
|
||||
hitcount/views.py,sha256=cG9Tc7xUQT_PHKHi--wM9zQqdne9HmEl9Tq8uBNuAvE,7288
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.30.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 4 - Beta", "Environment :: Plugins", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "damontimm@gmail.com", "name": "Damon Timm", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/thornomad/django-hitcount"}}}, "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "django-hitcount", "summary": "Hit counting application for Django.", "version": "1.3.0"}
|
@ -0,0 +1 @@
|
||||
hitcount
|
@ -0,0 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
VERSION = (1, 3, 0)
|
||||
|
||||
__version__ = '.'.join(str(i) for i in VERSION)
|
102
thesisenv/lib/python3.6/site-packages/hitcount/admin.py
Normal file
102
thesisenv/lib/python3.6/site-packages/hitcount/admin.py
Normal file
@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib import admin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import Hit, HitCount, BlacklistIP, BlacklistUserAgent
|
||||
|
||||
|
||||
class HitAdmin(admin.ModelAdmin):
|
||||
list_display = ('created', 'user', 'ip', 'user_agent', 'hitcount')
|
||||
search_fields = ('ip', 'user_agent')
|
||||
date_hierarchy = 'created'
|
||||
actions = ['blacklist_ips',
|
||||
'blacklist_user_agents',
|
||||
'blacklist_delete_ips',
|
||||
'blacklist_delete_user_agents',
|
||||
'delete_queryset',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HitAdmin, self).__init__(*args, **kwargs)
|
||||
self.list_display_links = None
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super(HitAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
|
||||
def blacklist_ips(self, request, queryset):
|
||||
for obj in queryset:
|
||||
ip, created = BlacklistIP.objects.get_or_create(ip=obj.ip)
|
||||
if created:
|
||||
ip.save()
|
||||
msg = _("Successfully blacklisted %d IPs") % queryset.count()
|
||||
self.message_user(request, msg)
|
||||
blacklist_ips.short_description = _("Blacklist selected IP addresses")
|
||||
|
||||
def blacklist_user_agents(self, request, queryset):
|
||||
for obj in queryset:
|
||||
ua, created = BlacklistUserAgent.objects.get_or_create(
|
||||
user_agent=obj.user_agent)
|
||||
if created:
|
||||
ua.save()
|
||||
msg = _("Successfully blacklisted %d User Agents") % queryset.count()
|
||||
self.message_user(request, msg)
|
||||
blacklist_user_agents.short_description = _("Blacklist selected User Agents")
|
||||
|
||||
def blacklist_delete_ips(self, request, queryset):
|
||||
self.blacklist_ips(request, queryset)
|
||||
self.delete_queryset(request, queryset)
|
||||
blacklist_delete_ips.short_description = _(
|
||||
"Delete selected hits and blacklist related IP addresses")
|
||||
|
||||
def blacklist_delete_user_agents(self, request, queryset):
|
||||
self.blacklist_user_agents(request, queryset)
|
||||
self.delete_queryset(request, queryset)
|
||||
blacklist_delete_user_agents.short_description = _(
|
||||
"Delete selected hits and blacklist related User Agents")
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
if not self.has_delete_permission(request):
|
||||
raise PermissionDenied
|
||||
else:
|
||||
if queryset.count() == 1:
|
||||
msg = "1 hit was"
|
||||
else:
|
||||
msg = "%s hits were" % queryset.count()
|
||||
|
||||
for obj in queryset.iterator():
|
||||
obj.delete() # calling it this way to get custom delete() method
|
||||
|
||||
self.message_user(request, "%s successfully deleted." % msg)
|
||||
delete_queryset.short_description = _("Delete selected hits")
|
||||
|
||||
admin.site.register(Hit, HitAdmin)
|
||||
|
||||
|
||||
class HitCountAdmin(admin.ModelAdmin):
|
||||
list_display = ('content_object', 'hits', 'modified')
|
||||
fields = ('hits',)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
admin.site.register(HitCount, HitCountAdmin)
|
||||
|
||||
|
||||
class BlacklistIPAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(BlacklistIP, BlacklistIPAdmin)
|
||||
|
||||
|
||||
class BlacklistUserAgentAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(BlacklistUserAgent, BlacklistUserAgentAdmin)
|
Binary file not shown.
@ -0,0 +1,51 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-07-08 12:52+0700\n"
|
||||
"PO-Revision-Date: 2013-07-08 13:31+0700\n"
|
||||
"Last-Translator: Basil Shubin <basil.shubin@gmail.com>\n"
|
||||
"Language-Team: <RU@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"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"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
|
||||
#: models.py:87
|
||||
msgid "hit count"
|
||||
msgstr "счетчик просмотров"
|
||||
|
||||
#: models.py:88
|
||||
msgid "hit counts"
|
||||
msgstr "просмотры"
|
||||
|
||||
#: models.py:144
|
||||
msgid "hit"
|
||||
msgstr "хит"
|
||||
|
||||
#: models.py:145
|
||||
msgid "hits"
|
||||
msgstr "хиты"
|
||||
|
||||
#: models.py:183
|
||||
msgid "Blacklisted IP"
|
||||
msgstr "бан"
|
||||
|
||||
#: models.py:184
|
||||
msgid "Blacklisted IPs"
|
||||
msgstr "черный список (IP)"
|
||||
|
||||
#: models.py:195
|
||||
msgid "Blacklisted User Agent"
|
||||
msgstr "бан"
|
||||
|
||||
#: models.py:196
|
||||
msgid "Blacklisted User Agents"
|
||||
msgstr "черный список (UA)"
|
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
try:
|
||||
from django.core.management.base import BaseCommand
|
||||
except ImportError:
|
||||
from django.core.management.base import NoArgsCommand as BaseCommand
|
||||
|
||||
from hitcount.models import Hit
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Can be run as a cronjob or directly to clean out old Hits objects from the database."
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Command, self).__init__(*args, **kwargs)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.handle_noargs()
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
grace = getattr(settings, 'HITCOUNT_KEEP_HIT_IN_DATABASE', {'days': 30})
|
||||
period = timezone.now() - timedelta(**grace)
|
||||
qs = Hit.objects.filter(created__lt=period)
|
||||
number_removed = qs.count()
|
||||
qs.delete()
|
||||
self.stdout.write('Successfully removed %s Hits' % number_removed)
|
45
thesisenv/lib/python3.6/site-packages/hitcount/managers.py
Normal file
45
thesisenv/lib/python3.6/site-packages/hitcount/managers.py
Normal file
@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
class HitCountManager(models.Manager):
|
||||
|
||||
def get_for_object(self, obj):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
hit_count, created = self.get_or_create(
|
||||
content_type=ctype, object_pk=obj.pk)
|
||||
return hit_count
|
||||
|
||||
|
||||
class HitManager(models.Manager):
|
||||
|
||||
def filter_active(self, *args, **kwargs):
|
||||
"""
|
||||
Return only the 'active' hits.
|
||||
|
||||
How you count a hit/view will depend on personal choice: Should the
|
||||
same user/visitor *ever* be counted twice? After a week, or a month,
|
||||
or a year, should their view be counted again?
|
||||
|
||||
The defaulf is to consider a visitor's hit still 'active' if they
|
||||
return within a the last seven days.. After that the hit
|
||||
will be counted again. So if one person visits once a week for a year,
|
||||
they will add 52 hits to a given object.
|
||||
|
||||
Change how long the expiration is by adding to settings.py:
|
||||
|
||||
HITCOUNT_KEEP_HIT_ACTIVE = {'days' : 30, 'minutes' : 30}
|
||||
|
||||
Accepts days, seconds, microseconds, milliseconds, minutes,
|
||||
hours, and weeks. It's creating a datetime.timedelta object.
|
||||
|
||||
"""
|
||||
grace = getattr(settings, 'HITCOUNT_KEEP_HIT_ACTIVE', {'days': 7})
|
||||
period = timezone.now() - timedelta(**grace)
|
||||
return self.filter(created__gte=period).filter(*args, **kwargs)
|
@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('contenttypes', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlacklistIP',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('ip', models.CharField(unique=True, max_length=40)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'hitcount_blacklist_ip',
|
||||
'verbose_name': 'Blacklisted IP',
|
||||
'verbose_name_plural': 'Blacklisted IPs',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlacklistUserAgent',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('user_agent', models.CharField(unique=True, max_length=255)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'hitcount_blacklist_user_agent',
|
||||
'verbose_name': 'Blacklisted User Agent',
|
||||
'verbose_name_plural': 'Blacklisted User Agents',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Hit',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('ip', models.CharField(max_length=40, editable=False)),
|
||||
('session', models.CharField(max_length=40, editable=False)),
|
||||
('user_agent', models.CharField(max_length=255, editable=False)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-created',),
|
||||
'get_latest_by': 'created',
|
||||
'verbose_name': 'hit',
|
||||
'verbose_name_plural': 'hits',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HitCount',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('hits', models.PositiveIntegerField(default=0)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('object_pk', models.PositiveIntegerField(verbose_name='object ID')),
|
||||
('content_type', models.ForeignKey(related_name='content_type_set_for_hitcount',
|
||||
to='contenttypes.ContentType', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'modified',
|
||||
'ordering': ('-hits',),
|
||||
'verbose_name_plural': 'hit counts',
|
||||
'db_table': 'hitcount_hit_count',
|
||||
'verbose_name': 'hit count',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='hitcount',
|
||||
unique_together=set([('content_type', 'object_pk')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hit',
|
||||
name='hitcount',
|
||||
field=models.ForeignKey(editable=False, to='hitcount.HitCount', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hit',
|
||||
name='user',
|
||||
field=models.ForeignKey(editable=False, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hitcount', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hit',
|
||||
name='ip',
|
||||
field=models.CharField(max_length=40, db_index=True, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hit',
|
||||
name='session',
|
||||
field=models.CharField(max_length=40, db_index=True, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hitcount',
|
||||
name='object_pk',
|
||||
field=models.PositiveIntegerField(verbose_name='object ID'),
|
||||
),
|
||||
]
|
197
thesisenv/lib/python3.6/site-packages/hitcount/models.py
Normal file
197
thesisenv/lib/python3.6/site-packages/hitcount/models.py
Normal file
@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
from django.dispatch import receiver
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
||||
|
||||
from .managers import HitCountManager, HitManager
|
||||
from .signals import delete_hit_count
|
||||
|
||||
|
||||
@receiver(delete_hit_count)
|
||||
def delete_hit_count_handler(sender, instance, save_hitcount=False, **kwargs):
|
||||
"""
|
||||
Custom callback for the Hit.delete() method.
|
||||
|
||||
Hit.delete(): removes the hit from the associated HitCount object.
|
||||
Hit.delete(save_hitcount=True): preserves the hit for the associated
|
||||
HitCount object.
|
||||
|
||||
"""
|
||||
if not save_hitcount:
|
||||
instance.hitcount.decrease()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class HitCount(models.Model):
|
||||
"""
|
||||
Model that stores the hit totals for any content object.
|
||||
|
||||
"""
|
||||
hits = models.PositiveIntegerField(default=0)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
content_type = models.ForeignKey(
|
||||
ContentType, related_name="content_type_set_for_%(class)s", on_delete=models.CASCADE)
|
||||
object_pk = models.PositiveIntegerField('object ID')
|
||||
content_object = GenericForeignKey('content_type', 'object_pk')
|
||||
|
||||
objects = HitCountManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('-hits',)
|
||||
get_latest_by = "modified"
|
||||
verbose_name = _("hit count")
|
||||
verbose_name_plural = _("hit counts")
|
||||
unique_together = ("content_type", "object_pk")
|
||||
db_table = "hitcount_hit_count"
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.content_object
|
||||
|
||||
def increase(self):
|
||||
self.hits = F('hits') + 1
|
||||
self.save()
|
||||
|
||||
def decrease(self):
|
||||
self.hits = F('hits') - 1
|
||||
self.save()
|
||||
|
||||
def hits_in_last(self, **kwargs):
|
||||
"""
|
||||
Returns hit count for an object during a given time period.
|
||||
|
||||
This will only work for as long as hits are saved in the Hit database.
|
||||
If you are purging your database after 45 days, for example, that means
|
||||
that asking for hits in the last 60 days will return an incorrect
|
||||
number as that the longest period it can search will be 45 days.
|
||||
|
||||
For example: hits_in_last(days=7).
|
||||
|
||||
Accepts days, seconds, microseconds, milliseconds, minutes,
|
||||
hours, and weeks. It's creating a datetime.timedelta object.
|
||||
|
||||
"""
|
||||
assert kwargs, "Must provide at least one timedelta arg (eg, days=1)"
|
||||
|
||||
period = timezone.now() - timedelta(**kwargs)
|
||||
return self.hit_set.filter(created__gte=period).count()
|
||||
|
||||
# def get_content_object_url(self):
|
||||
# """
|
||||
# Django has this in its contrib.comments.model file -- seems worth
|
||||
# implementing though it may take a couple steps.
|
||||
#
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Hit(models.Model):
|
||||
"""
|
||||
Model captures a single Hit by a visitor.
|
||||
|
||||
None of the fields are editable because they are all dynamically created.
|
||||
Browsing the Hit list in the Admin will allow one to blacklist both
|
||||
IP addresses as well as User Agents. Blacklisting simply causes those
|
||||
hits to not be counted or recorded.
|
||||
|
||||
Depending on how long you set the HITCOUNT_KEEP_HIT_ACTIVE, and how long
|
||||
you want to be able to use `HitCount.hits_in_last(days=30)` you can choose
|
||||
to clean up your Hit table by using the management `hitcount_cleanup`
|
||||
management command.
|
||||
|
||||
"""
|
||||
created = models.DateTimeField(editable=False, auto_now_add=True, db_index=True)
|
||||
ip = models.CharField(max_length=40, editable=False, db_index=True)
|
||||
session = models.CharField(max_length=40, editable=False, db_index=True)
|
||||
user_agent = models.CharField(max_length=255, editable=False)
|
||||
user = models.ForeignKey(AUTH_USER_MODEL, null=True, editable=False, on_delete=models.CASCADE)
|
||||
hitcount = models.ForeignKey(HitCount, editable=False, on_delete=models.CASCADE)
|
||||
|
||||
objects = HitManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('-created',)
|
||||
get_latest_by = 'created'
|
||||
verbose_name = _("hit")
|
||||
verbose_name_plural = _("hits")
|
||||
|
||||
def __str__(self):
|
||||
return 'Hit: %s' % self.pk
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
The first time the object is created and saved, we increment
|
||||
the associated HitCount object by one. The opposite applies
|
||||
if the Hit is deleted.
|
||||
|
||||
"""
|
||||
if self.pk is None:
|
||||
self.hitcount.increase()
|
||||
|
||||
super(Hit, self).save(*args, **kwargs)
|
||||
|
||||
def delete(self, save_hitcount=False):
|
||||
"""
|
||||
If a Hit is deleted and save_hitcount=True, it will preserve the
|
||||
HitCount object's total. However, under normal circumstances, a
|
||||
delete() will trigger a subtraction from the HitCount object's total.
|
||||
|
||||
NOTE: This doesn't work at all during a queryset.delete().
|
||||
|
||||
"""
|
||||
delete_hit_count.send(
|
||||
sender=self, instance=self, save_hitcount=save_hitcount)
|
||||
super(Hit, self).delete()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class BlacklistIP(models.Model):
|
||||
|
||||
ip = models.CharField(max_length=40, unique=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "hitcount_blacklist_ip"
|
||||
verbose_name = _("Blacklisted IP")
|
||||
verbose_name_plural = _("Blacklisted IPs")
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.ip
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class BlacklistUserAgent(models.Model):
|
||||
|
||||
user_agent = models.CharField(max_length=255, unique=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "hitcount_blacklist_user_agent"
|
||||
verbose_name = _("Blacklisted User Agent")
|
||||
verbose_name_plural = _("Blacklisted User Agents")
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.user_agent
|
||||
|
||||
|
||||
class HitCountMixin(object):
|
||||
"""
|
||||
HitCountMixin provides an easy way to add a `hit_count` property to your
|
||||
model that will return the related HitCount object.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hit_count(self):
|
||||
ctype = ContentType.objects.get_for_model(self.__class__)
|
||||
hit_count, created = HitCount.objects.get_or_create(
|
||||
content_type=ctype, object_pk=self.pk)
|
||||
return hit_count
|
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from django.dispatch import Signal
|
||||
|
||||
delete_hit_count = Signal(providing_args=['save_hitcount'])
|
@ -0,0 +1,60 @@
|
||||
$(document).ready(function() {
|
||||
/**
|
||||
* https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
|
||||
*
|
||||
* Remember you will need to ensure csrf tokens by adding:
|
||||
* @ensure_csrf_cookie to your views that require this javascript
|
||||
*
|
||||
* Also, you will probably want to include this with your other sitewide
|
||||
* javascript files ... this is just an example.
|
||||
*/
|
||||
|
||||
if ( typeof hitcountJS === 'undefined' ) {
|
||||
// since this is loaded on every page only do something
|
||||
// if a hit is going to be counted
|
||||
return;
|
||||
}
|
||||
|
||||
var hitcountPK = hitcountJS['hitcountPK'];
|
||||
var hitcountURL = hitcountJS['hitcountURL'];
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$.post( hitcountURL, { "hitcountPK" : hitcountPK },
|
||||
function(data, status) {
|
||||
|
||||
console.log(data); // just so you can see the response
|
||||
|
||||
if (data.status == 'error') {
|
||||
// do something for error?
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Wrapper for jQuery's $.post() that retrieves the CSRF token from the browser
|
||||
* cookie and sets then sets "X-CSRFToken" header in one fell swoop.
|
||||
*
|
||||
* Based on the example code given at the Django docs:
|
||||
* https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax
|
||||
*
|
||||
* Use as you would $.post().
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
$.postCSRF = function(url, data, callback, type) {
|
||||
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
// shift arguments if data argument was omitted
|
||||
if ($.isFunction(data)) {
|
||||
type = type || callback;
|
||||
callback = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
return $.ajax(jQuery.extend({
|
||||
url: url,
|
||||
type: "POST",
|
||||
dataType: type,
|
||||
data: data,
|
||||
success: callback,
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
}, jQuery.isPlainObject(url) && url));
|
||||
};
|
||||
|
||||
}(jQuery));
|
@ -0,0 +1,319 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from collections import namedtuple
|
||||
|
||||
from django import template
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
try:
|
||||
from django.core.urlresolvers import reverse
|
||||
except ImportError:
|
||||
from django.urls import reverse
|
||||
|
||||
from hitcount.models import HitCount
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
def get_hit_count_from_obj_variable(context, obj_variable, tag_name):
|
||||
"""
|
||||
Helper function to return a HitCount for a given template object variable.
|
||||
|
||||
Raises TemplateSyntaxError if the passed object variable cannot be parsed.
|
||||
"""
|
||||
error_to_raise = template.TemplateSyntaxError(
|
||||
"'%(a)s' requires a valid individual model variable "
|
||||
"in the form of '%(a)s for [model_obj]'.\n"
|
||||
"Got: %(b)s" % {'a': tag_name, 'b': obj_variable}
|
||||
)
|
||||
|
||||
try:
|
||||
obj = obj_variable.resolve(context)
|
||||
except template.VariableDoesNotExist:
|
||||
raise error_to_raise
|
||||
|
||||
try:
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
except AttributeError:
|
||||
raise error_to_raise
|
||||
|
||||
hit_count, created = HitCount.objects.get_or_create(
|
||||
content_type=ctype, object_pk=obj.pk)
|
||||
|
||||
return hit_count
|
||||
|
||||
|
||||
def return_period_from_string(arg):
|
||||
"""
|
||||
Takes a string such as "days=1,seconds=30" and strips the quotes
|
||||
and returns a dictionary with the key/value pairs
|
||||
|
||||
"""
|
||||
period = {}
|
||||
|
||||
if arg[0] == '"' and arg[-1] == '"':
|
||||
opt = arg[1:-1] # remove quotes
|
||||
else:
|
||||
opt = arg
|
||||
|
||||
for o in opt.split(","):
|
||||
key, value = o.split("=")
|
||||
period[str(key)] = int(value)
|
||||
|
||||
return period
|
||||
|
||||
|
||||
class GetHitCount(template.Node):
|
||||
|
||||
def handle_token(cls, parser, token):
|
||||
args = token.contents.split()
|
||||
|
||||
# {% get_hit_count for [obj] %}
|
||||
if len(args) == 3 and args[1] == 'for':
|
||||
return cls(obj_as_str=args[2])
|
||||
|
||||
# {% get_hit_count for [obj] as [var] %}
|
||||
elif len(args) == 5 and args[1] == 'for' and args[3] == 'as':
|
||||
return cls(obj_as_str=args[2],
|
||||
as_varname=args[4],)
|
||||
|
||||
# {% get_hit_count for [obj] within ["days=1,minutes=30"] %}
|
||||
elif len(args) == 5 and args[1] == 'for' and args[3] == 'within':
|
||||
return cls(obj_as_str=args[2],
|
||||
period=return_period_from_string(args[4]))
|
||||
|
||||
# {% get_hit_count for [obj] within ["days=1,minutes=30"] as [var] %}
|
||||
elif len(args) == 7 and args[1] == 'for' and \
|
||||
args[3] == 'within' and args[5] == 'as':
|
||||
return cls(obj_as_str=args[2],
|
||||
as_varname=args[6],
|
||||
period=return_period_from_string(args[4]))
|
||||
|
||||
else: # TODO - should there be more troubleshooting prior to bailing?
|
||||
raise template.TemplateSyntaxError(
|
||||
"'get_hit_count' requires "
|
||||
"'for [object] in [period] as [var]' (got %r)" % args
|
||||
)
|
||||
|
||||
handle_token = classmethod(handle_token)
|
||||
|
||||
def __init__(self, obj_as_str, as_varname=None, period=None):
|
||||
self.obj_variable = template.Variable(obj_as_str)
|
||||
self.as_varname = as_varname
|
||||
self.period = period
|
||||
|
||||
def render(self, context):
|
||||
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count')
|
||||
|
||||
if self.period: # if user sets a time period, use it
|
||||
try:
|
||||
hits = hit_count.hits_in_last(**self.period)
|
||||
except TypeError:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'get_hit_count for [obj] within [timedelta]' requires "
|
||||
"a valid comma separated list of timedelta arguments. "
|
||||
"For example, ['days=5,hours=6']. "
|
||||
"Got these instead: %s" % self.period
|
||||
)
|
||||
else:
|
||||
hits = hit_count.hits
|
||||
|
||||
if self.as_varname: # if user gives us a variable to return
|
||||
context[self.as_varname] = str(hits)
|
||||
return ''
|
||||
else:
|
||||
return str(hits)
|
||||
|
||||
|
||||
def get_hit_count(parser, token):
|
||||
"""
|
||||
Returns hit counts for an object.
|
||||
|
||||
- Return total hits for an object:
|
||||
{% get_hit_count for [object] %}
|
||||
|
||||
- Get total hits for an object as a specified variable:
|
||||
{% get_hit_count for [object] as [var] %}
|
||||
|
||||
- Get total hits for an object over a certain time period:
|
||||
{% get_hit_count for [object] within ["days=1,minutes=30"] %}
|
||||
|
||||
- Get total hits for an object over a certain time period as a variable:
|
||||
{% get_hit_count for [object] within ["days=1,minutes=30"] as [var] %}
|
||||
|
||||
The time arguments need to follow datetime.timedelta's limitations:
|
||||
Accepts days, seconds, microseconds, milliseconds, minutes,
|
||||
hours, and weeks.
|
||||
|
||||
"""
|
||||
return GetHitCount.handle_token(parser, token)
|
||||
|
||||
register.tag('get_hit_count', get_hit_count)
|
||||
|
||||
|
||||
class WriteHitCountJavascriptVariables(template.Node):
|
||||
|
||||
def handle_token(cls, parser, token):
|
||||
args = token.contents.split()
|
||||
|
||||
if len(args) == 3 and args[1] == 'for':
|
||||
return cls(obj_variable=args[2])
|
||||
|
||||
else:
|
||||
raise template.TemplateSyntaxError(
|
||||
'insert_hit_count_js_variables requires this syntax: '
|
||||
'"insert_hit_count_js_variables for [object]"\n'
|
||||
'Got: %s' % ' '.join(str(i) for i in args)
|
||||
)
|
||||
|
||||
handle_token = classmethod(handle_token)
|
||||
|
||||
def __init__(self, obj_variable):
|
||||
self.obj_variable = template.Variable(obj_variable)
|
||||
|
||||
def render(self, context):
|
||||
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'insert_hit_count_js_variables')
|
||||
|
||||
js = '<script type="text/javascript">\n' + \
|
||||
"var hitcountJS = {" + \
|
||||
"hitcountPK : '" + str(hit_count.pk) + "'," + \
|
||||
"hitcountURL : '" + str(reverse('hitcount:hit_ajax')) + "'};" + \
|
||||
"\n</script>"
|
||||
|
||||
return js
|
||||
|
||||
|
||||
def insert_hit_count_js_variables(parser, token):
|
||||
"""
|
||||
Injects JavaScript global variables into your template. These variables
|
||||
can be used in your JavaScript files to send the correctly mapped HitCount
|
||||
ID to the server (see: hitcount-jquery.js for an example).
|
||||
|
||||
{% insert_hit_count_js_variables for [object] %}
|
||||
"""
|
||||
return WriteHitCountJavascriptVariables.handle_token(parser, token)
|
||||
|
||||
register.tag('insert_hit_count_js_variables', insert_hit_count_js_variables)
|
||||
|
||||
|
||||
class GetHitCountJavascriptVariables(template.Node):
|
||||
|
||||
def handle_token(cls, parser, token):
|
||||
args = token.contents.split()
|
||||
|
||||
if len(args) == 5 and args[1] == 'for' and args[3] == 'as':
|
||||
return cls(obj_variable=args[2], as_varname=args[4])
|
||||
|
||||
else:
|
||||
raise template.TemplateSyntaxError(
|
||||
'get_hit_count_js_variables requires this syntax: '
|
||||
'"get_hit_count_js_variables for [object] as [var_name]."\n'
|
||||
'Got: %s' % ' '.join(str(i) for i in args)
|
||||
)
|
||||
|
||||
handle_token = classmethod(handle_token)
|
||||
|
||||
def __init__(self, obj_variable, as_varname):
|
||||
self.obj_variable = template.Variable(obj_variable)
|
||||
self.as_varname = as_varname
|
||||
|
||||
def render(self, context):
|
||||
HitcountVariables = namedtuple('HitcountVariables', 'pk ajax_url hits')
|
||||
|
||||
hit_count = get_hit_count_from_obj_variable(context, self.obj_variable, 'get_hit_count_js_variables')
|
||||
|
||||
context[self.as_varname] = HitcountVariables(
|
||||
hit_count.pk, str(reverse('hitcount:hit_ajax')), str(hit_count.hits))
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
def get_hit_count_js_variables(parser, token):
|
||||
"""
|
||||
Injects JavaScript global variables into your template. These variables
|
||||
can be used in your JavaScript files to send the correctly mapped HitCount
|
||||
ID to the server (see: hitcount-jquery.js for an example).
|
||||
|
||||
{% get_hit_count_js_variables for [object] as [var_name] %}
|
||||
|
||||
Will provide two variables:
|
||||
[var_name].pk = the hitcount pk to be sent via JavaScript
|
||||
[var_name].ajax_url = the relative url to post the ajax request to
|
||||
"""
|
||||
return GetHitCountJavascriptVariables.handle_token(parser, token)
|
||||
|
||||
register.tag('get_hit_count_js_variables', get_hit_count_js_variables)
|
||||
|
||||
|
||||
class WriteHitCountJavascript(template.Node):
|
||||
|
||||
JS_TEMPLATE = """
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
jQuery(document).ready(function($) {
|
||||
$.postCSRF("%s", {
|
||||
hitcountPK: "%s"
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
"""
|
||||
|
||||
JS_TEMPLATE_DEBUG = """
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
jQuery(document).ready(function($) {
|
||||
$.postCSRF("%s", {
|
||||
hitcountPK: "%s"
|
||||
}).done(function(data) {
|
||||
console.log('django-hitcount: AJAX POST succeeded.');
|
||||
console.log(data);
|
||||
}).fail(function(data) {
|
||||
console.log('django-hitcount: AJAX POST failed.');
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
"""
|
||||
|
||||
def handle_token(cls, parser, token):
|
||||
args = token.contents.split()
|
||||
|
||||
if len(args) == 3 and args[1] == 'for':
|
||||
return cls(obj_variable=args[2], debug=False)
|
||||
elif len(args) == 4 and args[1] == 'for' and args[3] == 'debug':
|
||||
return cls(obj_variable=args[2], debug=True)
|
||||
else:
|
||||
raise template.TemplateSyntaxError(
|
||||
'insert_hit_count_js requires this syntax: '
|
||||
'"insert_hit_count_js for [object]"\n'
|
||||
'"insert_hit_count_js for [object] debug"'
|
||||
'Got: %s' % ' '.join(str(i) for i in args)
|
||||
)
|
||||
|
||||
handle_token = classmethod(handle_token)
|
||||
|
||||
def __init__(self, obj_variable, debug):
|
||||
self.obj_variable = template.Variable(obj_variable)
|
||||
self.debug = debug
|
||||
|
||||
def render(self, context):
|
||||
hit_count = get_hit_count_from_obj_variable(
|
||||
context,
|
||||
self.obj_variable,
|
||||
'insert_hit_count_js'
|
||||
)
|
||||
template = self.JS_TEMPLATE_DEBUG if self.debug else self.JS_TEMPLATE
|
||||
return template % (str(reverse('hitcount:hit_ajax')), str(hit_count.pk))
|
||||
|
||||
|
||||
def insert_hit_count_js(parser, token):
|
||||
"""
|
||||
Injects the JavaScript into your template that works with jquery.postcsrf.js.
|
||||
|
||||
{% insert_hit_count_js_variables for [object] %}
|
||||
"""
|
||||
return WriteHitCountJavascript.handle_token(parser, token)
|
||||
|
||||
|
||||
register.tag('insert_hit_count_js', insert_hit_count_js)
|
12
thesisenv/lib/python3.6/site-packages/hitcount/urls.py
Normal file
12
thesisenv/lib/python3.6/site-packages/hitcount/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from hitcount.views import HitCountJSONView
|
||||
|
||||
app_name = 'hitcount'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^hit/ajax/$', HitCountJSONView.as_view(), name='hit_ajax'),
|
||||
]
|
47
thesisenv/lib/python3.6/site-packages/hitcount/utils.py
Normal file
47
thesisenv/lib/python3.6/site-packages/hitcount/utils.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import warnings
|
||||
|
||||
|
||||
# this is not intended to be an all-knowing IP address regex
|
||||
IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
|
||||
|
||||
|
||||
def get_ip(request):
|
||||
"""
|
||||
Retrieves the remote IP address from the request data. If the user is
|
||||
behind a proxy, they may have a comma-separated list of IP addresses, so
|
||||
we need to account for that. In such a case, only the first IP in the
|
||||
list will be retrieved. Also, some hosts that use a proxy will put the
|
||||
REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
|
||||
IP from the proper place.
|
||||
|
||||
**NOTE** This function was taken from django-tracking (MIT LICENSE)
|
||||
http://code.google.com/p/django-tracking/
|
||||
"""
|
||||
|
||||
# if neither header contain a value, just use local loopback
|
||||
ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
|
||||
request.META.get('REMOTE_ADDR', '127.0.0.1'))
|
||||
if ip_address:
|
||||
# make sure we have one and only one IP
|
||||
try:
|
||||
ip_address = IP_RE.match(ip_address)
|
||||
if ip_address:
|
||||
ip_address = ip_address.group(0)
|
||||
else:
|
||||
# no IP, probably from some dirty proxy or other device
|
||||
# throw in some bogus IP
|
||||
ip_address = '10.0.0.1'
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return ip_address
|
||||
|
||||
|
||||
class RemovedInHitCount13Warning(DeprecationWarning):
|
||||
pass
|
||||
|
||||
# enable warnings by default for our deprecated
|
||||
warnings.simplefilter("default", RemovedInHitCount13Warning)
|
189
thesisenv/lib/python3.6/site-packages/hitcount/views.py
Normal file
189
thesisenv/lib/python3.6/site-packages/hitcount/views.py
Normal file
@ -0,0 +1,189 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
|
||||
from django.http import Http404, JsonResponse, HttpResponseBadRequest
|
||||
from django.conf import settings
|
||||
from django.views.generic import View, DetailView
|
||||
|
||||
from hitcount.utils import get_ip
|
||||
from hitcount.models import Hit, HitCount, BlacklistIP, BlacklistUserAgent
|
||||
from hitcount.utils import RemovedInHitCount13Warning
|
||||
|
||||
|
||||
class HitCountMixin(object):
|
||||
"""
|
||||
Mixin to evaluate a HttpRequest and a HitCount and determine whether or not
|
||||
the HitCount should be incremented and the Hit recorded.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def hit_count(self, request, hitcount):
|
||||
"""
|
||||
Called with a HttpRequest and HitCount object it will return a
|
||||
namedtuple:
|
||||
|
||||
UpdateHitCountResponse(hit_counted=Boolean, hit_message='Message').
|
||||
|
||||
`hit_counted` will be True if the hit was counted and False if it was
|
||||
not. `'hit_message` will indicate by what means the Hit was either
|
||||
counted or ignored.
|
||||
"""
|
||||
UpdateHitCountResponse = namedtuple(
|
||||
'UpdateHitCountResponse', 'hit_counted hit_message')
|
||||
|
||||
# as of Django 1.8.4 empty sessions are not being saved
|
||||
# https://code.djangoproject.com/ticket/25489
|
||||
if request.session.session_key is None:
|
||||
request.session.save()
|
||||
|
||||
user = request.user
|
||||
try:
|
||||
is_authenticated_user = user.is_authenticated()
|
||||
except:
|
||||
is_authenticated_user = user.is_authenticated
|
||||
session_key = request.session.session_key
|
||||
ip = get_ip(request)
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '')[:255]
|
||||
hits_per_ip_limit = getattr(settings, 'HITCOUNT_HITS_PER_IP_LIMIT', 0)
|
||||
exclude_user_group = getattr(settings, 'HITCOUNT_EXCLUDE_USER_GROUP', None)
|
||||
|
||||
# first, check our request against the IP blacklist
|
||||
if BlacklistIP.objects.filter(ip__exact=ip):
|
||||
return UpdateHitCountResponse(
|
||||
False, 'Not counted: user IP has been blacklisted')
|
||||
|
||||
# second, check our request against the user agent blacklist
|
||||
if BlacklistUserAgent.objects.filter(user_agent__exact=user_agent):
|
||||
return UpdateHitCountResponse(
|
||||
False, 'Not counted: user agent has been blacklisted')
|
||||
|
||||
# third, see if we are excluding a specific user group or not
|
||||
if exclude_user_group and is_authenticated_user:
|
||||
if user.groups.filter(name__in=exclude_user_group):
|
||||
return UpdateHitCountResponse(
|
||||
False, 'Not counted: user excluded by group')
|
||||
|
||||
# eliminated first three possible exclusions, now on to checking our database of
|
||||
# active hits to see if we should count another one
|
||||
|
||||
# start with a fresh active query set (HITCOUNT_KEEP_HIT_ACTIVE)
|
||||
qs = Hit.objects.filter_active()
|
||||
|
||||
# check limit on hits from a unique ip address (HITCOUNT_HITS_PER_IP_LIMIT)
|
||||
if hits_per_ip_limit:
|
||||
if qs.filter(ip__exact=ip).count() >= hits_per_ip_limit:
|
||||
return UpdateHitCountResponse(
|
||||
False, 'Not counted: hits per IP address limit reached')
|
||||
|
||||
# create a generic Hit object with request data
|
||||
hit = Hit(session=session_key, hitcount=hitcount, ip=get_ip(request),
|
||||
user_agent=request.META.get('HTTP_USER_AGENT', '')[:255],)
|
||||
|
||||
# first, use a user's authentication to see if they made an earlier hit
|
||||
if is_authenticated_user:
|
||||
if not qs.filter(user=user, hitcount=hitcount):
|
||||
hit.user = user # associate this hit with a user
|
||||
hit.save()
|
||||
|
||||
response = UpdateHitCountResponse(
|
||||
True, 'Hit counted: user authentication')
|
||||
else:
|
||||
response = UpdateHitCountResponse(
|
||||
False, 'Not counted: authenticated user has active hit')
|
||||
|
||||
# if not authenticated, see if we have a repeat session
|
||||
else:
|
||||
if not qs.filter(session=session_key, hitcount=hitcount):
|
||||
hit.save()
|
||||
response = UpdateHitCountResponse(
|
||||
True, 'Hit counted: session key')
|
||||
else:
|
||||
response = UpdateHitCountResponse(
|
||||
False, 'Not counted: session key has active hit')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class HitCountJSONView(View, HitCountMixin):
|
||||
"""
|
||||
JSON response view to handle HitCount POST.
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.is_ajax():
|
||||
raise Http404()
|
||||
return super(HitCountJSONView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
msg = "Hits counted via POST only."
|
||||
return JsonResponse({'success': False, 'error_message': msg})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
hitcount_pk = request.POST.get('hitcountPK')
|
||||
|
||||
try:
|
||||
hitcount = HitCount.objects.get(pk=hitcount_pk)
|
||||
except:
|
||||
return HttpResponseBadRequest("HitCount object_pk not working")
|
||||
|
||||
hit_count_response = self.hit_count(request, hitcount)
|
||||
return JsonResponse(hit_count_response._asdict())
|
||||
|
||||
|
||||
class HitCountDetailView(DetailView, HitCountMixin):
|
||||
"""
|
||||
HitCountDetailView provides an inherited DetailView that will inject the
|
||||
template context with a `hitcount` variable giving you the number of
|
||||
Hits for an object without using a template tag.
|
||||
|
||||
Optionally, by setting `count_hit = True` you can also do the business of
|
||||
counting the Hit for this object (in lieu of using JavaScript). It will
|
||||
then further inject the response from the attempt to count the Hit into
|
||||
the template context.
|
||||
"""
|
||||
count_hit = False
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HitCountDetailView, self).get_context_data(**kwargs)
|
||||
if self.object:
|
||||
hit_count = HitCount.objects.get_for_object(self.object)
|
||||
hits = hit_count.hits
|
||||
context['hitcount'] = {'pk': hit_count.pk}
|
||||
|
||||
if self.count_hit:
|
||||
hit_count_response = self.hit_count(self.request, hit_count)
|
||||
if hit_count_response.hit_counted:
|
||||
hits = hits + 1
|
||||
context['hitcount']['hit_counted'] = hit_count_response.hit_counted
|
||||
context['hitcount']['hit_message'] = hit_count_response.hit_message
|
||||
|
||||
context['hitcount']['total_hits'] = hits
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def _update_hit_count(request, hitcount):
|
||||
"""
|
||||
Deprecated in 1.2. Use hitcount.views.Hit CountMixin.hit_count() instead.
|
||||
"""
|
||||
warnings.warn(
|
||||
"hitcount.views._update_hit_count is deprecated. "
|
||||
"Use hitcount.views.HitCountMixin.hit_count() instead.",
|
||||
RemovedInHitCount13Warning
|
||||
)
|
||||
return HitCountMixin.hit_count(request, hitcount)
|
||||
|
||||
|
||||
def update_hit_count_ajax(request, *args, **kwargs):
|
||||
"""
|
||||
Deprecated in 1.2. Use hitcount.views.HitCountJSONView instead.
|
||||
"""
|
||||
warnings.warn(
|
||||
"hitcount.views.update_hit_count_ajax is deprecated. "
|
||||
"Use hitcount.views.HitCountJSONView instead.",
|
||||
RemovedInHitCount13Warning
|
||||
)
|
||||
view = HitCountJSONView.as_view()
|
||||
return view(request, *args, **kwargs)
|
Loading…
x
Reference in New Issue
Block a user