From d773cba9085a416c96cc39c2d14f7cbeb751d582 Mon Sep 17 00:00:00 2001 From: Esther Kleinhenz Date: Tue, 30 Oct 2018 18:43:30 +0100 Subject: [PATCH] added hitcount for staff --- application/templates/post_detail.html | 10 +- application/views.py | 19 +- mysite/settings.py | 1 + .../DESCRIPTION.rst | 38 +++ .../django_hitcount-1.3.0.dist-info/INSTALLER | 1 + .../django_hitcount-1.3.0.dist-info/METADATA | 61 ++++ .../django_hitcount-1.3.0.dist-info/RECORD | 73 ++++ .../django_hitcount-1.3.0.dist-info/WHEEL | 6 + .../metadata.json | 1 + .../top_level.txt | 1 + .../site-packages/hitcount/__init__.py | 5 + .../python3.6/site-packages/hitcount/admin.py | 102 ++++++ .../hitcount/locale/ru/LC_MESSAGES/django.mo | Bin 0 -> 922 bytes .../hitcount/locale/ru/LC_MESSAGES/django.po | 51 +++ .../hitcount/management/__init__.py | 0 .../hitcount/management/commands/__init__.py | 0 .../management/commands/hitcount_cleanup.py | 32 ++ .../site-packages/hitcount/managers.py | 45 +++ .../hitcount/migrations/0001_initial.py | 94 ++++++ .../migrations/0002_index_ip_and_session.py | 29 ++ .../hitcount/migrations/__init__.py | 0 .../site-packages/hitcount/models.py | 197 +++++++++++ .../site-packages/hitcount/signals.py | 5 + .../static/hitcount/hitcount-jquery.js | 60 ++++ .../static/hitcount/jquery.postcsrf.js | 59 ++++ .../hitcount/templatetags/__init__.py | 0 .../hitcount/templatetags/hitcount_tags.py | 319 ++++++++++++++++++ .../python3.6/site-packages/hitcount/urls.py | 12 + .../python3.6/site-packages/hitcount/utils.py | 47 +++ .../python3.6/site-packages/hitcount/views.py | 189 +++++++++++ 30 files changed, 1445 insertions(+), 12 deletions(-) create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/DESCRIPTION.rst create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/INSTALLER create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/METADATA create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/RECORD create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/WHEEL create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/metadata.json create mode 100644 thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/top_level.txt create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/__init__.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/admin.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.mo create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.po create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/management/__init__.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/management/commands/__init__.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/management/commands/hitcount_cleanup.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/managers.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/migrations/0001_initial.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/migrations/0002_index_ip_and_session.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/migrations/__init__.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/models.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/signals.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/hitcount-jquery.js create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/jquery.postcsrf.js create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/templatetags/__init__.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/templatetags/hitcount_tags.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/urls.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/utils.py create mode 100644 thesisenv/lib/python3.6/site-packages/hitcount/views.py diff --git a/application/templates/post_detail.html b/application/templates/post_detail.html index 4b8512f..26a7569 100644 --- a/application/templates/post_detail.html +++ b/application/templates/post_detail.html @@ -1,5 +1,7 @@ -{% extends 'base.html' %} {% block content %} +{% extends 'base.html' %}{% block content %} + {% load hitcount_tags %}
+ {% if post.published_date %}
{{ post.published_date }} @@ -17,6 +19,7 @@

{{ post.title }}

{{ post.text|linebreaksbr }}

+ Tags: {% for tag in post.tags.all %} {{ tag.name }}, {% endfor %} @@ -24,5 +27,10 @@

Autor: {{ post.author }}

+ {% if user.is_staff %} +

+ {% get_hit_count for post %} Benutzer haben diesen Post bereits gelesen! +

+ {% endif %}
{% endblock %} \ No newline at end of file diff --git a/application/views.py b/application/views.py index cf241c5..c94ed88 100644 --- a/application/views.py +++ b/application/views.py @@ -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 {{ name }}, 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 there!', ) diff --git a/mysite/settings.py b/mysite/settings.py index 0aa2afd..b31a053 100644 --- a/mysite/settings.py +++ b/mysite/settings.py @@ -48,6 +48,7 @@ INSTALLED_APPS = [ 'taggit_templatetags2', 'kombu.transport.django', 'post_office', + 'hitcount', ] MIDDLEWARE = [ diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/DESCRIPTION.rst b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..4e94b94 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/DESCRIPTION.rst @@ -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: +-------------- + +``_ + +Source Code: +------------ + +``_ + +Issues +------ + +Use the GitHub `issue tracker`_ for django-hitcount to submit bugs, issues, and feature requests. + +Changelog +--------- + +``_ + +.. _issue tracker: https://github.com/thornomad/django-hitcount/issues + + diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/INSTALLER b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/METADATA b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/METADATA new file mode 100644 index 0000000..54628f3 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/METADATA @@ -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: +-------------- + +``_ + +Source Code: +------------ + +``_ + +Issues +------ + +Use the GitHub `issue tracker`_ for django-hitcount to submit bugs, issues, and feature requests. + +Changelog +--------- + +``_ + +.. _issue tracker: https://github.com/thornomad/django-hitcount/issues + + diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/RECORD b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/RECORD new file mode 100644 index 0000000..cdee8fd --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/RECORD @@ -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 diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/WHEEL b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/WHEEL new file mode 100644 index 0000000..7332a41 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/WHEEL @@ -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 + diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/metadata.json b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/metadata.json new file mode 100644 index 0000000..e00f50d --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/metadata.json @@ -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"} \ No newline at end of file diff --git a/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/top_level.txt b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/top_level.txt new file mode 100644 index 0000000..a725063 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/django_hitcount-1.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +hitcount diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/__init__.py b/thesisenv/lib/python3.6/site-packages/hitcount/__init__.py new file mode 100644 index 0000000..544262d --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/__init__.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + +VERSION = (1, 3, 0) + +__version__ = '.'.join(str(i) for i in VERSION) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/admin.py b/thesisenv/lib/python3.6/site-packages/hitcount/admin.py new file mode 100644 index 0000000..3ab1616 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/admin.py @@ -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) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.mo b/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..5db3106564f43a4bd81dbe3c49dd94d40a00c1d2 GIT binary patch literal 922 zcma)(-)a*<7{y1eRu`<$3wt9nB9;c3bT(;e*=B41*lM7`&?Zv6n03+ZTa2&KVil77f-~f0R+=D96Iru?@uYyCM_3X$`9Qify zI?f+}!{Br9Jop;K2yzy(eRd5(IPU$9&HTPj!`S``Y`b`oV+MF(MvEp!9TY6*_3 z_iAiWNnwy;w}p`&EpWHU_!Q&Q)Gc_Eg`0eea|GF{d=lDef5{cSqTA1{2yIwRiC9O% zB+8=;LWdDu+vqmJn3fvW<#o@^1#JP-Oxm-~iim@*2qddX(e@}Ut=8wFFrTPk)~S>$ zOUM7j&2wili4EQlt3B)B!bUzd$PPzeVnuRc_UTvwq>{qmn;i z%U%aBxtwlp_FItQIrO;bDdc9Hb#_O_QuW_XHIXfRNH;&3pKwU}K7E(&lk8RcF?*5j zXFKU0&9>6RbQk+U`iYK}tGWM3)#r00dx@@Xl5O>-ZDZfXfb0XB_HA3IAIQnW JZ0D#O#Si>o5={UA literal 0 HcmV?d00001 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.po b/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..b2cd2c2 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.po @@ -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 , 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 \n" +"Language-Team: \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)" diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/management/__init__.py b/thesisenv/lib/python3.6/site-packages/hitcount/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/management/commands/__init__.py b/thesisenv/lib/python3.6/site-packages/hitcount/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/management/commands/hitcount_cleanup.py b/thesisenv/lib/python3.6/site-packages/hitcount/management/commands/hitcount_cleanup.py new file mode 100644 index 0000000..1d3dd1f --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/management/commands/hitcount_cleanup.py @@ -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) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/managers.py b/thesisenv/lib/python3.6/site-packages/hitcount/managers.py new file mode 100644 index 0000000..9ac7e9c --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/managers.py @@ -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) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0001_initial.py b/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0001_initial.py new file mode 100644 index 0000000..e6964c8 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0001_initial.py @@ -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, + ), + ] diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0002_index_ip_and_session.py b/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0002_index_ip_and_session.py new file mode 100644 index 0000000..a568b2a --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/migrations/0002_index_ip_and_session.py @@ -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'), + ), + ] diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/migrations/__init__.py b/thesisenv/lib/python3.6/site-packages/hitcount/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/models.py b/thesisenv/lib/python3.6/site-packages/hitcount/models.py new file mode 100644 index 0000000..f58b8d0 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/models.py @@ -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 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/signals.py b/thesisenv/lib/python3.6/site-packages/hitcount/signals.py new file mode 100644 index 0000000..8457777 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/signals.py @@ -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']) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/hitcount-jquery.js b/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/hitcount-jquery.js new file mode 100644 index 0000000..e7106b3 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/hitcount-jquery.js @@ -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)); +} diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/jquery.postcsrf.js b/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/jquery.postcsrf.js new file mode 100644 index 0000000..911626a --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/static/hitcount/jquery.postcsrf.js @@ -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)); diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/templatetags/__init__.py b/thesisenv/lib/python3.6/site-packages/hitcount/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/templatetags/hitcount_tags.py b/thesisenv/lib/python3.6/site-packages/hitcount/templatetags/hitcount_tags.py new file mode 100644 index 0000000..0cac2f0 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/templatetags/hitcount_tags.py @@ -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 = '" + + 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 = """ + +""" + + JS_TEMPLATE_DEBUG = """ + +""" + + 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) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/urls.py b/thesisenv/lib/python3.6/site-packages/hitcount/urls.py new file mode 100644 index 0000000..9bfadfb --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/urls.py @@ -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'), +] diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/utils.py b/thesisenv/lib/python3.6/site-packages/hitcount/utils.py new file mode 100644 index 0000000..93326a4 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/utils.py @@ -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) diff --git a/thesisenv/lib/python3.6/site-packages/hitcount/views.py b/thesisenv/lib/python3.6/site-packages/hitcount/views.py new file mode 100644 index 0000000..9d1b1e5 --- /dev/null +++ b/thesisenv/lib/python3.6/site-packages/hitcount/views.py @@ -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)