added hitcount for staff
This commit is contained in:
parent
38a25437b6
commit
d773cba908
@ -1,5 +1,7 @@
|
|||||||
{% extends 'base.html' %}{% block content %}
|
{% extends 'base.html' %}{% block content %}
|
||||||
|
{% load hitcount_tags %}
|
||||||
<div class="post">
|
<div class="post">
|
||||||
|
|
||||||
{% if post.published_date %}
|
{% if post.published_date %}
|
||||||
<div class="date">
|
<div class="date">
|
||||||
{{ post.published_date }}
|
{{ post.published_date }}
|
||||||
@ -17,6 +19,7 @@
|
|||||||
<h1>{{ post.title }}</h1>
|
<h1>{{ post.title }}</h1>
|
||||||
<p>{{ post.text|linebreaksbr }}</p>
|
<p>{{ post.text|linebreaksbr }}</p>
|
||||||
<p>
|
<p>
|
||||||
|
Tags:
|
||||||
{% for tag in post.tags.all %}
|
{% for tag in post.tags.all %}
|
||||||
<a href="{% url 'blog_search_list_view' %}">{{ tag.name }}, </a>
|
<a href="{% url 'blog_search_list_view' %}">{{ tag.name }}, </a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -24,5 +27,10 @@
|
|||||||
<p>
|
<p>
|
||||||
Autor: {{ post.author }}
|
Autor: {{ post.author }}
|
||||||
</p>
|
</p>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<p>
|
||||||
|
{% get_hit_count for post %} Benutzer haben diesen Post bereits gelesen!
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -16,6 +16,8 @@ from django.contrib import messages
|
|||||||
from post_office.models import EmailTemplate
|
from post_office.models import EmailTemplate
|
||||||
from post_office import mail
|
from post_office import mail
|
||||||
|
|
||||||
|
from hitcount.models import HitCount
|
||||||
|
from hitcount.views import HitCountMixin
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -61,7 +63,9 @@ def post_list(request, slug=None):
|
|||||||
@login_required
|
@login_required
|
||||||
def post_detail(request, pk):
|
def post_detail(request, pk):
|
||||||
post = get_object_or_404(Post, pk=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
|
@login_required
|
||||||
@ -182,17 +186,10 @@ def blog_search_list_view(request):
|
|||||||
def tag_cloud(request):
|
def tag_cloud(request):
|
||||||
return render(request, 'tag_cloud.html', {})
|
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(
|
mail.send(
|
||||||
'kleinhenz.e@gmail.com', # List of email addresses also accepted
|
'kleinhenz.e@gmail.com', # List of email addresses also accepted
|
||||||
'esther.kleinhenz@web.de',
|
'esther.kleinhenz@web.de',
|
||||||
template='weekly-update',
|
subject='My email',
|
||||||
context={'name': 'alice'},
|
message='Hi there!',
|
||||||
|
html_message='Hi <strong>there</strong>!',
|
||||||
)
|
)
|
||||||
|
@ -48,6 +48,7 @@ INSTALLED_APPS = [
|
|||||||
'taggit_templatetags2',
|
'taggit_templatetags2',
|
||||||
'kombu.transport.django',
|
'kombu.transport.django',
|
||||||
'post_office',
|
'post_office',
|
||||||
|
'hitcount',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
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