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 0000000..5db3106
Binary files /dev/null and b/thesisenv/lib/python3.6/site-packages/hitcount/locale/ru/LC_MESSAGES/django.mo differ
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)