Merge lp:~widelands-dev/widelands-website/django1_11 into lp:widelands-website

Proposed by kaputtnik
Status: Merged
Merged at revision: 491
Proposed branch: lp:~widelands-dev/widelands-website/django1_11
Merge into: lp:widelands-website
Diff against target: 5509 lines (+472/-3558)
113 files modified
check_input/__init__.py (+0/-1)
django_messages_wl/apps.py (+9/-0)
django_messages_wl/management.py (+18/-0)
django_messages_wl/urls.py (+9/-0)
django_messages_wl/views.py (+27/-0)
djangoratings/fields.py (+2/-2)
djangoratings/management/commands/update_recommendations.py (+3/-3)
djangoratings/templatetags/ratings.py (+4/-4)
local_settings.py.sample (+7/-0)
local_urls.py.sample (+3/-3)
mainpage/templatetags/online_users.py (+0/-32)
mainpage/templatetags/wl_extras.py (+1/-8)
mainpage/urls.py (+11/-7)
mainpage/views.py (+0/-1)
news/feeds.py (+1/-1)
news/models.py (+1/-1)
notification/management/commands/emit_notices.py (+3/-3)
notification/models.py (+22/-24)
notification/views.py (+3/-4)
online_users_middleware.py (+56/-0)
pip_requirements.txt (+4/-4)
pybb/apps.py (+12/-0)
pybb/feeds.py (+1/-1)
pybb/forms.py (+0/-7)
pybb/management/commands/pybb_resave_post.py (+0/-1)
pybb/management/pybb_notifications.py (+2/-7)
pybb/models.py (+1/-1)
pybb/templatetags/pybb_extras.py (+1/-3)
pybb/unread.py (+1/-1)
pybb/urls.py (+1/-1)
pybb/urls.py.orig (+0/-21)
pybb/util.py (+3/-4)
pybb/views.py (+4/-4)
settings.py (+37/-29)
sphinxdoc/models.py (+2/-2)
sphinxdoc/views.py (+3/-4)
static_sitemap.py (+1/-1)
templates/django_messages/compose.html (+1/-1)
templates/django_messages/inbox.html (+1/-1)
templates/django_messages/outbox.html (+1/-1)
templates/django_messages/trash.html (+2/-2)
templates/mainpage/online_users.html (+0/-18)
templates/notification/email_subject.txt (+0/-4)
templates/notification/forum_new_post/full.txt (+2/-1)
templates/notification/forum_new_topic/full.txt (+2/-2)
templates/notification/maps_new_map/full.txt (+2/-1)
templates/notification/messages_received/full.txt (+2/-1)
templates/notification/messages_reply_received/full.txt (+2/-1)
templates/notification/short.txt (+1/-1)
templates/notification/wiki_observed_article_changed/full.txt (+2/-1)
templates/right_boxes.html (+12/-2)
templates/wiki/article_teaser.html (+0/-20)
templates/wlmaps/index.html (+1/-1)
templates/wlmaps/map_detail.html (+1/-1)
threadedcomments/templatetags/gravatar.py (+1/-1)
threadedcomments/templatetags/threadedcommentstags.py (+1/-1)
threadedcomments/tests/__init__.py (+0/-8)
threadedcomments/tests/moderator_tests.py (+0/-403)
threadedcomments/tests/templatetags_tests.py (+0/-561)
threadedcomments/tests/threadedcomments_urls.py (+0/-6)
threadedcomments/tests/views_tests.py (+0/-856)
threadedcomments/views.py (+17/-20)
tracking/AUTHORS (+0/-6)
tracking/CHANGES (+0/-44)
tracking/LICENSE (+0/-19)
tracking/__init__.py (+0/-8)
tracking/admin.py (+0/-5)
tracking/listeners.py (+0/-35)
tracking/locale/de/LC_MESSAGES/django.po (+0/-110)
tracking/media/tracking/js/jquery-1.4.4.min.js (+0/-167)
tracking/middleware.py (+0/-195)
tracking/migrations/0001_initial.py (+0/-65)
tracking/models.py (+0/-133)
tracking/templates/base.html (+0/-21)
tracking/templates/tracking/_active_users.html (+0/-21)
tracking/templates/tracking/_active_users.js (+0/-155)
tracking/templates/tracking/refresh_active_users.js (+0/-26)
tracking/templates/tracking/visitor_map.html (+0/-24)
tracking/templatetags/tracking_tags.py (+0/-53)
tracking/urls.py (+0/-16)
tracking/utils.py (+0/-74)
tracking/views.py (+0/-115)
urls.py (+6/-11)
wiki/__init__.py (+0/-3)
wiki/apps.py (+12/-0)
wiki/management.py (+2/-6)
wiki/models.py (+1/-1)
wiki/static_urls.py (+0/-10)
wiki/templatetags/switchcase.py (+4/-4)
wiki/templatetags/wiki_extras.py (+0/-15)
wiki/views.py (+18/-25)
wl_utils.py (+7/-15)
wlggz/views.py (+4/-6)
wlimages/tests.py (+1/-1)
wlimages/views.py (+4/-5)
wlmaps/apps.py (+13/-0)
wlmaps/management.py (+3/-8)
wlmaps/models.py (+2/-2)
wlmaps/tests/test_views.py (+1/-1)
wlmaps/views.py (+10/-15)
wlpoll/models.py (+2/-2)
wlpoll/views.py (+2/-2)
wlprofile/management/commands/profile_fetch_gravatars.py (+0/-1)
wlprofile/templatetags/custom_date.py (+2/-2)
wlprofile/templatetags/wlprofile_extras.py (+1/-1)
wlprofile/views.py (+6/-9)
wlscreens/migrations/0001_initial.py (+41/-0)
wlscreens/models.py (+17/-10)
wlscreens/tests/test_views.py (+1/-1)
wlscreens/views.py (+4/-5)
wlsearch/urls.py (+1/-1)
wlsearch/views.py (+1/-1)
wlwebchat/views.py (+2/-3)
To merge this branch: bzr merge lp:~widelands-dev/widelands-website/django1_11
Reviewer Review Type Date Requested Status
kaputtnik (community) Needs Fixing
GunChleoc Approve
SirVer Approve
Review via email: mp+343065@code.launchpad.net

Commit message

Updated to fit Django 1.11.12

Description of the change

Major update of the website code to Django 1.11.12. I have gone through all release notes form Django 1.8 until Django 1.11.

- Added migrations for wlscreens
- Fixed creation of noticetypes by converting signals post_syncdb to post_migrate. This is done by applying some files called 'apps.py'. This makes it also possible to have our own notification types for django_messages.
- Replaced linaro-django-pagination (outdated, not maintained) with dj-pagination
- Removed using of context_instance (deprecated since Django 1.8)
- Added 'options' for setting DATABASES when using mysql version > 5.6
- updated third party app django-registration
- Replaced setting MIDDLE_CLASSES with MIDDLEWARE
- Added setting PASSWORD_HASHERS which is needed because some old password hashers are removed. Doing so old password stored with an old hasher will be converted automatically.
- removed some unused code
- moved mainpage urls from urls.py to mainpage/urls.py
- Removed app 'tracking' and replaced it with a single middleware, including some own modifications
- Fixes getting a list of usernames for JavaScript. This may need a review from a security perspective.

How to get this in:

Be sure there are no mails awaiting for sending. Just call

./manage.py emit_notices

before the update.

1. activate the virtualenvironment
2. shut down the website (and activate the maintenance view)
3. make a backup of the database
4. run 'pip install -U pip setuptools'
5. merge this branch
6. There will be a conflict: Delete the folder 'tracking' and 'resolve --all'
7. delete remaining '*.pyc' files
8. run 'pip install -R pip_requirements.txt'
9. Modify local_settings.py and add the DATABASE 'options:' like in local_settings.py.sample
10. run './manage.py migrate --fake-initial' (faking initial because of wlscreens)
11. deactivate maintenance view and start the website again
12. pray

If all is working:
13. Commit the changes and push
14. optionally drop the database tables related to tracking called: tracking_bannedip, tracking_untrackeduseragent, tracking_visitor

To post a comment you must log in.
511. By kaputtnik

forgotten autoesacpes on notification templates; comments and cleanups

512. By kaputtnik

clarified a comment; fixed two more failures

513. By kaputtnik

removed empty bottom lines in email templates

514. By kaputtnik

updated django-haystack

Revision history for this message
kaputtnik (franku) wrote :

Possible code reviews:

Providing usernames for JS when writing PMs: This is maybe a security risk because a username can contain an at sign (@). The Django documentation says:

"If additional escaping is not desired, you will need to use mark_safe() if you are absolutely sure that your code does not contain XSS vulnerabilities."

I can't evaluate the security risk here. Code: https://bazaar.launchpad.net/~widelands-dev/widelands-website/django1_11/revision/509#mainpage/templatetags/wl_extras.py

RegEx urls: Please check the regexes for urls.py and mainpage/urls.py: https://bazaar.launchpad.net/~widelands-dev/widelands-website/django1_11/revision/503

Password hashers: I am not sure if we need all default hashers. As far i understand the first in PASSWORDHASHERS (so PBKDF2) is used by default. Explanantion: https://docs.djangoproject.com/en/1.11/topics/auth/passwords/#auth-password-storage
Code: https://bazaar.launchpad.net/~widelands-dev/widelands-website/django1_11/revision/500#settings.py

Replacing lambdas with callables: Django can't serialize lambdas for migrations. For the screens app i have replaced the lambdas with callables: https://bazaar.launchpad.net/~widelands-dev/widelands-website/django1_11/revision/494#wlscreens/views.py

For the other things i just followed the recommendations by django, e.g. the additional database options.

Revision history for this message
kaputtnik (franku) wrote :

Here instructions how to update (just in case i can't do that, and of course also as a reminder for myself ;) ):

1. activate the virtualenvironment
2. shut down the website (and activate the maintenance view)
3. make a backup of the database
4. run 'pip install -U pip setuptools'
5. merge this branch
6. There will be a conflict: Remove the folder 'tracking' and 'resolve --all'
7. delete remaining '*.pyc' files
8. run './manage.py migrate --fake-initial' (faking initial because of wlscreens)
9. deactivate maintenance view and start the website again
10. pray

If all is working:
11. Commit the changes and push
12. optionally drop the database tables related to tracking called: tracking_bannedip, tracking_untrackeduseragent, tracking_visitor

Revision history for this message
SirVer (sirver) wrote :

Thanks for this massive update and keeping our homepage alive and kicking!

Drive by comment, only refering to the questions you've asked. I did not review the rest of the change - it seems massive.

> Providing usernames for JS when writing PMs: This is maybe a security risk because a username can contain an at sign (@). The Django documentation says:

Are we sure usernames can only contain the following characters? @A-Za-z0-9 If yes, than that function is indeed safe. If they can contain other characters (."'/\) we will be vulnerable.

> RegEx urls

Seems correct to me.

> PBKDF2

This is as sufficiently good hasher for us.

> Replacing lambdas with callables: Django can't serialize lambdas for migrations. For the screens app i have replaced the lambdas with callables: https://bazaar.launchpad.net/~widelands-dev/widelands-website/django1_11/revision/494#wlscreens/views.py

For this I do not know. If it works in your tests, that is probably all fine.

review: Approve
Revision history for this message
kaputtnik (franku) wrote :

Thanks, SirVer. Nice to hear from you :-)

I assume you logged in on the alpha site, and this worked so far?

I will shut down alpha tomorrow, in the morning.

Revision history for this message
kaputtnik (franku) wrote :

Forgotten to answer:

> If they can contain other characters (."'/\) we will be vulnerable.

This differs on the used python version. Django says:

"A field validator allowing only ASCII letters and numbers, in addition to @, ., +, -, and _. The default validator for User.username on Python 2."

See: https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.models.User

So a username can contain "." and "@". Should we drop this feature then? As mentioned in the code, this is only used to provide a list for the JQuery.UI.autocomplete widget, to give a user the possibility to search for a user by giving at least three characters when writing PMs. See: http://api.jqueryui.com/autocomplete/#option-source

Revision history for this message
GunChleoc (gunchleoc) wrote :

*waves to SirVer*

Can we disallow usernames with "." or "@" in them? Allowing ' would be good, since that character is used in real-world names.

I had a look at the diff as far as Launchpad will display it. Just 1 potential nit.

Since the alpha site is already switched off, I haven't done any testing.

> 14. optionally drop the database tables related to tracking called: tracking_bannedip, tracking_untrackeduseragent, tracking_visitor

We should definitely do this once we're sure that everything's OK.

review: Approve
Revision history for this message
kaputtnik (franku) wrote :

Allowing ' needs implementing an own username validator. Can you give an example for real world name containing an apostrophe?

What we should consider is to allow unicode characters in usernames, so french, or polish names can be used.See https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.validators.UnicodeUsernameValidator

For the username widget (when composing messages) i would assume it is safe, because the labels are pure text, not html. See the link for 'autocomplete' postet before. Nevertheless i am not satisfied with the current solution, because all usernames are shown in the code and everyone can grab them from his browser. Better would be to use a callback function, but i was not able to implement such a solution so far :-(

I would be glad if you can test some stuff on the alpha site, so i switched it in on again.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> Can you give an example for real world name containing an apostrophe?

John O'Neill, Iain Mac a' Ghobhainn

+1 for Unicode names, that would be really cool :)

I don't think that we need to resolve the username issue in this branch though.

I'll try to get some testing in this week.

515. By kaputtnik

autocompletion uses now a callback function to prevend having all users in the users browsers

Revision history for this message
kaputtnik (franku) wrote :

Got the callback function to work. Will apply the latest change to the alpha site, so this can also be tested in production. Have to shut down and restart the alpha site again...

Revision history for this message
kaputtnik (franku) wrote :

Regarding the screenshots: They old ones aren't physical there. I have tested it by adding two categories and uploaded two images to each categorie.

The online_users_middleware does not work as i expected... Hitting 'log out' does not remove the user name from the list. But in general it works.

I have to investigate (again) the thing with the missing subject in emails.

review: Needs Fixing
516. By kaputtnik

fixed subject for emails; fixed missing autoescape tag

517. By kaputtnik

better solution

Revision history for this message
kaputtnik (franku) wrote :

As far as i can see the Email subjects are solved.

I will announce the website downtime and schedule the update for the next week.

@GunChleoc: Regarding your permissions: On alpha you are set as superuser, whereas on wl.widelands you are not a superuser. Thats the difference regarding the shown buttons in the forum. Some time ago i asked you to get superuser status, but you said you want it not to have a developer with normal rights.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Yes, all emails look fine. I also tested image uploading.

The scheduler is producing an error.

I think it would be good for me to have access to moderator functions on the forums. Otherwise, you'll be the only one who can o it, since SirVer doesn't have the time any more.

Revision history for this message
kaputtnik (franku) wrote :

> The scheduler is producing an error.

I got the errors... My fault :-(. When switching the database i had forgotten to run the migrate command.

Will try to give you moderator permissions this weekend.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Shit happens. Let me know if/when you want me to do some schedule testing.

Revision history for this message
kaputtnik (franku) wrote :

alpha is shut down again.

Revision history for this message
kaputtnik (franku) wrote :

The update is made on the productive website. I've encountered some problems in the database:

When applying the migrations i got some foreign constraints error:

django.db.utils.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`wl_django`.`#sql-2d6_4516`, CONSTRAINT
                                                                                                          `notification_noticesetting_user_id_4fab85e6_fk` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`))')

After searching the web i found the problem: The table notification_noticesetting had rows with a 'user_id' which does not exist in auth_user (probably deleted user). The solution was to find the rows in notification_noticesetting which have no corresponding user in auth_user and delete those rows (16 rows). I've checked if the users really doesn't exist in auth_user before deleting the rows.

Same problem appeared for the table wlprofile_profile (one row).

After deleting such rows the migrate command runs through (puhhh).

SirVer: Any remarks?

The changes are not commited so far and the tracking related tables aren't deleted yet.

Revision history for this message
kaputtnik (franku) wrote :

Oh, there are some warnings about unordered lists:

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'wiki.models.Article'> QuerySet.

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'wlhelp.models.Tribe'> QuerySet.

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'wlhelp.models.Building'> QuerySet.

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'wlhelp.models.Worker'> QuerySet.

Didn't get them locally... have to look at this.

Revision history for this message
kaputtnik (franku) wrote :

I think it works in general ... the changes are committed now. I think it is better to have some fixes in a follow up branch.

The last server errors were produced by check_input, and were not related to this branch. I will fix it soon.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Agreed, let's have a follow-up branch.

As to the UnorderedObjectListWarning, the sorting probably isn't stable when adding pagination. Is there an OrderedObjectList or something like that that we can use instead? Should make the warning go away in theory.

Revision history for this message
kaputtnik (franku) wrote :

The UnorderedObjectListWarning can be fixed by giving the models a default ordering. Not really complicated :-)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'check_input/__init__.py'
2--- check_input/__init__.py 2017-11-24 11:11:43 +0000
3+++ check_input/__init__.py 2018-04-19 17:49:06 +0000
4@@ -1,1 +0,0 @@
5-default_app_config = 'check_input.apps.CheckInput'
6
7=== added directory 'django_messages_wl'
8=== added file 'django_messages_wl/__init__.py'
9=== added file 'django_messages_wl/apps.py'
10--- django_messages_wl/apps.py 1970-01-01 00:00:00 +0000
11+++ django_messages_wl/apps.py 2018-04-19 17:49:06 +0000
12@@ -0,0 +1,9 @@
13+from django_messages.apps import DjangoMessagesConfig
14+from django.db.models import signals
15+
16+
17+class WLDjangoMessagesConfig(DjangoMessagesConfig):
18+
19+ def ready(self):
20+ from django_messages_wl.management import create_notice_types
21+ signals.post_migrate.connect(create_notice_types, sender=self)
22\ No newline at end of file
23
24=== added file 'django_messages_wl/management.py'
25--- django_messages_wl/management.py 1970-01-01 00:00:00 +0000
26+++ django_messages_wl/management.py 2018-04-19 17:49:06 +0000
27@@ -0,0 +1,18 @@
28+#!/usr/bin/python
29+# -*- coding: utf-8 -*-
30+
31+from django.conf import settings
32+from django.utils.translation import ugettext_lazy as _
33+
34+
35+if 'notification' in settings.INSTALLED_APPS and getattr(settings, 'DJANGO_MESSAGES_NOTIFY', True):
36+ from notification import models as notification
37+
38+ def create_notice_types(sender, **kwargs):
39+ print('Creating wl specific noticetypes for django-messages ...')
40+ notification.create_notice_type('messages_received', _(
41+ 'Message Received'), _('you have received a message'), default=2)
42+ notification.create_notice_type('messages_reply_received', _(
43+ 'Reply Received'), _('you have received a reply to a message'), default=2)
44+else:
45+ print('Skipping creation of NoticeTypes as notification app not found')
46
47=== added file 'django_messages_wl/urls.py'
48--- django_messages_wl/urls.py 1970-01-01 00:00:00 +0000
49+++ django_messages_wl/urls.py 2018-04-19 17:49:06 +0000
50@@ -0,0 +1,9 @@
51+from django.conf.urls import *
52+from django_messages.urls import urlpatterns
53+from . import views
54+
55+
56+urlpatterns += [
57+ url(r'^django_messages_wl/get_usernames/', views.get_usernames),
58+]
59+
60
61=== added file 'django_messages_wl/views.py'
62--- django_messages_wl/views.py 1970-01-01 00:00:00 +0000
63+++ django_messages_wl/views.py 2018-04-19 17:49:06 +0000
64@@ -0,0 +1,27 @@
65+from django.contrib.auth.models import User
66+from django.http import HttpResponse
67+import json
68+
69+
70+def get_usernames(request):
71+ """AJAX Callback for JS autocomplete.
72+
73+ This is used for autocompletion of usernames when writing PMs.
74+ The path.name of this function has to be used in each place:
75+ 1. Argument of source of the JS widget
76+ 2. urls.py
77+
78+ """
79+ if request.is_ajax():
80+ q = request.GET.get('term', '')
81+
82+ usernames = User.objects.filter(username__icontains=q)
83+ results = []
84+ for user in usernames:
85+ name_json = {'value': user.username}
86+ results.append(name_json)
87+ data = json.dumps(results)
88+ else:
89+ data = 'fail'
90+ mimetype = 'application/json'
91+ return HttpResponse(data, mimetype)
92
93=== modified file 'djangoratings/fields.py'
94--- djangoratings/fields.py 2016-12-13 18:28:51 +0000
95+++ djangoratings/fields.py 2018-04-19 17:49:06 +0000
96@@ -115,7 +115,7 @@
97 key=self.field.key,
98 )
99
100- if not (user and user.is_authenticated()):
101+ if not (user and user.is_authenticated):
102 if not ip_address:
103 raise ValueError('``user`` or ``ip_address`` must be present.')
104 kwargs['user__isnull'] = True
105@@ -169,7 +169,7 @@
106 raise InvalidRating('%s is not a valid choice for %s' %
107 (score, self.field.name))
108
109- is_anonymous = (user is None or not user.is_authenticated())
110+ is_anonymous = (user is None or not user.is_authenticated)
111 if is_anonymous and not self.field.allow_anonymous:
112 raise AuthRequired("user must be a user, not '%r'" % (user,))
113
114
115=== modified file 'djangoratings/management/commands/update_recommendations.py'
116--- djangoratings/management/commands/update_recommendations.py 2016-12-13 18:28:51 +0000
117+++ djangoratings/management/commands/update_recommendations.py 2018-04-19 17:49:06 +0000
118@@ -1,9 +1,9 @@
119-from django.core.management.base import NoArgsCommand, CommandError
120+from django.core.management.base import BaseCommand, CommandError
121
122 from djangoratings.models import SimilarUser
123
124
125-class Command(NoArgsCommand):
126+class Command(BaseCommand):
127
128- def handle_noargs(self, **options):
129+ def handle(self, *args, **options):
130 SimilarUser.objects.update_recommendations()
131
132=== modified file 'djangoratings/templatetags/ratings.py'
133--- djangoratings/templatetags/ratings.py 2016-12-13 18:28:51 +0000
134+++ djangoratings/templatetags/ratings.py 2018-04-19 17:49:06 +0000
135@@ -20,8 +20,8 @@
136
137 def render(self, context):
138 try:
139- request = template.resolve_variable(self.request, context)
140- obj = template.resolve_variable(self.obj, context)
141+ request = django.template.Variable(self.request).resolve(context)
142+ obj = django.template.Variable(self.obj).resolve(context)
143 field = getattr(obj, self.field_name)
144 except (template.VariableDoesNotExist, AttributeError):
145 return ''
146@@ -63,8 +63,8 @@
147
148 def render(self, context):
149 try:
150- user = template.resolve_variable(self.request, context)
151- obj = template.resolve_variable(self.obj, context)
152+ user = django.template.Variable(self.request).resolve(context)
153+ obj = django.template.Variable(self.obj).resolve(context)
154 field = getattr(obj, self.field_name)
155 except template.VariableDoesNotExist:
156 return ''
157
158=== modified file 'local_settings.py.sample'
159--- local_settings.py.sample 2017-11-25 11:11:17 +0000
160+++ local_settings.py.sample 2018-04-19 17:49:06 +0000
161@@ -27,6 +27,13 @@
162 'PASSWORD': '', # Not used with sqlite3.
163 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
164 'PORT': '', # Set to empty string for default. Not used with sqlite3.
165+ # Next is only used for mysql. Explanations:
166+ # https://docs.djangoproject.com/en/1.11/ref/databases/#connecting-to-the-database
167+ # 'init_command': is recommended for MySQL >= 5.6
168+ # 'OPTIONS': {
169+ # 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
170+ # 'isolation_level': 'read committed',
171+ #},
172 }
173 }
174
175
176=== modified file 'local_urls.py.sample'
177--- local_urls.py.sample 2016-06-04 14:17:40 +0000
178+++ local_urls.py.sample 2018-04-19 17:49:06 +0000
179@@ -1,14 +1,14 @@
180 from django.conf.urls import *
181 from django.conf import settings
182-
183+from django.views.static import serve
184
185 local_urlpatterns = [
186 url(r'^wlmedia/(?P<path>.*)$',
187- 'django.views.static.serve',
188+ serve,
189 {'document_root': settings.STATIC_MEDIA_PATH},
190 name='static_media'),
191 url(r'^media/(?P<path>.*)$',
192- 'django.views.static.serve',
193+ serve,
194 {'document_root': settings.STATIC_MEDIA_PATH},
195 name='static_media_pybb'),
196 ]
197
198=== removed file 'mainpage/templatetags/online_users.py'
199--- mainpage/templatetags/online_users.py 2016-12-13 18:28:51 +0000
200+++ mainpage/templatetags/online_users.py 1970-01-01 00:00:00 +0000
201@@ -1,32 +0,0 @@
202-#!/usr/bin/env python -tt
203-# encoding: utf-8
204-#
205-# File: online_user.py
206-#
207-# Created by Holger Rapp on 2009-02-19.
208-# Copyright (c) 2009 HolgerRapp@gmx.net. All rights reserved.
209-#
210-# Last Modified: 2009-02-20 22:37:16
211-#
212-
213-from django import template
214-from django.contrib.auth.models import User
215-from django.db.models import Count
216-import datetime
217-from tracking.models import Visitor
218-
219-register = template.Library()
220-
221-
222-@register.inclusion_tag('mainpage/online_users.html')
223-def online_users(num):
224- """Show user that has been login an hour ago."""
225- users = [l.user for l in Visitor.objects.active().exclude(user=None)]
226-
227- # There might still be duplicates, so we make a set and order it into
228- # a list again
229- users = sorted(list(set(users)), key=lambda user: user.username)
230-
231- return {
232- 'users': users,
233- }
234
235=== modified file 'mainpage/templatetags/wl_extras.py'
236--- mainpage/templatetags/wl_extras.py 2018-02-17 11:22:19 +0000
237+++ mainpage/templatetags/wl_extras.py 2018-04-19 17:49:06 +0000
238@@ -2,6 +2,7 @@
239 # encoding: utf-8
240
241 from django import template
242+from django.utils.safestring import mark_safe
243
244 register = template.Library()
245
246@@ -22,14 +23,6 @@
247 return settings.LOGO_FILE
248
249
250-@register.simple_tag
251-def all_users():
252- """Provide a list of all users."""
253-
254- from django.contrib.auth.models import User
255- return [str(u.username) for u in User.objects.all()]
256-
257-
258 @register.inclusion_tag('mainpage/forum_navigation.html')
259 def forum_navigation():
260 """Makes the forum list available to the navigation.
261
262=== modified file 'mainpage/urls.py'
263--- mainpage/urls.py 2016-12-13 18:28:51 +0000
264+++ mainpage/urls.py 2018-04-19 17:49:06 +0000
265@@ -1,8 +1,12 @@
266 from django.conf.urls import *
267-from widelands.mainpage import views
268-
269-urlpatterns = patterns('',
270- # Example:
271- url(r'^$', views.mainpage, name='mainpage'),
272-
273- )
274+from mainpage import views
275+
276+urlpatterns = [
277+ url(r'^$', views.mainpage, name='mainpage'),
278+ url(r'^locale/$', views.view_locale),
279+ url(r'^changelog/$', views.changelog, name='changelog'),
280+ url(r'^developers/$', views.developers, name='developers'),
281+ url(r'^legal_notice/$', views.legal_notice, name='legal_notice'),
282+ url(r'^legal_notice_thanks/$', views.legal_notice_thanks,
283+ name='legal_notice_thanks'),
284+]
285
286=== modified file 'mainpage/views.py'
287--- mainpage/views.py 2018-02-11 13:50:52 +0000
288+++ mainpage/views.py 2018-04-19 17:49:06 +0000
289@@ -1,4 +1,3 @@
290-from django.template import RequestContext
291 from settings import WIDELANDS_SVN_DIR, INQUIRY_RECIPIENTS
292 from templatetags.wl_markdown import do_wl_markdown
293 from operator import itemgetter
294
295=== modified file 'news/feeds.py'
296--- news/feeds.py 2016-12-13 18:28:51 +0000
297+++ news/feeds.py 2018-04-19 17:49:06 +0000
298@@ -1,6 +1,6 @@
299 from django.contrib.syndication.views import Feed, FeedDoesNotExist
300 from django.core.exceptions import ObjectDoesNotExist
301-from django.core.urlresolvers import reverse
302+from django.urls import reverse
303 from news.models import Post, Category
304
305 # Validated through http://validator.w3.org/feed/
306
307=== modified file 'news/models.py'
308--- news/models.py 2017-09-12 18:04:34 +0000
309+++ news/models.py 2018-04-19 17:49:06 +0000
310@@ -4,7 +4,7 @@
311 from django.contrib.auth.models import User
312 from tagging.fields import TagField
313 from news.managers import PublicManager
314-from django.core.urlresolvers import reverse
315+from django.urls import reverse
316 import datetime
317 import settings
318 import tagging
319
320=== modified file 'notification/management/commands/emit_notices.py'
321--- notification/management/commands/emit_notices.py 2017-04-20 20:22:28 +0000
322+++ notification/management/commands/emit_notices.py 2018-04-19 17:49:06 +0000
323@@ -1,15 +1,15 @@
324
325 import logging
326
327-from django.core.management.base import NoArgsCommand
328+from django.core.management.base import BaseCommand
329
330 from notification.engine import send_all
331
332
333-class Command(NoArgsCommand):
334+class Command(BaseCommand):
335 help = 'Emit queued notices.'
336
337- def handle_noargs(self, **options):
338+ def handle(self, *args, **options):
339 # Franku: Uncomment for debugging purposes
340 # logging.basicConfig(level=logging.DEBUG, format='%(message)s')
341 logging.info('-' * 72)
342
343=== modified file 'notification/models.py'
344--- notification/models.py 2018-02-05 12:32:22 +0000
345+++ notification/models.py 2018-04-19 17:49:06 +0000
346@@ -8,8 +8,7 @@
347 from django.db import models
348 from django.db.models.query import QuerySet
349 from django.conf import settings
350-from django.core.urlresolvers import reverse
351-from django.template import Context
352+from django.urls import reverse
353 from django.template.loader import render_to_string
354
355 from django.core.exceptions import ImproperlyConfigured
356@@ -132,7 +131,7 @@
357 def create_notice_type(label, display, description, default=2, verbosity=1):
358 """Creates a new NoticeType.
359
360- This is intended to be used by other apps as a post_syncdb
361+ This is intended to be used by other apps as a post_migrate
362 manangement step.
363
364 """
365@@ -186,15 +185,14 @@
366
367 """
368 format_templates = {}
369+
370 for format in formats:
371- # conditionally turn off autoescaping for .txt extensions in format
372- if format.endswith('.txt'):
373- context.autoescape = False
374- else:
375- context.autoescape = True
376+ # Switch off escaping for .txt templates was done here, but now it
377+ # resides in the templates
378 format_templates[format] = render_to_string((
379 'notification/%s/%s' % (label, format),
380- 'notification/%s' % format), context_instance=context)
381+ 'notification/%s' % format), context)
382+
383 return format_templates
384
385
386@@ -232,8 +230,8 @@
387 current_language = get_language()
388
389 formats = (
390- 'short.txt',
391- 'full.txt',
392+ 'short.txt', # used for subject
393+ 'full.txt', # used for email body
394 ) # TODO make formats configurable
395
396 for user in users:
397@@ -250,22 +248,20 @@
398 activate(language)
399
400 # update context with user specific translations
401- context = Context({
402+ context = {
403 'user': user,
404- 'notices_url': notices_url,
405 'current_site': current_site,
406- 'subject': notice_type.display,
407- 'description': notice_type.description,
408- })
409+ 'subject': notice_type.display
410+ }
411 context.update(extra_context)
412
413- # get prerendered format messages
414+ # get prerendered format messages and subjects
415 messages = get_formatted_messages(formats, label, context)
416-
417- # Strip newlines from subject
418- subject = ''.join(render_to_string('notification/email_subject.txt', {
419- 'message': messages['short.txt'],
420- }, context).splitlines())
421+
422+ # Create the subject
423+ # Use 'email_subject.txt' to add Strings in every emails subject
424+ subject = render_to_string('notification/email_subject.txt',
425+ {'message': messages['short.txt'],}).replace('\n', '')
426
427 # Strip leading newlines. Make writing the email templates easier:
428 # Each linebreak in the templates results in a linebreak in the emails
429@@ -273,12 +269,14 @@
430 # email will contain an empty line at the top.
431 body = render_to_string('notification/email_body.txt', {
432 'message': messages['full.txt'],
433- }, context).lstrip()
434+ 'notices_url': notices_url,
435+ }).lstrip()
436
437 if should_send(user, notice_type, '1') and user.email: # Email
438 recipients.append(user.email)
439+
440 send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, recipients)
441-
442+
443 # reset environment to original language
444 activate(current_language)
445 except NoticeType.DoesNotExist:
446
447=== modified file 'notification/views.py'
448--- notification/views.py 2017-05-03 19:12:49 +0000
449+++ notification/views.py 2018-04-19 17:49:06 +0000
450@@ -1,5 +1,4 @@
451-from django.shortcuts import render_to_response
452-from django.template import RequestContext
453+from django.shortcuts import render
454 from django.contrib.auth.decorators import login_required
455 from collections import OrderedDict
456 from notification.models import *
457@@ -28,7 +27,7 @@
458
459 app_tables[app].append({'notice_type': notice_type, 'html_values': checkbox_values})
460
461- return render_to_response('notification/notice_settings.html', {
462+ return render(request, 'notification/notice_settings.html', {
463 'column_headers': [medium_display for medium_id, medium_display in NOTICE_MEDIA],
464 'app_tables': OrderedDict(sorted(app_tables.items(), key=lambda t: t[0]))
465- }, context_instance=RequestContext(request))
466+ })
467
468=== added file 'online_users_middleware.py'
469--- online_users_middleware.py 1970-01-01 00:00:00 +0000
470+++ online_users_middleware.py 2018-04-19 17:49:06 +0000
471@@ -0,0 +1,56 @@
472+from django.conf import settings
473+from django.core.cache import cache
474+from django.contrib.auth.models import User
475+from django.utils.deprecation import MiddlewareMixin
476+from django.contrib.auth import user_logged_out
477+from django.dispatch import receiver
478+
479+ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 60 * 15)
480+ONLINE_MAX = getattr(settings, 'ONLINE_MAX', 50)
481+
482+
483+def get_online_now(self):
484+ return User.objects.filter(id__in=self.online_now_ids or [])
485+
486+
487+@receiver(user_logged_out)
488+def logout(sender, **kwargs):
489+ cache.delete('online-%s' % kwargs['user'].id)
490+
491+
492+class OnlineNowMiddleware(MiddlewareMixin):
493+ """Maintains a list of users who have interacted with the website recently.
494+
495+ Their user IDs are available as ``online_now_ids`` on the request
496+ object, and their corresponding users are available (lazily) as the
497+ ``online_now`` property on the request object.
498+
499+ """
500+
501+ def process_request(self, request):
502+ # First get the index
503+ uids = cache.get('online-now', [])
504+
505+ # Perform the multiget on the individual online uid keys
506+ online_keys = ['online-%s' % (u,) for u in uids]
507+ fresh = cache.get_many(online_keys).keys()
508+ online_now_ids = [int(k.replace('online-', '')) for k in fresh]
509+
510+ # If the user is authenticated, add their id to the list
511+ if request.user.is_authenticated():
512+ uid = request.user.id
513+ # If their uid is already in the list, we want to bump it
514+ # to the top, so we remove the earlier entry.
515+ if uid in online_now_ids:
516+ online_now_ids.remove(uid)
517+ online_now_ids.append(uid)
518+ if len(online_now_ids) > ONLINE_MAX:
519+ del online_now_ids[0]
520+
521+ # Attach our modifications to the request object
522+ request.__class__.online_now_ids = online_now_ids
523+ request.__class__.online_now = property(get_online_now)
524+
525+ # Set the new cache
526+ cache.set('online-%s' % (request.user.pk,), True, ONLINE_THRESHOLD)
527+ cache.set('online-now', online_now_ids, ONLINE_THRESHOLD)
528
529=== modified file 'pip_requirements.txt'
530--- pip_requirements.txt 2018-01-12 19:52:31 +0000
531+++ pip_requirements.txt 2018-04-19 17:49:06 +0000
532@@ -1,15 +1,15 @@
533 # Python requirements for widelands-website at 22.06.2017
534
535 BeautifulSoup==3.2.0
536-Django==1.8
537+Django==1.11.12
538 django-contrib-comments==1.8.0
539-django-haystack==2.6.1
540+django-haystack==2.8.1
541 # django-messages is very old on pypi
542 # Do not install newer versions because our notifications app is affected
543 -e git://github.com/arneb/django-messages.git@2d8dabb755e0b5ace876bde25f45d07c2051ac37#egg=django_messages
544 django-nocaptcha-recaptcha==0.0.19
545--e git://github.com/zyga/django-pagination.git#egg=linaro_django_pagination
546-django-registration==2.2
547+dj-pagination==2.3.2
548+django-registration==2.4.1
549 django-tagging==0.4.5
550 gunicorn==19.7.1
551 Markdown==2.6.8
552
553=== added file 'pybb/apps.py'
554--- pybb/apps.py 1970-01-01 00:00:00 +0000
555+++ pybb/apps.py 2018-04-19 17:49:06 +0000
556@@ -0,0 +1,12 @@
557+from django.apps import AppConfig
558+from django.db.models import signals
559+
560+
561+class PybbConfig(AppConfig):
562+
563+ name = 'pybb'
564+ verbose_name = 'Pybb'
565+
566+ def ready(self):
567+ from pybb.management.pybb_notifications import create_notice_types
568+ signals.post_migrate.connect(create_notice_types, sender=self)
569
570=== modified file 'pybb/feeds.py'
571--- pybb/feeds.py 2016-12-13 18:28:51 +0000
572+++ pybb/feeds.py 2018-04-19 17:49:06 +0000
573@@ -1,5 +1,5 @@
574 from django.contrib.syndication.views import Feed
575-from django.core.urlresolvers import reverse
576+from django.urls import reverse
577 from django.core.exceptions import ObjectDoesNotExist
578 from django.utils.feedgenerator import Atom1Feed
579 from pybb.models import Post, Topic, Forum
580
581=== modified file 'pybb/forms.py'
582--- pybb/forms.py 2017-11-23 16:12:01 +0000
583+++ pybb/forms.py 2018-04-19 17:49:06 +0000
584@@ -28,13 +28,6 @@
585 self.ip = kwargs.pop('ip', None)
586 super(AddPostForm, self).__init__(*args, **kwargs)
587
588- # TODO(Franku): This doesn't work anymore with django 1.8 Use 'field_order'
589- # with django 1.9
590- self.fields.keyOrder = ['name',
591- 'body',
592- 'markup',
593- 'attachment']
594-
595 if self.topic:
596 self.fields['name'].widget = forms.HiddenInput()
597 self.fields['name'].required = False
598
599=== modified file 'pybb/management/commands/pybb_resave_post.py'
600--- pybb/management/commands/pybb_resave_post.py 2016-12-13 18:28:51 +0000
601+++ pybb/management/commands/pybb_resave_post.py 2018-04-19 17:49:06 +0000
602@@ -1,4 +1,3 @@
603-from optparse import make_option
604
605 from django.core.management.base import BaseCommand, CommandError
606 from django.contrib.auth.models import User
607
608=== modified file 'pybb/management/pybb_notifications.py'
609--- pybb/management/pybb_notifications.py 2017-04-29 19:52:28 +0000
610+++ pybb/management/pybb_notifications.py 2018-04-19 17:49:06 +0000
611@@ -1,11 +1,11 @@
612-from django.db.models import signals
613
614 from django.utils.translation import ugettext_noop as _
615
616 try:
617 from notification import models as notification
618
619- def create_notice_types(app, created_models, verbosity, **kwargs):
620+ def create_notice_types(sender, **kwargs):
621+ print("Creating noticetypes for pybb ...")
622 notification.create_notice_type('forum_new_topic',
623 _('Forum New Topic'),
624 _('a new topic has been added to the forum'),
625@@ -13,10 +13,5 @@
626 notification.create_notice_type('forum_new_post',
627 _('Forum New Post'),
628 _('a new comment has been posted to a topic you observe'))
629-
630- # TODO (Franku): post_syncdb is deprecated since Django 1.7
631- # See: https://docs.djangoproject.com/en/1.8/ref/signals/#post-syncdb
632- signals.post_syncdb.connect(create_notice_types,
633- sender=notification)
634 except ImportError:
635 print 'Skipping creation of NoticeTypes as notification app not found'
636
637=== modified file 'pybb/models.py'
638--- pybb/models.py 2017-09-12 18:04:34 +0000
639+++ pybb/models.py 2018-04-19 17:49:06 +0000
640@@ -5,7 +5,7 @@
641
642 from django.db import models
643 from django.contrib.auth.models import User
644-from django.core.urlresolvers import reverse
645+from django.urls import reverse
646 from django.utils.html import strip_tags
647 from django.utils.translation import ugettext_lazy as _
648 from django.conf import settings
649
650=== modified file 'pybb/templatetags/pybb_extras.py'
651--- pybb/templatetags/pybb_extras.py 2017-06-22 06:33:22 +0000
652+++ pybb/templatetags/pybb_extras.py 2018-04-19 17:49:06 +0000
653@@ -5,9 +5,7 @@
654 from pprint import pprint
655
656 from django import template
657-from django.core.urlresolvers import reverse
658 from django.utils.safestring import mark_safe
659-from django.template import RequestContext
660 from django.template.defaultfilters import stringfilter
661 from django.utils.encoding import smart_unicode
662 from django.utils.html import escape
663@@ -132,7 +130,7 @@
664 else:
665 return topic.updated <= read.time
666
667- if not user.is_authenticated():
668+ if not user.is_authenticated:
669 return False
670 else:
671 if isinstance(topic, Topic):
672
673=== modified file 'pybb/unread.py'
674--- pybb/unread.py 2016-12-13 18:28:51 +0000
675+++ pybb/unread.py 2018-04-19 17:49:06 +0000
676@@ -2,7 +2,7 @@
677
678
679 def cache_unreads(qs, user):
680- if not len(qs) or not user.is_authenticated():
681+ if not len(qs) or not user.is_authenticated:
682 return qs
683 if isinstance(qs[0], Topic):
684 reads = Read.objects.filter(topic__pk__in=set(x.id for x in qs),
685
686=== modified file 'pybb/urls.py'
687--- pybb/urls.py 2016-12-13 18:28:51 +0000
688+++ pybb/urls.py 2018-04-19 17:49:06 +0000
689@@ -48,7 +48,7 @@
690 url('^api/post_ajax_preview/$', views.post_ajax_preview,
691 name='pybb_post_ajax_preview'),
692
693- # Subsciption
694+ # Subscription
695 url('^topic/(?P<topic_id>\d+)/subscribe/$',
696 views.add_subscription, name='pybb_add_subscription'),
697 url('^topic/(?P<topic_id>\d+)/unsubscribe/$',
698
699=== removed file 'pybb/urls.py.orig'
700--- pybb/urls.py.orig 2009-02-25 16:55:36 +0000
701+++ pybb/urls.py.orig 1970-01-01 00:00:00 +0000
702@@ -1,21 +0,0 @@
703-from django.conf.urls.defaults import *
704-from django.views.generic.simple import redirect_to
705-from django.conf import settings
706-import django.views.static
707-
708-from django.contrib import admin
709-admin.autodiscover()
710-
711-urlpatterns = patterns('',
712- (r'^$', redirect_to, {'url': '/forum/'}),
713- (r'^admin/(.*)', admin.site.root),
714- (r'', include('account.urls')),
715- # (r'^forum/', include('pybb.urls')),
716- # (r'^forum/', include('forum.urls')),
717-)
718-
719-if (settings.DEBUG):
720- urlpatterns += patterns('',
721- (r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
722- django.views.static.serve, {'document_root': settings.MEDIA_ROOT}),
723- )
724
725=== modified file 'pybb/util.py'
726--- pybb/util.py 2017-08-21 19:13:19 +0000
727+++ pybb/util.py 2018-04-19 17:49:06 +0000
728@@ -5,8 +5,7 @@
729
730 from BeautifulSoup import BeautifulSoup
731 from datetime import datetime
732-from django.shortcuts import render_to_response
733-from django.template import RequestContext
734+from django.shortcuts import render
735 from django.http import HttpResponse
736 from django.utils.functional import Promise
737 from django.utils.translation import check_for_language
738@@ -32,7 +31,7 @@
739 output = func(request, *args, **kwargs)
740 if not isinstance(output, dict):
741 return output
742- kwargs = {'context_instance': RequestContext(request)}
743+
744
745 # TODO(Franku): 'MIME_TYPE' is never in output as i can see for now.
746 # But if, this should maybe 'content_type' instead
747@@ -43,7 +42,7 @@
748 else:
749 template = template_path
750
751- return render_to_response(template, output, **kwargs)
752+ return render(request, template, output)
753 return wrapper
754
755 return decorator
756
757=== modified file 'pybb/views.py'
758--- pybb/views.py 2017-12-10 13:18:47 +0000
759+++ pybb/views.py 2018-04-19 17:49:06 +0000
760@@ -7,7 +7,7 @@
761 from django.contrib.auth.models import User
762 from django.contrib.auth.decorators import login_required
763 from django.conf import settings
764-from django.core.urlresolvers import reverse
765+from django.urls import reverse
766 from django.db import connection
767 from django.utils import translation
768 from django.shortcuts import render
769@@ -88,7 +88,7 @@
770 topic.views += 1
771 topic.save()
772
773- if request.user.is_authenticated():
774+ if request.user.is_authenticated:
775 topic.update_read(request.user)
776
777 if pybb_settings.FREEZE_FIRST_POST:
778@@ -98,13 +98,13 @@
779 last_post = topic.posts.order_by('-created')[0]
780
781 initial = {}
782- if request.user.is_authenticated():
783+ if request.user.is_authenticated:
784 initial = {'markup': 'markdown'}
785 form = AddPostForm(topic=topic, initial=initial)
786
787 moderator = (request.user.is_superuser or
788 request.user in topic.forum.moderators.all())
789- subscribed = (request.user.is_authenticated() and
790+ subscribed = (request.user.is_authenticated and
791 request.user in topic.subscribers.all())
792
793 posts = topic.posts.exclude(hidden=True).select_related()
794
795=== modified file 'settings.py'
796--- settings.py 2017-12-23 09:15:02 +0000
797+++ settings.py 2018-04-19 17:49:06 +0000
798@@ -67,7 +67,7 @@
799 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
800 ]
801
802-INSTALLED_APPS = (
803+INSTALLED_APPS = [
804 'django.contrib.admin',
805 'django.contrib.auth',
806 'django.contrib.contenttypes',
807@@ -78,8 +78,6 @@
808 'django.contrib.humanize',
809 'django.contrib.sitemaps',
810 'nocaptcha_recaptcha',
811- # Thirdparty apps, but need preload
812- 'tracking', # included as wlapp
813
814 # Our own apps
815 'wiki.templatetags.restructuredtext',
816@@ -91,43 +89,42 @@
817 'wlsearch',
818 'wlpoll',
819 'wlevents',
820- 'wlmaps',
821+ 'wlmaps.apps.WlMapsConfig',
822 'wlscreens',
823 'wlggz',
824 'wlscheduling',
825- 'check_input',
826+ 'check_input.apps.CheckInput',
827 'haystack', # search engine; see option HAYSTACK_CONNECTIONS
828
829 # Modified 3rd party apps
830- 'wiki', # This is based on wikiapp, but has some local modifications
831+ 'wiki.apps.WikiConfig', # This is based on wikiapp, but has some local modifications
832 'news', # This is based on simple-blog, but has some local modifications
833 'news.managers',
834- 'pybb', # Feature enriched version of pybb
835+ 'pybb.apps.PybbConfig', # Feature enriched version of pybb
836
837 # Thirdparty apps
838 'threadedcomments', # included as wlapp
839 'notification', # included as wlapp
840- 'django_messages',
841- 'linaro_django_pagination',
842+ 'django_messages_wl.apps.WLDjangoMessagesConfig',
843+ 'dj_pagination',
844 'tagging',
845 'djangoratings', # included as wlapp
846 'sphinxdoc', # included as wlapp
847-)
848+]
849
850-MIDDLEWARE_CLASSES = (
851+MIDDLEWARE = [
852+ 'django.middleware.security.SecurityMiddleware',
853+ 'django.contrib.sessions.middleware.SessionMiddleware',
854 'django.middleware.common.CommonMiddleware',
855- 'django.contrib.sessions.middleware.SessionMiddleware',
856- 'django.contrib.messages.middleware.MessageMiddleware',
857 'django.middleware.csrf.CsrfViewMiddleware',
858 'django.contrib.auth.middleware.AuthenticationMiddleware',
859- 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
860+ 'django.contrib.messages.middleware.MessageMiddleware',
861 'django.middleware.clickjacking.XFrameOptionsMiddleware',
862- 'django.middleware.security.SecurityMiddleware',
863-
864- 'linaro_django_pagination.middleware.PaginationMiddleware',
865- 'tracking.middleware.VisitorTrackingMiddleware',
866- 'tracking.middleware.VisitorCleanUpMiddleware',
867-)
868+
869+ # Foreign middleware
870+ 'dj_pagination.middleware.PaginationMiddleware',
871+ 'online_users_middleware.OnlineNowMiddleware',
872+]
873
874 TEMPLATES = [
875 {
876@@ -161,6 +158,13 @@
877 DEFAULT_FROM_EMAIL = 'noreply@widelands.org'
878 ACCOUNT_ACTIVATION_DAYS = 2 # Days an activation token keeps active
879
880+# Franku: SHA1 Needed as compatibility for old passwords
881+# https://docs.djangoproject.com/en/1.11/releases/1.10/#removed-weak-password-hashers-from-the-default-password-hashers-setting
882+PASSWORD_HASHERS = [
883+ 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
884+ 'django.contrib.auth.hashers.SHA1PasswordHasher'
885+]
886+
887 ######################
888 # Wiki configuration #
889 ######################
890@@ -253,11 +257,6 @@
891 },
892 }
893
894-############
895-# Tracking #
896-############
897-TRACKING_CLEANUP_TIMEOUT = 48
898-
899 ###########################
900 # Widelands SVN directory #
901 ###########################
902@@ -311,12 +310,21 @@
903 BLEACH_ALLOWED_ATTRIBUTES = {'img': ['src', 'alt'], 'a': [
904 'href'], 'td': ['align'], '*': ['class', 'id', 'title']}
905
906-################################
907-# Pagination settings #
908-# for linaro-django-pagination #
909-################################
910+##########################
911+# Pagination settings #
912+# for dj-pagination #
913+##########################
914 PAGINATION_DEFAULT_WINDOW = 2
915
916+###########################
917+# Settings for displaying #
918+# online users #
919+###########################
920+
921+# Time in seconds how long a user will be shown online
922+ONLINE_THRESHOLD = 60 * 30
923+# Number of stored users
924+ONLINE_MAX = 25
925
926 try:
927 from local_settings import *
928
929=== modified file 'sphinxdoc/models.py'
930--- sphinxdoc/models.py 2016-12-13 18:28:51 +0000
931+++ sphinxdoc/models.py 2018-04-19 17:49:06 +0000
932@@ -4,6 +4,7 @@
933 """
934
935 from django.db import models
936+from django.urls import reverse
937
938
939 class App(models.Model):
940@@ -15,9 +16,8 @@
941 def __unicode__(self):
942 return self.name
943
944- @models.permalink
945 def get_absolute_url(self):
946- return ('doc-index', (), {'slug': self.slug})
947+ return reverse('doc-index', kwargs={'slug': self.slug})
948
949 class Meta:
950 app_label = 'sphinxdoc'
951
952=== modified file 'sphinxdoc/views.py'
953--- sphinxdoc/views.py 2016-12-13 18:28:51 +0000
954+++ sphinxdoc/views.py 2018-04-19 17:49:06 +0000
955@@ -4,8 +4,8 @@
956 import os.path
957
958 from django.http import Http404
959-from django.shortcuts import get_object_or_404, render_to_response
960-from django.template import RequestContext
961+from django.shortcuts import get_object_or_404, render
962+#from django.template import RequestContext
963 import json
964 from django.views import static
965
966@@ -52,8 +52,7 @@
967 if 'title' not in data['doc']:
968 data['doc']['title'] = SPECIAL_TITLES[page_name]
969
970- return render_to_response(templates, data,
971- context_instance=RequestContext(request))
972+ return render(request, templates, data)
973
974
975 def search(request, slug):
976
977=== modified file 'static_sitemap.py'
978--- static_sitemap.py 2016-11-22 19:21:00 +0000
979+++ static_sitemap.py 2018-04-19 17:49:06 +0000
980@@ -1,5 +1,5 @@
981 from django.contrib.sitemaps import Sitemap
982-from django.core.urlresolvers import reverse
983+from django.urls import reverse
984
985
986 class StaticViewSitemap(Sitemap):
987
988=== modified file 'templates/django_messages/compose.html'
989--- templates/django_messages/compose.html 2018-02-11 14:48:26 +0000
990+++ templates/django_messages/compose.html 2018-04-19 17:49:06 +0000
991@@ -10,7 +10,7 @@
992 <script>
993 $(function() {
994 $( "#id_recipient" ).autocomplete({
995- source: {% all_users %},
996+ source: '/messages/django_messages_wl/get_usernames/',
997 minLength: 3,
998 });
999 });
1000
1001=== modified file 'templates/django_messages/inbox.html'
1002--- templates/django_messages/inbox.html 2017-11-07 18:04:38 +0000
1003+++ templates/django_messages/inbox.html 2018-04-19 17:49:06 +0000
1004@@ -31,7 +31,7 @@
1005 </td>
1006 <td>{{ message.sent_at|custom_date:user }}</td>
1007 <td>
1008- <a href="{% url 'django_messages.views.delete' message.id %}?next={{ request.path|iriencode }}">
1009+ <a href="{% url 'messages_delete' message.id %}?next={{ request.path|iriencode }}">
1010 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />
1011 </a>
1012 </td>
1013
1014=== modified file 'templates/django_messages/outbox.html'
1015--- templates/django_messages/outbox.html 2017-11-07 18:04:38 +0000
1016+++ templates/django_messages/outbox.html 2018-04-19 17:49:06 +0000
1017@@ -27,7 +27,7 @@
1018 </td>
1019 <td>{{ message.sent_at|custom_date:user }}</td>
1020 <td>
1021- <a href="{% url 'django_messages.views.delete' message.id %}?next={{ request.path|iriencode }}">
1022+ <a href="{% url 'messages_delete' message.id %}?next={{ request.path|iriencode }}">
1023 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />
1024 </a>
1025 </td>
1026
1027=== modified file 'templates/django_messages/trash.html'
1028--- templates/django_messages/trash.html 2017-11-07 18:04:38 +0000
1029+++ templates/django_messages/trash.html 2018-04-19 17:49:06 +0000
1030@@ -34,11 +34,11 @@
1031 <td>{{ message.sent_at|custom_date:user }}</td>
1032 <td>
1033 {% if message.sender == request.user %}
1034- <a href="{% url 'django_messages.views.undelete' message.id %}?next=/messages/outbox/">
1035+ <a href="{% url 'messages_undelete' message.id %}?next=/messages/outbox/">
1036 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />
1037 </a>
1038 {% else %}
1039- <a href="{% url 'django_messages.views.undelete' message.id %}?next=/messages/inbox/">
1040+ <a href="{% url 'messages_undelete' message.id %}?next=/messages/inbox/">
1041 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />
1042 </a>
1043 {% endif %}
1044
1045=== removed file 'templates/mainpage/online_users.html'
1046--- templates/mainpage/online_users.html 2016-06-05 20:58:46 +0000
1047+++ templates/mainpage/online_users.html 1970-01-01 00:00:00 +0000
1048@@ -1,18 +0,0 @@
1049-{% load wlprofile_extras %}
1050-
1051-{% if users %}
1052-<div class="columnModule">
1053- <h3>Currently Online</h3>
1054- <div class="columnModuleBox">
1055- {% if users %}
1056- <ul class="player">
1057- {% for user in users %}
1058- <li><a href="{% url 'profile_view' user %}">{{user.username}}</a></li>
1059- {% endfor %}
1060- </ul>
1061- {% else %}
1062- <p>Currently nobody is online.</p>
1063- {% endif %}
1064- </div>
1065-</div>
1066-{% endif %}
1067
1068=== modified file 'templates/notification/email_subject.txt'
1069--- templates/notification/email_subject.txt 2017-12-07 07:28:38 +0000
1070+++ templates/notification/email_subject.txt 2018-04-19 17:49:06 +0000
1071@@ -1,6 +1,2 @@
1072 {% load i18n %}
1073-{% if message|length > 1 %}
1074 {% blocktrans %}{{ message }}{% endblocktrans %}
1075-{% else %}
1076-{% blocktrans %}{{ subject }}{% endblocktrans %}
1077-{% endif %}
1078
1079=== modified file 'templates/notification/forum_new_post/full.txt'
1080--- templates/notification/forum_new_post/full.txt 2018-02-04 09:31:30 +0000
1081+++ templates/notification/forum_new_post/full.txt 2018-04-19 17:49:06 +0000
1082@@ -1,3 +1,4 @@
1083+{% autoescape off %}
1084 {% load i18n %}A new forum post was added to the topic "{{ topic }}":
1085
1086 "{{ user }}" wrote:
1087@@ -7,4 +8,4 @@
1088 -------------------------
1089 Link to post: http://{{ current_site }}{{ post_url }}
1090 Link to topic: http://{{ current_site }}{{ topic_url }}
1091-{% endblocktrans %}
1092\ No newline at end of file
1093+{% endblocktrans %}{% endautoescape %}
1094\ No newline at end of file
1095
1096=== modified file 'templates/notification/forum_new_topic/full.txt'
1097--- templates/notification/forum_new_topic/full.txt 2018-03-18 10:48:37 +0000
1098+++ templates/notification/forum_new_topic/full.txt 2018-04-19 17:49:06 +0000
1099@@ -1,9 +1,9 @@
1100+{% autoescape off %}
1101 {% load i18n %}{% blocktrans with topic.get_absolute_url as topic_url and post.body as txt %}The Forum topic "{{ topic }}" has been created by {{ user }}.
1102-
1103 {{ user }} wrote:
1104
1105 {{ txt }}
1106
1107 -------------------------
1108 Link to topic: http://{{ current_site }}{{ topic_url }}
1109-{% endblocktrans %}
1110+{% endblocktrans %}{% endautoescape %}
1111\ No newline at end of file
1112
1113=== modified file 'templates/notification/maps_new_map/full.txt'
1114--- templates/notification/maps_new_map/full.txt 2018-02-04 09:31:30 +0000
1115+++ templates/notification/maps_new_map/full.txt 2018-04-19 17:49:06 +0000
1116@@ -1,3 +1,4 @@
1117+{% autoescape off %}
1118 {% load i18n %}A new map has been uploaded to the Website by {{ user }}:
1119 {% blocktrans %}
1120 Mapname: {{ mapname }}
1121@@ -5,4 +6,4 @@
1122
1123 -------------------------
1124 Link to map: http://{{ current_site }}{{ url }}
1125-{% endblocktrans %}
1126+{% endblocktrans %}{% endautoescape %}
1127\ No newline at end of file
1128
1129=== modified file 'templates/notification/messages_received/full.txt'
1130--- templates/notification/messages_received/full.txt 2018-02-04 09:31:30 +0000
1131+++ templates/notification/messages_received/full.txt 2018-04-19 17:49:06 +0000
1132@@ -1,3 +1,4 @@
1133+{% autoescape off %}
1134 {% load i18n %}{% blocktrans with message.sender as message_sender and message.body|safe as message_body and message.get_absolute_url as message_url and message.id as message_id %}{{ message_sender }} has sent you a message:
1135
1136 {{ message }}
1137@@ -6,4 +7,4 @@
1138
1139 -------------------------
1140 Link to Message: http://{{ current_site }}{{ message_url }}
1141-Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/{% endblocktrans %}
1142+Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/{% endblocktrans %}{% endautoescape %}
1143\ No newline at end of file
1144
1145=== modified file 'templates/notification/messages_reply_received/full.txt'
1146--- templates/notification/messages_reply_received/full.txt 2018-02-04 09:31:30 +0000
1147+++ templates/notification/messages_reply_received/full.txt 2018-04-19 17:49:06 +0000
1148@@ -1,3 +1,4 @@
1149+{% autoescape off %}
1150 {% load i18n %}{% blocktrans with message.sender as message_sender and message.parent_msg as message_parent_msg and message.body|safe as message_body and message.get_absolute_url as message_url and message.id as message_id%}{{ message_sender }} replied to '{{ message_parent_msg }}':
1151
1152 {{ message }}
1153@@ -7,4 +8,4 @@
1154 -------------------------
1155 Link to message: http://{{ current_site }}{{ message_url }}
1156 Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/
1157-{% endblocktrans %}
1158+{% endblocktrans %}{% endautoescape %}
1159
1160=== modified file 'templates/notification/short.txt'
1161--- templates/notification/short.txt 2017-12-05 19:18:28 +0000
1162+++ templates/notification/short.txt 2018-04-19 17:49:06 +0000
1163@@ -1,1 +1,1 @@
1164-{% load i18n %}{% blocktrans %}{{ notice }}{% endblocktrans %}
1165+{% load i18n %}{% blocktrans %}{{ subject }}{% endblocktrans %}
1166
1167=== modified file 'templates/notification/wiki_observed_article_changed/full.txt'
1168--- templates/notification/wiki_observed_article_changed/full.txt 2018-02-04 12:31:56 +0000
1169+++ templates/notification/wiki_observed_article_changed/full.txt 2018-04-19 17:49:06 +0000
1170@@ -1,3 +1,4 @@
1171+{% autoescape off %}
1172 {% load i18n %}{% url 'wiki_changeset' article rev as diff_url %}{% url 'wiki_article' article as article_url %}{% blocktrans %}
1173 The article "{{ article }}" that you observe has been edited by {{ editor }}.
1174 Comment for this revision: "{{ rev_comment }}"
1175@@ -5,4 +6,4 @@
1176 -------------------------
1177 A diff is available at: http://{{ current_site }}{{ diff_url }}
1178 Link to article: http://{{ current_site }}{{ article_url }}
1179-{% endblocktrans %}
1180\ No newline at end of file
1181+{% endblocktrans %}{% endautoescape %}
1182\ No newline at end of file
1183
1184=== modified file 'templates/right_boxes.html'
1185--- templates/right_boxes.html 2016-06-05 20:58:46 +0000
1186+++ templates/right_boxes.html 2018-04-19 17:49:06 +0000
1187@@ -10,7 +10,6 @@
1188 {% load i18n %}
1189 {% load wlprofile_extras wlpoll_extras wlevents_extras %}
1190 {% load pybb_extras %}
1191-{% load online_users %}
1192
1193 <!-- Donation Box -->
1194 <div class="columnModule">
1195@@ -109,7 +108,18 @@
1196
1197
1198 <!-- Logged in users -->
1199-{% online_users 10 %}
1200+{% if request.online_now %}
1201+<div class="columnModule">
1202+ <h3>Currently Online</h3>
1203+ <div class="columnModuleBox">
1204+ <ul class="player">
1205+ {% for user in request.online_now|slice:":10" %}
1206+ <li><a href="{% url 'profile_view' user %}">{{ user.username }}</a></li>
1207+ {% endfor %}
1208+ </ul>
1209+ </div>
1210+</div>
1211+{% endif %}
1212
1213 <!-- Latest Post -->
1214 {% pybb_last_posts %}
1215
1216=== removed file 'templates/wiki/article_teaser.html'
1217--- templates/wiki/article_teaser.html 2016-03-07 20:38:39 +0000
1218+++ templates/wiki/article_teaser.html 1970-01-01 00:00:00 +0000
1219@@ -1,20 +0,0 @@
1220-
1221-{% load wiki_extras %}
1222-{% load avatar_tags %}
1223-{% load custom_date %}
1224-
1225-<tr class="{% cycle odd,even %}">
1226- <td class="meta">
1227- <div class="avatar">{% avatar article.latest_changeset.editor 40 %}</div>
1228- <div class="details">
1229- <a href="{% url 'profiles.views.profile' article.latest_changeset.editor.username %}">
1230- {{ article.latest_changeset.editor }}
1231- </a>
1232- </div>
1233- {{ article.last_update|custom_date:user }}
1234- </td>
1235- <td>
1236- <h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
1237- <div class="body">{% render_content article 'summary' %}</div>
1238- </td>
1239-</tr>
1240
1241=== modified file 'templates/wlmaps/index.html'
1242--- templates/wlmaps/index.html 2017-11-12 16:15:08 +0000
1243+++ templates/wlmaps/index.html 2018-04-19 17:49:06 +0000
1244@@ -22,7 +22,7 @@
1245 <table class="maps">
1246 {% for map in object_list %}
1247 <tr class="{% cycle "odd" "even" %}">
1248- <td class="first-column"><a href="{{ map.get_absolute_url }}"><img class="minimap" src="{{ MEDIA_URL }}{{ map.minimap.url }}" alt="{{ map.name }}" /></a></td>
1249+ <td class="first-column"><a href="{{ map.get_absolute_url }}"><img class="minimap" src="{{ MEDIA_URL }}{{ map.minimap }}" alt="{{ map.name }}" /></a></td>
1250 <td>
1251 <h3><a class="invertedColor" href="{{ map.get_absolute_url }}">{{ map.name }}</a></h3>
1252 <table>
1253
1254=== modified file 'templates/wlmaps/map_detail.html'
1255--- templates/wlmaps/map_detail.html 2017-11-12 16:15:08 +0000
1256+++ templates/wlmaps/map_detail.html 2018-04-19 17:49:06 +0000
1257@@ -38,7 +38,7 @@
1258 <div>
1259 <a href="{% url 'wlmaps_index' %}">Maps</a> &#187; {{ map.name }}
1260 </div>
1261- <img class="posLeft map" style="float: left" src="{{ MEDIA_URL }}{{ map.minimap.url }}" alt="{{ map.name }}" />
1262+ <img class="posLeft map" style="float: left" src="{{ MEDIA_URL }}{{ map.minimap }}" alt="{{ map.name }}" />
1263 <div>
1264 <h3>Description:</h3>
1265 <p>{{ map.descr|wl_markdown:"bleachit" }}</p>
1266
1267=== modified file 'threadedcomments/templatetags/gravatar.py'
1268--- threadedcomments/templatetags/gravatar.py 2016-12-13 18:28:51 +0000
1269+++ threadedcomments/templatetags/gravatar.py 2018-04-19 17:49:06 +0000
1270@@ -3,7 +3,7 @@
1271 from django.template.defaultfilters import stringfilter
1272 from django.utils.encoding import smart_str
1273 from django.utils.safestring import mark_safe
1274-from django.utils.hashcompat import md5_constructor
1275+from hashlib import md5 as md5_constructor
1276 import urllib
1277
1278 GRAVATAR_MAX_RATING = getattr(settings, 'GRAVATAR_MAX_RATING', 'R')
1279
1280=== modified file 'threadedcomments/templatetags/threadedcommentstags.py'
1281--- threadedcomments/templatetags/threadedcommentstags.py 2017-01-24 11:53:11 +0000
1282+++ threadedcomments/templatetags/threadedcommentstags.py 2018-04-19 17:49:06 +0000
1283@@ -1,7 +1,7 @@
1284 import re
1285 from django import template
1286 from django.contrib.contenttypes.models import ContentType
1287-from django.core.urlresolvers import reverse
1288+from django.urls import reverse
1289 from django.utils.encoding import force_unicode
1290 from django.utils.safestring import mark_safe
1291 from threadedcomments.models import ThreadedComment, FreeThreadedComment
1292
1293=== removed directory 'threadedcomments/tests'
1294=== removed file 'threadedcomments/tests/__init__.py'
1295--- threadedcomments/tests/__init__.py 2016-05-15 14:41:54 +0000
1296+++ threadedcomments/tests/__init__.py 1970-01-01 00:00:00 +0000
1297@@ -1,8 +0,0 @@
1298-from views_tests import *
1299-from templatetags_tests import *
1300-try:
1301- import comment_utils
1302-except ImportError:
1303- pass
1304-else:
1305- from moderator_tests import *
1306
1307=== removed file 'threadedcomments/tests/moderator_tests.py'
1308--- threadedcomments/tests/moderator_tests.py 2016-12-13 18:28:51 +0000
1309+++ threadedcomments/tests/moderator_tests.py 1970-01-01 00:00:00 +0000
1310@@ -1,403 +0,0 @@
1311-from django.core import mail
1312-from django.test import TestCase
1313-
1314-from django.contrib.auth.models import User
1315-
1316-from threadedcomments.moderation import moderator, CommentModerator
1317-from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
1318-from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
1319-
1320-
1321-__all__ = ('ModeratorTestCase',)
1322-
1323-
1324-class ModeratorTestCase(TestCase):
1325-
1326- def test_threadedcomment(self):
1327- topic = TestModel.objects.create(name='Test')
1328- user = User.objects.create_user(
1329- 'user', 'floguy@gmail.com', password='password')
1330- user2 = User.objects.create_user(
1331- 'user2', 'floguy@gmail.com', password='password')
1332-
1333- comment1 = ThreadedComment.objects.create_for_object(
1334- topic, user=user, ip_address='127.0.0.1',
1335- comment='This is fun! This is very fun!',
1336- )
1337- comment2 = ThreadedComment.objects.create_for_object(
1338- topic, user=user, ip_address='127.0.0.1',
1339- comment='This is stupid! I hate it!',
1340- )
1341- comment3 = ThreadedComment.objects.create_for_object(
1342- topic, user=user, ip_address='127.0.0.1', parent=comment2,
1343- comment='I agree, the first comment was wrong and you are right!',
1344- )
1345- comment4 = ThreadedComment.objects.create_for_object(
1346- topic, user=user, ip_address='127.0.0.1',
1347- comment='What are we talking about?',
1348- )
1349- comment5 = ThreadedComment.objects.create_for_object(
1350- topic, user=user, ip_address='127.0.0.1', parent=comment3,
1351- comment="I'm a fanboy!",
1352- )
1353- comment6 = ThreadedComment.objects.create_for_object(
1354- topic, user=user, ip_address='127.0.0.1', parent=comment1,
1355- comment='What are you talking about?',
1356- )
1357-
1358- class Moderator1(CommentModerator):
1359- enable_field = 'is_public'
1360- auto_close_field = 'date'
1361- close_after = 15
1362- moderator.register(TestModel, Moderator1)
1363-
1364- comment7 = ThreadedComment.objects.create_for_object(
1365- topic, user=user, ip_address='127.0.0.1',
1366- comment='Post moderator addition. Does it still work?',
1367- )
1368-
1369- topic.is_public = False
1370- topic.save()
1371-
1372- comment8 = ThreadedComment.objects.create_for_object(
1373- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1374- comment='This should not appear, due to enable_field',
1375- )
1376-
1377- moderator.unregister(TestModel)
1378-
1379- comment9 = ThreadedComment.objects.create_for_object(
1380- topic, user=user, ip_address='127.0.0.1',
1381- comment='This should appear again, due to unregistration',
1382- )
1383-
1384- self.assertEquals(len(mail.outbox), 0)
1385-
1386- ##################
1387-
1388- class Moderator2(CommentModerator):
1389- enable_field = 'is_public'
1390- auto_close_field = 'date'
1391- close_after = 15
1392- akismet = False
1393- email_notification = True
1394- moderator.register(TestModel, Moderator2)
1395-
1396- comment10 = ThreadedComment.objects.create_for_object(
1397- topic, user=user, ip_address='127.0.0.1',
1398- comment='This should not appear again, due to registration with a new manager.',
1399- )
1400-
1401- topic.is_public = True
1402- topic.save()
1403-
1404- comment11 = ThreadedComment.objects.create_for_object(
1405- topic, user=user, ip_address='127.0.0.1', parent=comment1,
1406- comment='This should appear again.',
1407- )
1408-
1409- self.assertEquals(len(mail.outbox), 1)
1410- mail.outbox = []
1411-
1412- topic.date = topic.date - datetime.timedelta(days=20)
1413- topic.save()
1414-
1415- comment12 = ThreadedComment.objects.create_for_object(
1416- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1417- comment="This shouldn't appear, due to close_after=15.",
1418- )
1419-
1420- topic.date = topic.date + datetime.timedelta(days=20)
1421- topic.save()
1422-
1423- moderator.unregister(TestModel)
1424-
1425- class Moderator3(CommentModerator):
1426- max_comment_length = 10
1427- moderator.register(TestModel, Moderator3)
1428-
1429- comment13 = ThreadedComment.objects.create_for_object(
1430- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1431- comment="This shouldn't appear because it has more than 10 chars.",
1432- )
1433-
1434- comment14 = ThreadedComment.objects.create_for_object(
1435- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1436- comment='<10chars',
1437- )
1438-
1439- moderator.unregister(TestModel)
1440-
1441- class Moderator4(CommentModerator):
1442- allowed_markup = [REST, ]
1443- moderator.register(TestModel, Moderator4)
1444-
1445- comment15 = ThreadedComment.objects.create_for_object(
1446- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1447- comment='INVALID Markup. Should not show up.', markup=TEXTILE
1448- )
1449-
1450- comment16 = ThreadedComment.objects.create_for_object(
1451- topic, user=user, ip_address='127.0.0.1', parent=comment7,
1452- comment='VALID Markup. Should show up.', markup=REST
1453- )
1454-
1455- moderator.unregister(TestModel)
1456-
1457- tree = ThreadedComment.public.get_tree(topic)
1458- output = []
1459- for comment in tree:
1460- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1461- self.assertEquals('\n'.join(output),
1462- """
1463-This is fun! This is very fun!
1464- What are you talking about?
1465- This should appear again.
1466-This is stupid! I hate it!
1467- I agree, the first comment was wrong and you are right!
1468- I'm a fanboy!
1469-What are we talking about?
1470-Post moderator addition. Does it still work?
1471- <10chars
1472- VALID Markup. Should show up.
1473-This should appear again, due to unregistration
1474-""".lstrip())
1475-
1476- tree = ThreadedComment.objects.get_tree(topic)
1477- output = []
1478- for comment in tree:
1479- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1480- self.assertEquals('\n'.join(output),
1481- """
1482-This is fun! This is very fun!
1483- What are you talking about?
1484- This should appear again.
1485-This is stupid! I hate it!
1486- I agree, the first comment was wrong and you are right!
1487- I'm a fanboy!
1488-What are we talking about?
1489-Post moderator addition. Does it still work?
1490- This shouldn't appear because it has more than 10 chars.
1491- <10chars
1492- VALID Markup. Should show up.
1493-This should appear again, due to unregistration
1494-""".lstrip())
1495-
1496- tree = ThreadedComment.objects.get_tree(topic, root=comment2)
1497- output = []
1498- for comment in tree:
1499- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1500- self.assertEquals('\n'.join(output),
1501- """
1502-This is stupid! I hate it!
1503- I agree, the first comment was wrong and you are right!
1504- I'm a fanboy!
1505-""".lstrip())
1506-
1507- tree = ThreadedComment.objects.get_tree(topic, root=comment2.id)
1508- for comment in tree:
1509- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1510- self.assertEquals('\n'.join(output),
1511- """
1512-This is stupid! I hate it!
1513- I agree, the first comment was wrong and you are right!
1514- I'm a fanboy!
1515-""".lstrip())
1516-
1517- def test_freethreadedcomment(self):
1518-
1519- ###########################
1520- ### FreeThreadedComment ###
1521- ###########################
1522-
1523- fcomment1 = FreeThreadedComment.objects.create_for_object(
1524- topic, name='Eric', ip_address='127.0.0.1',
1525- comment='This is fun! This is very fun!',
1526- )
1527- fcomment2 = FreeThreadedComment.objects.create_for_object(
1528- topic, name='Eric', ip_address='127.0.0.1',
1529- comment='This is stupid! I hate it!',
1530- )
1531- fcomment3 = FreeThreadedComment.objects.create_for_object(
1532- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment2,
1533- comment='I agree, the first comment was wrong and you are right!',
1534- )
1535- fcomment4 = FreeThreadedComment.objects.create_for_object(
1536- topic, name='Eric', ip_address='127.0.0.1',
1537- website='http://www.eflorenzano.com/', email='floguy@gmail.com',
1538- comment='What are we talking about?',
1539- )
1540- fcomment5 = FreeThreadedComment.objects.create_for_object(
1541- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment3,
1542- comment="I'm a fanboy!",
1543- )
1544- fcomment6 = FreeThreadedComment.objects.create_for_object(
1545- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment1,
1546- comment='What are you talking about?',
1547- )
1548-
1549- moderator.register(TestModel, Moderator1)
1550-
1551- fcomment7 = FreeThreadedComment.objects.create_for_object(
1552- topic, name='Eric', ip_address='127.0.0.1',
1553- comment='Post moderator addition. Does it still work?',
1554- )
1555-
1556- topic.is_public = False
1557- topic.save()
1558-
1559- fcomment8 = FreeThreadedComment.objects.create_for_object(
1560- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
1561- comment='This should not appear, due to enable_field',
1562- )
1563-
1564- moderator.unregister(TestModel)
1565-
1566- fcomment9 = FreeThreadedComment.objects.create_for_object(
1567- topic, name='Eric', ip_address='127.0.0.1',
1568- comment='This should appear again, due to unregistration',
1569- )
1570-
1571- self.assertEquals(len(mail.outbox), 0)
1572-
1573- moderator.register(TestModel, Moderator2)
1574-
1575- fcomment10 = FreeThreadedComment.objects.create_for_object(
1576- topic, name='Eric', ip_address='127.0.0.1',
1577- comment='This should not appear again, due to registration with a new manager.',
1578- )
1579-
1580- topic.is_public = True
1581- topic.save()
1582-
1583- fcomment11 = FreeThreadedComment.objects.create_for_object(
1584- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment1,
1585- comment='This should appear again.',
1586- )
1587-
1588- self.assertEquals(len(mail.outbox), 1)
1589-
1590- mail.outbox = []
1591-
1592- topic.date = topic.date - datetime.timedelta(days=20)
1593- topic.save()
1594-
1595- fcomment12 = FreeThreadedComment.objects.create_for_object(
1596- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
1597- comment="This shouldn't appear, due to close_after=15.",
1598- )
1599-
1600- topic.date = topic.date + datetime.timedelta(days=20)
1601- topic.save()
1602-
1603- moderator.unregister(TestModel)
1604- moderator.register(TestModel, Moderator3)
1605-
1606- fcomment13 = FreeThreadedComment.objects.create_for_object(
1607- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
1608- comment="This shouldn't appear because it has more than 10 chars.",
1609- )
1610-
1611- fcomment14 = FreeThreadedComment.objects.create_for_object(
1612- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
1613- comment='<10chars',
1614- )
1615-
1616- moderator.unregister(TestModel)
1617-
1618- class Moderator5(CommentModerator):
1619- allowed_markup = [REST, ]
1620- max_depth = 3
1621- moderator.register(TestModel, Moderator5)
1622-
1623- fcomment15 = FreeThreadedComment.objects.create_for_object(
1624- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
1625- comment='INVALID Markup. Should not show up.', markup=TEXTILE
1626- )
1627-
1628- fcomment16 = FreeThreadedComment.objects.create_for_object(
1629- topic, name='Eric', ip_address='127.0.0.1', parent=None,
1630- comment='VALID Markup. Should show up.', markup=REST
1631- )
1632-
1633- fcomment17 = FreeThreadedComment.objects.create_for_object(
1634- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment16,
1635- comment='Building Depth...Should Show Up.', markup=REST
1636- )
1637-
1638- fcomment18 = FreeThreadedComment.objects.create_for_object(
1639- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment17,
1640- comment='More Depth...Should Show Up.', markup=REST
1641- )
1642-
1643- fcomment19 = FreeThreadedComment.objects.create_for_object(
1644- topic, name='Eric', ip_address='127.0.0.1', parent=fcomment18,
1645- comment='Too Deep..Should NOT Show UP', markup=REST
1646- )
1647-
1648- moderator.unregister(TestModel)
1649-
1650- tree = FreeThreadedComment.public.get_tree(topic)
1651- output = []
1652- for comment in tree:
1653- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1654- self.assertEquals('\n'.join(output),
1655- """
1656-This is fun! This is very fun!
1657- What are you talking about?
1658- This should appear again.
1659-This is stupid! I hate it!
1660- I agree, the first comment was wrong and you are right!
1661- I'm a fanboy!
1662-What are we talking about?
1663-Post moderator addition. Does it still work?
1664- <10chars
1665-This should appear again, due to unregistration
1666-VALID Markup. Should show up.
1667- Building Depth...Should Show Up.
1668- More Depth...Should Show Up.
1669-""".lstrip())
1670-
1671- tree = FreeThreadedComment.objects.get_tree(topic)
1672- output = []
1673- for comment in tree:
1674- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1675- self.assertEquals('\n'.join(output),
1676- """
1677-This is fun! This is very fun!
1678- What are you talking about?
1679- This should appear again.
1680-This is stupid! I hate it!
1681- I agree, the first comment was wrong and you are right!
1682- I'm a fanboy!
1683-What are we talking about?
1684-Post moderator addition. Does it still work?
1685- This shouldn't appear because it has more than 10 chars.
1686- <10chars
1687-This should appear again, due to unregistration
1688-VALID Markup. Should show up.
1689- Building Depth...Should Show Up.
1690- More Depth...Should Show Up.
1691-""".lstrip())
1692-
1693- tree = FreeThreadedComment.objects.get_tree(topic, root=comment2)
1694- output = []
1695- for comment in tree:
1696- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1697- self.assertEquals('\n'.join(output),
1698- """
1699-This is stupid! I hate it!
1700- I agree, the first comment was wrong and you are right!
1701- I'm a fanboy!
1702-""".lstrip())
1703-
1704- tree = FreeThreadedComment.objects.get_tree(topic, root=comment2.id)
1705- output = []
1706- for comment in tree:
1707- output.append('%s %s' % (' ' * comment.depth, comment.comment))
1708- self.assertEquals('\n'.join(output),
1709- """
1710-This is stupid! I hate it!
1711- I agree, the first comment was wrong and you are right!
1712- I'm a fanboy!
1713-""".lstrip())
1714
1715=== removed file 'threadedcomments/tests/templatetags_tests.py'
1716--- threadedcomments/tests/templatetags_tests.py 2016-12-13 18:28:51 +0000
1717+++ threadedcomments/tests/templatetags_tests.py 1970-01-01 00:00:00 +0000
1718@@ -1,561 +0,0 @@
1719-import datetime
1720-
1721-from xml.dom.minidom import parseString
1722-
1723-from django.core import mail
1724-from django.core.urlresolvers import reverse
1725-from django.template import Context, Template
1726-from django.test import TestCase
1727-from django.utils.simplejson import loads
1728-
1729-from django.contrib.auth.models import User
1730-from django.contrib.contenttypes.models import ContentType
1731-
1732-from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
1733-from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
1734-from threadedcomments.templatetags import threadedcommentstags as tags
1735-
1736-
1737-__all__ = ('TemplateTagTestCase',)
1738-
1739-
1740-class TemplateTagTestCase(TestCase):
1741- urls = 'threadedcomments.tests.threadedcomments_urls'
1742-
1743- def test_get_comment_url(self):
1744-
1745- user = User.objects.create_user(
1746- 'user', 'floguy@gmail.com', password='password')
1747-
1748- topic = TestModel.objects.create(name='Test2')
1749- content_type = ContentType.objects.get_for_model(topic)
1750-
1751- comment = ThreadedComment.objects.create_for_object(topic,
1752- user=user,
1753- ip_address='127.0.0.1',
1754- comment='My test comment!',
1755- )
1756-
1757- c = Context({
1758- 'topic': topic,
1759- 'parent': comment
1760- })
1761- sc = {
1762- 'ct': content_type.pk,
1763- 'id': topic.pk,
1764- 'pid': comment.pk,
1765- }
1766-
1767- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url topic %}').render(
1768- c), u'/comment/%(ct)s/%(id)s/' % sc)
1769- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url topic parent %}').render(
1770- c), u'/comment/%(ct)s/%(id)s/%(pid)s/' % sc)
1771- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_json topic %}').render(
1772- c), u'/comment/%(ct)s/%(id)s/json/' % sc)
1773- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_xml topic %}').render(
1774- c), u'/comment/%(ct)s/%(id)s/xml/' % sc)
1775- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_json topic parent %}').render(
1776- c), u'/comment/%(ct)s/%(id)s/%(pid)s/json/' % sc)
1777- self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_xml topic parent %}').render(
1778- c), u'/comment/%(ct)s/%(id)s/%(pid)s/xml/' % sc)
1779-
1780- def test_get_free_comment_url(self):
1781-
1782- topic = TestModel.objects.create(name='Test2')
1783- content_type = ContentType.objects.get_for_model(topic)
1784-
1785- comment = FreeThreadedComment.objects.create_for_object(topic,
1786- ip_address='127.0.0.1',
1787- comment='My test free comment!',
1788- )
1789-
1790- c = Context({
1791- 'topic': topic,
1792- 'parent': comment,
1793- })
1794- sc = {
1795- 'ct': content_type.pk,
1796- 'id': topic.pk,
1797- 'pid': comment.pk,
1798- }
1799-
1800- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url topic %}').render(
1801- c), u'/freecomment/%(ct)s/%(id)s/' % sc)
1802- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url topic parent %}').render(
1803- c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/' % sc)
1804- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_json topic %}').render(
1805- c), u'/freecomment/%(ct)s/%(id)s/json/' % sc)
1806- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_xml topic %}').render(
1807- c), u'/freecomment/%(ct)s/%(id)s/xml/' % sc)
1808- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_json topic parent %}').render(
1809- c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/json/' % sc)
1810- self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_xml topic parent %}').render(
1811- c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/xml/' % sc)
1812-
1813- def test_get_comment_count(self):
1814-
1815- user = User.objects.create_user(
1816- 'user', 'floguy@gmail.com', password='password')
1817-
1818- topic = TestModel.objects.create(name='Test2')
1819-
1820- comment = ThreadedComment.objects.create_for_object(topic,
1821- user=user,
1822- ip_address='127.0.0.1',
1823- comment='My test comment!',
1824- )
1825-
1826- c = Context({
1827- 'topic': topic,
1828- })
1829-
1830- self.assertEquals(
1831- Template(
1832- '{% load threadedcommentstags %}{% get_comment_count for topic as count %}{{ count }}').render(c),
1833- u'1'
1834- )
1835-
1836- def test_get_free_comment_count(self):
1837-
1838- topic = TestModel.objects.create(name='Test2')
1839-
1840- comment = FreeThreadedComment.objects.create_for_object(topic,
1841- ip_address='127.0.0.1',
1842- comment='My test free comment!',
1843- )
1844-
1845- c = Context({
1846- 'topic': topic,
1847- })
1848-
1849- self.assertEquals(
1850- Template(
1851- '{% load threadedcommentstags %}{% get_free_comment_count for topic as count %}{{ count }}').render(c),
1852- u'1'
1853- )
1854-
1855- def test_get_threaded_comment_form(self):
1856- self.assertEquals(
1857- Template('{% load threadedcommentstags %}{% get_threaded_comment_form as form %}{{ form }}').render(
1858- Context({})),
1859- u'<tr><th><label for="id_comment">comment:</label></th><td><textarea id="id_comment" rows="10" cols="40" name="comment"></textarea></td></tr>\n<tr><th><label for="id_markup">Markup:</label></th><td><select name="markup" id="id_markup">\n<option value="">---------</option>\n<option value="1">markdown</option>\n<option value="2">textile</option>\n<option value="3">restructuredtext</option>\n<option value="5" selected="selected">plaintext</option>\n</select></td></tr>'
1860- )
1861-
1862- def test_get_latest_comments(self):
1863-
1864- user = User.objects.create_user(
1865- 'user', 'floguy@gmail.com', password='password')
1866-
1867- topic = TestModel.objects.create(name='Test2')
1868- old_topic = topic
1869- content_type = ContentType.objects.get_for_model(topic)
1870-
1871- ThreadedComment.objects.create_for_object(topic,
1872- user=user,
1873- ip_address='127.0.0.1',
1874- comment='Test 1',
1875- )
1876- ThreadedComment.objects.create_for_object(topic,
1877- user=user,
1878- ip_address='127.0.0.1',
1879- comment='Test 2',
1880- )
1881- ThreadedComment.objects.create_for_object(topic,
1882- user=user,
1883- ip_address='127.0.0.1',
1884- comment='Test 3',
1885- )
1886-
1887- self.assertEquals(
1888- Template('{% load threadedcommentstags %}{% get_latest_comments 2 as comments %}{{ comments }}').render(
1889- Context({})),
1890- u'[&lt;ThreadedComment: Test 3&gt;, &lt;ThreadedComment: Test 2&gt;]'
1891- )
1892-
1893- def test_get_latest_free_comments(self):
1894-
1895- topic = TestModel.objects.create(name='Test2')
1896-
1897- FreeThreadedComment.objects.create_for_object(topic,
1898- ip_address='127.0.0.1',
1899- comment='Test 1',
1900- )
1901- FreeThreadedComment.objects.create_for_object(topic,
1902- ip_address='127.0.0.1',
1903- comment='Test 2',
1904- )
1905- FreeThreadedComment.objects.create_for_object(topic,
1906- ip_address='127.0.0.1',
1907- comment='Test 3',
1908- )
1909-
1910- self.assertEquals(
1911- Template('{% load threadedcommentstags %}{% get_latest_free_comments 2 as comments %}{{ comments }}').render(
1912- Context({})),
1913- u'[&lt;FreeThreadedComment: Test 3&gt;, &lt;FreeThreadedComment: Test 2&gt;]'
1914- )
1915-
1916- def test_get_threaded_comment_tree(self):
1917-
1918- user = User.objects.create_user(
1919- 'user', 'floguy@gmail.com', password='password')
1920-
1921- topic = TestModel.objects.create(name='Test2')
1922-
1923- parent1 = ThreadedComment.objects.create_for_object(topic,
1924- user=user,
1925- ip_address='127.0.0.1',
1926- comment='test1',
1927- )
1928- ThreadedComment.objects.create_for_object(topic,
1929- user=user,
1930- ip_address='127.0.0.1',
1931- comment='test2',
1932- parent=parent1,
1933- )
1934- parent2 = ThreadedComment.objects.create_for_object(topic,
1935- user=user,
1936- ip_address='127.0.0.1',
1937- comment='test3',
1938- )
1939- ThreadedComment.objects.create_for_object(topic,
1940- user=user,
1941- ip_address='127.0.0.1',
1942- comment='test4',
1943- parent=parent2,
1944- )
1945-
1946- c = Context({
1947- 'topic': topic,
1948- })
1949-
1950- self.assertEquals(
1951- Template(
1952- '{% load threadedcommentstags %}{% get_threaded_comment_tree for topic as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
1953- u'[(0)test1,(1)test2,(0)test3,(1)test4,]'
1954- )
1955- self.assertEquals(
1956- Template(
1957- '{% load threadedcommentstags %}{% get_threaded_comment_tree for topic 3 as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
1958- u'[(0)test3,(1)test4,]'
1959- )
1960-
1961- def test_get_free_threaded_comment_tree(self):
1962-
1963- topic = TestModel.objects.create(name='Test2')
1964-
1965- parent1 = FreeThreadedComment.objects.create_for_object(topic,
1966- ip_address='127.0.0.1',
1967- comment='test1',
1968- )
1969- FreeThreadedComment.objects.create_for_object(topic,
1970- ip_address='127.0.0.1',
1971- comment='test2',
1972- parent=parent1,
1973- )
1974- parent2 = FreeThreadedComment.objects.create_for_object(topic,
1975- ip_address='127.0.0.1',
1976- comment='test3',
1977- )
1978- FreeThreadedComment.objects.create_for_object(topic,
1979- ip_address='127.0.0.1',
1980- comment='test4',
1981- parent=parent2,
1982- )
1983-
1984- c = Context({
1985- 'topic': topic,
1986- })
1987-
1988- self.assertEquals(
1989- Template(
1990- '{% load threadedcommentstags %}{% get_free_threaded_comment_tree for topic as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
1991- u'[(0)test1,(1)test2,(0)test3,(1)test4,]'
1992- )
1993- self.assertEquals(
1994- Template(
1995- '{% load threadedcommentstags %}{% get_free_threaded_comment_tree for topic 3 as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
1996- u'[(0)test3,(1)test4,]'
1997- )
1998-
1999- def test_user_comment_tags(self):
2000-
2001- user1 = User.objects.create_user(
2002- 'eric', 'floguy@gmail.com', password='password')
2003- user2 = User.objects.create_user(
2004- 'brian', 'brosner@gmail.com', password='password')
2005-
2006- topic = TestModel.objects.create(name='Test2')
2007-
2008- ThreadedComment.objects.create_for_object(topic,
2009- user=user1,
2010- ip_address='127.0.0.1',
2011- comment='Eric comment',
2012- )
2013- ThreadedComment.objects.create_for_object(topic,
2014- user=user2,
2015- ip_address='127.0.0.1',
2016- comment='Brian comment',
2017- )
2018-
2019- c = Context({
2020- 'user': user1,
2021- })
2022-
2023- self.assertEquals(
2024- Template(
2025- '{% load threadedcommentstags %}{% get_user_comments for user as comments %}{{ comments }}').render(c),
2026- u'[&lt;ThreadedComment: Eric comment&gt;]'
2027- )
2028- self.assertEquals(
2029- Template(
2030- '{% load threadedcommentstags %}{% get_user_comment_count for user as comment_count %}{{ comment_count }}').render(c),
2031- u'1',
2032- )
2033-
2034- def test_markdown_comment(self):
2035-
2036- user = User.objects.create_user(
2037- 'user', 'floguy@gmail.com', password='password')
2038- topic = TestModel.objects.create(name='Test2')
2039-
2040- markdown_txt = '''
2041-A First Level Header
2042-====================
2043-
2044-A Second Level Header
2045----------------------
2046-
2047-Now is the time for all good men to come to
2048-the aid of their country. This is just a
2049-regular paragraph.
2050-
2051-The quick brown fox jumped over the lazy
2052-dog's back.
2053-
2054-### Header 3
2055-
2056-> This is a blockquote.
2057->
2058-> This is the second paragraph in the blockquote.
2059->
2060-> ## This is an H2 in a blockquote
2061-'''
2062-
2063- comment_markdown = ThreadedComment.objects.create_for_object(
2064- topic, user=user, ip_address='127.0.0.1', markup=MARKDOWN,
2065- comment=markdown_txt,
2066- )
2067-
2068- c = Context({
2069- 'comment': comment_markdown,
2070- })
2071- s = Template('{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(
2072- c).replace('\\n', '')
2073- self.assertEquals(s.startswith(u"<h1>"), True)
2074-
2075- def test_textile_comment(self):
2076-
2077- user = User.objects.create_user(
2078- 'user', 'floguy@gmail.com', password='password')
2079- topic = TestModel.objects.create(name='Test2')
2080-
2081- textile_txt = '''
2082-h2{color:green}. This is a title
2083-
2084-h3. This is a subhead
2085-
2086-p{color:red}. This is some text of dubious character. Isn't the use of "quotes" just lazy ... writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote.
2087-
2088-bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised.
2089-
2090-Simple list:
2091-
2092-#{color:blue} one
2093-# two
2094-# three
2095-
2096-Multi-level list:
2097-
2098-# one
2099-## aye
2100-## bee
2101-## see
2102-# two
2103-## x
2104-## y
2105-# three
2106-
2107-Mixed list:
2108-
2109-* Point one
2110-* Point two
2111-## Step 1
2112-## Step 2
2113-## Step 3
2114-* Point three
2115-** Sub point 1
2116-** Sub point 2
2117-
2118-
2119-Well, that went well. How about we insert an <a href="/" title="watch out">old-fashioned ... hypertext link</a>? Will the quote marks in the tags get messed up? No!
2120-
2121-"This is a link (optional title)":http://www.textism.com
2122-
2123-table{border:1px solid black}.
2124-|_. this|_. is|_. a|_. header|
2125-<{background:gray}. |\2. this is|{background:red;width:200px}. a|^<>{height:200px}. row|
2126-|this|<>{padding:10px}. is|^. another|(bob#bob). row|
2127-
2128-An image:
2129-
2130-!/common/textist.gif(optional alt text)!
2131-
2132-# Librarians rule
2133-# Yes they do
2134-# But you knew that
2135-
2136-Some more text of dubious character. Here is a noisome string of CAPITAL letters. Here is ... something we want to _emphasize_.
2137-That was a linebreak. And something to indicate *strength*. Of course I could use <em>my ... own HTML tags</em> if I <strong>felt</strong> like it.
2138-
2139-h3. Coding
2140-
2141-This <code>is some code, "isn't it"</code>. Watch those quote marks! Now for some preformatted text:
2142-
2143-<pre>
2144-<code>
2145- $text = str_replace("<p>%::%</p>","",$text);
2146- $text = str_replace("%::%</p>","",$text);
2147- $text = str_replace("%::%","",$text);
2148-
2149-</code>
2150-</pre>
2151-
2152-This isn't code.
2153-
2154-
2155-So you see, my friends:
2156-
2157-* The time is now
2158-* The time is not later
2159-* The time is not yesterday
2160-* We must act
2161-'''
2162-
2163- comment_textile = ThreadedComment.objects.create_for_object(
2164- topic, user=user, ip_address='127.0.0.1', markup=TEXTILE,
2165- comment=textile_txt,
2166- )
2167- c = Context({
2168- 'comment': comment_textile
2169- })
2170- s = Template(
2171- '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c)
2172- self.assertEquals('<h3>' in s, True)
2173-
2174- def test_rest_comment(self):
2175-
2176- user = User.objects.create_user(
2177- 'user', 'floguy@gmail.com', password='password')
2178- topic = TestModel.objects.create(name='Test2')
2179-
2180- rest_txt = '''
2181-FooBar Header
2182-=============
2183-reStructuredText is **nice**. It has its own webpage_.
2184-
2185-A table:
2186-
2187-===== ===== ======
2188- Inputs Output
2189------------- ------
2190- A B A or B
2191-===== ===== ======
2192-False False False
2193-True False True
2194-False True True
2195-True True True
2196-===== ===== ======
2197-
2198-RST TracLinks
2199--------------
2200-
2201-See also ticket `#42`::.
2202-
2203-.. _webpage: http://docutils.sourceforge.net/rst.html
2204-'''
2205-
2206- comment_rest = ThreadedComment.objects.create_for_object(
2207- topic, user=user, ip_address='127.0.0.1', markup=REST,
2208- comment=rest_txt,
2209- )
2210- c = Context({
2211- 'comment': comment_rest
2212- })
2213- s = Template(
2214- '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c)
2215- self.assertEquals(s.startswith('<p>reStructuredText is'), True)
2216-
2217- def test_plaintext_comment(self):
2218-
2219- user = User.objects.create_user(
2220- 'user', 'floguy@gmail.com', password='password')
2221- topic = TestModel.objects.create(name='Test2')
2222-
2223- comment_plaintext = ThreadedComment.objects.create_for_object(
2224- topic, user=user, ip_address='127.0.0.1', markup=PLAINTEXT,
2225- comment='<b>This is Funny</b>',
2226- )
2227- c = Context({
2228- 'comment': comment_plaintext
2229- })
2230- self.assertEquals(
2231- Template(
2232- '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c),
2233- u'&lt;b&gt;This is Funny&lt;/b&gt;'
2234- )
2235-
2236- comment_plaintext = ThreadedComment.objects.create_for_object(
2237- topic, user=user, ip_address='127.0.0.1', markup=PLAINTEXT,
2238- comment='<b>This is Funny</b>',
2239- )
2240- c = Context({
2241- 'comment': comment_plaintext
2242- })
2243- self.assertEquals(
2244- Template(
2245- '{% load threadedcommentstags %}{% auto_transform_markup comment as abc %}{{ abc }}').render(c),
2246- u'&lt;b&gt;This is Funny&lt;/b&gt;'
2247- )
2248-
2249- def test_gravatar_tags(self):
2250- c = Context({
2251- 'email': 'floguy@gmail.com',
2252- 'rating': 'G',
2253- 'size': 30,
2254- 'default': 'overridectx',
2255- })
2256- self.assertEquals(
2257- Template(
2258- '{% load gravatar %}{% get_gravatar_url for email %}').render(c),
2259- u'http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
2260- )
2261- self.assertEquals(
2262- Template(
2263- '{% load gravatar %}{% get_gravatar_url for email as var %}Var: {{ var }}').render(c),
2264- u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
2265- )
2266- self.assertEquals(
2267- Template(
2268- '{% load gravatar %}{% get_gravatar_url for email size 30 rating "G" default override as var %}Var: {{ var }}').render(c),
2269- u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=G&size=30&default=override'
2270- )
2271- self.assertEquals(
2272- Template(
2273- '{% load gravatar %}{% get_gravatar_url for email size size rating rating default default as var %}Var: {{ var }}').render(c),
2274- u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=G&size=30&default=overridectx'
2275- )
2276- self.assertEquals(
2277- Template('{% load gravatar %}{{ email|gravatar }}').render(c),
2278- u'http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
2279- )
2280
2281=== removed file 'threadedcomments/tests/threadedcomments_urls.py'
2282--- threadedcomments/tests/threadedcomments_urls.py 2016-12-13 18:28:51 +0000
2283+++ threadedcomments/tests/threadedcomments_urls.py 1970-01-01 00:00:00 +0000
2284@@ -1,6 +0,0 @@
2285-from django.conf.urls.defaults import *
2286-
2287-
2288-urlpatterns = patterns('',
2289- url(r"", include('threadedcomments.urls')),
2290- )
2291
2292=== removed file 'threadedcomments/tests/views_tests.py'
2293--- threadedcomments/tests/views_tests.py 2016-12-13 18:28:51 +0000
2294+++ threadedcomments/tests/views_tests.py 1970-01-01 00:00:00 +0000
2295@@ -1,856 +0,0 @@
2296-import datetime
2297-
2298-from xml.dom.minidom import parseString
2299-
2300-from django.core import mail
2301-from django.core.urlresolvers import reverse
2302-from django.template import Context, Template
2303-from django.test import TestCase
2304-from django.utils.simplejson import loads
2305-
2306-from django.contrib.auth.models import User
2307-from django.contrib.contenttypes.models import ContentType
2308-
2309-from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
2310-from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
2311-from threadedcomments.templatetags import threadedcommentstags as tags
2312-
2313-
2314-__all__ = ('ViewsTestCase',)
2315-
2316-
2317-class ViewsTestCase(TestCase):
2318- urls = 'threadedcomments.tests.threadedcomments_urls'
2319-
2320- def test_freecomment_create(self):
2321-
2322- topic = TestModel.objects.create(name='Test2')
2323- content_type = ContentType.objects.get_for_model(topic)
2324-
2325- url = reverse('tc_free_comment', kwargs={
2326- 'content_type': content_type.id,
2327- 'object_id': topic.id
2328- })
2329- response = self.client.post(url, {
2330- 'comment': 'test1',
2331- 'name': 'eric',
2332- 'website': 'http://www.eflorenzano.com/',
2333- 'email': 'floguy@gmail.com',
2334- 'next': '/'
2335- })
2336- o = FreeThreadedComment.objects.latest(
2337- 'date_submitted').get_base_data(show_dates=False)
2338- self.assertEquals(o, {
2339- 'website': u'http://www.eflorenzano.com/',
2340- 'comment': u'test1',
2341- 'name': u'eric',
2342- 'parent': None,
2343- 'markup': u'plaintext',
2344- 'content_object': topic,
2345- 'is_public': True,
2346- 'ip_address': u'127.0.0.1',
2347- 'email': u'floguy@gmail.com',
2348- 'is_approved': False
2349- })
2350-
2351- def test_freecomment_preview(self):
2352-
2353- topic = TestModel.objects.create(name='Test2')
2354- content_type = ContentType.objects.get_for_model(topic)
2355-
2356- url = reverse('tc_free_comment', kwargs={
2357- 'content_type': content_type.id,
2358- 'object_id': topic.id
2359- })
2360-
2361- response = self.client.post(url, {
2362- 'comment': 'test1',
2363- 'name': 'eric',
2364- 'website': 'http://www.eflorenzano.com/',
2365- 'email': 'floguy@gmail.com',
2366- 'next': '/',
2367- 'preview': 'True'
2368- })
2369- self.assertEquals(len(response.content) > 0, True)
2370-
2371- def test_freecomment_edit(self):
2372-
2373- topic = TestModel.objects.create(name='Test2')
2374-
2375- comment = FreeThreadedComment.objects.create_for_object(topic,
2376- ip_address='127.0.0.1',
2377- comment='My test free comment!',
2378- )
2379-
2380- url = reverse('tc_free_comment_edit', kwargs={
2381- 'edit_id': comment.pk
2382- })
2383-
2384- response = self.client.post(url, {
2385- 'comment': 'test1_edited',
2386- 'name': 'eric',
2387- 'website': 'http://www.eflorenzano.com/',
2388- 'email': 'floguy@gmail.com',
2389- 'next': '/'
2390- })
2391- o = FreeThreadedComment.objects.latest(
2392- 'date_submitted').get_base_data(show_dates=False)
2393- self.assertEquals(o, {
2394- 'website': u'http://www.eflorenzano.com/',
2395- 'comment': u'test1_edited',
2396- 'name': u'eric',
2397- 'parent': None,
2398- 'markup': u'plaintext',
2399- 'content_object': topic,
2400- 'is_public': True,
2401- 'ip_address': u'127.0.0.1',
2402- 'email': u'floguy@gmail.com',
2403- 'is_approved': False
2404- })
2405-
2406- def test_freecomment_edit_with_preview(self):
2407-
2408- topic = TestModel.objects.create(name='Test2')
2409-
2410- comment = FreeThreadedComment.objects.create_for_object(topic,
2411- website='http://oebfare.com/',
2412- comment='My test free comment!',
2413- ip_address='127.0.0.1',
2414- )
2415-
2416- url = reverse('tc_free_comment_edit', kwargs={
2417- 'edit_id': comment.pk
2418- })
2419-
2420- response = self.client.post(url, {
2421- 'comment': 'test1_edited',
2422- 'name': 'eric',
2423- 'website': 'http://www.eflorenzano.com/',
2424- 'email': 'floguy@gmail.com',
2425- 'next': '/',
2426- 'preview': 'True'
2427- })
2428- o = FreeThreadedComment.objects.latest(
2429- 'date_submitted').get_base_data(show_dates=False)
2430- self.assertEquals(o, {
2431- 'website': u'http://oebfare.com/',
2432- 'comment': u'My test free comment!',
2433- 'name': u'',
2434- 'parent': None,
2435- 'markup': u'plaintext',
2436- 'content_object': topic,
2437- 'is_public': True,
2438- 'ip_address': u'127.0.0.1',
2439- 'email': u'',
2440- 'is_approved': False
2441- })
2442- self.assertEquals(len(response.content) > 0, True)
2443-
2444- def test_freecomment_json_create(self):
2445-
2446- topic = TestModel.objects.create(name='Test2')
2447- content_type = ContentType.objects.get_for_model(topic)
2448-
2449- url = reverse('tc_free_comment_ajax', kwargs={
2450- 'content_type': content_type.id,
2451- 'object_id': topic.id,
2452- 'ajax': 'json'
2453- })
2454-
2455- response = self.client.post(url, {
2456- 'comment': 'test2',
2457- 'name': 'eric',
2458- 'website': 'http://www.eflorenzano.com/',
2459- 'email': 'floguy@gmail.com'
2460- })
2461- tmp = loads(response.content)
2462- o = FreeThreadedComment.objects.latest(
2463- 'date_submitted').get_base_data(show_dates=False)
2464- self.assertEquals(o, {
2465- 'website': u'http://www.eflorenzano.com/',
2466- 'comment': u'test2',
2467- 'name': u'eric',
2468- 'parent': None,
2469- 'markup': u'plaintext',
2470- 'content_object': topic,
2471- 'is_public': True,
2472- 'ip_address': u'127.0.0.1',
2473- 'email': u'floguy@gmail.com',
2474- 'is_approved': False
2475- })
2476-
2477- def test_freecomment_json_edit(self):
2478-
2479- topic = TestModel.objects.create(name='Test2')
2480-
2481- comment = FreeThreadedComment.objects.create_for_object(topic,
2482- ip_address='127.0.0.1',
2483- comment='My test free comment!',
2484- )
2485-
2486- url = reverse('tc_free_comment_edit_ajax', kwargs={
2487- 'edit_id': comment.pk,
2488- 'ajax': 'json'
2489- })
2490-
2491- response = self.client.post(url, {
2492- 'comment': 'test2_edited',
2493- 'name': 'eric',
2494- 'website': 'http://www.eflorenzano.com/',
2495- 'email': 'floguy@gmail.com'
2496- })
2497- tmp = loads(response.content)
2498- o = FreeThreadedComment.objects.latest(
2499- 'date_submitted').get_base_data(show_dates=False)
2500- self.assertEquals(o, {
2501- 'website': u'http://www.eflorenzano.com/',
2502- 'comment': u'test2_edited',
2503- 'name': u'eric',
2504- 'parent': None,
2505- 'markup': u'plaintext',
2506- 'content_object': topic,
2507- 'is_public': True,
2508- 'ip_address': u'127.0.0.1',
2509- 'email': u'floguy@gmail.com',
2510- 'is_approved': False
2511- })
2512-
2513- def test_freecomment_xml_create(self):
2514-
2515- topic = TestModel.objects.create(name='Test2')
2516- content_type = ContentType.objects.get_for_model(topic)
2517-
2518- url = reverse('tc_free_comment_ajax', kwargs={
2519- 'content_type': content_type.id,
2520- 'object_id': topic.id,
2521- 'ajax': 'xml'
2522- })
2523-
2524- response = self.client.post(url, {'comment': 'test3', 'name': 'eric',
2525- 'website': 'http://www.eflorenzano.com/', 'email': 'floguy@gmail.com', 'next': '/'})
2526- tmp = parseString(response.content)
2527- o = FreeThreadedComment.objects.latest(
2528- 'date_submitted').get_base_data(show_dates=False)
2529- self.assertEquals(o, {
2530- 'website': u'http://www.eflorenzano.com/',
2531- 'comment': u'test3',
2532- 'name': u'eric',
2533- 'parent': None,
2534- 'markup': u'plaintext',
2535- 'content_object': topic,
2536- 'is_public': True,
2537- 'ip_address': u'127.0.0.1',
2538- 'email': u'floguy@gmail.com',
2539- 'is_approved': False
2540- })
2541-
2542- def test_freecomment_xml_edit(self):
2543-
2544- topic = TestModel.objects.create(name='Test2')
2545-
2546- comment = FreeThreadedComment.objects.create_for_object(topic,
2547- ip_address='127.0.0.1',
2548- comment='My test free comment!',
2549- )
2550-
2551- url = reverse('tc_free_comment_edit_ajax', kwargs={
2552- 'edit_id': comment.pk,
2553- 'ajax': 'xml'
2554- })
2555-
2556- response = self.client.post(url, {
2557- 'comment': 'test2_edited',
2558- 'name': 'eric',
2559- 'website': 'http://www.eflorenzano.com/',
2560- 'email': 'floguy@gmail.com'
2561- })
2562- tmp = parseString(response.content)
2563- o = FreeThreadedComment.objects.latest(
2564- 'date_submitted').get_base_data(show_dates=False)
2565- self.assertEquals(o, {
2566- 'website': u'http://www.eflorenzano.com/',
2567- 'comment': u'test2_edited',
2568- 'name': u'eric',
2569- 'parent': None,
2570- 'markup': u'plaintext',
2571- 'content_object': topic,
2572- 'is_public': True,
2573- 'ip_address': u'127.0.0.1',
2574- 'email': u'floguy@gmail.com',
2575- 'is_approved': False
2576- })
2577-
2578- def test_freecomment_child_create(self):
2579-
2580- topic = TestModel.objects.create(name='Test2')
2581- content_type = ContentType.objects.get_for_model(topic)
2582-
2583- parent = FreeThreadedComment.objects.create_for_object(topic,
2584- ip_address='127.0.0.1',
2585- comment='My test free comment!',
2586- )
2587-
2588- url = reverse('tc_free_comment_parent', kwargs={
2589- 'content_type': content_type.id,
2590- 'object_id': topic.id,
2591- 'parent_id': parent.id
2592- })
2593- response = self.client.post(url, {
2594- 'comment': 'test4',
2595- 'name': 'eric',
2596- 'website': 'http://www.eflorenzano.com/',
2597- 'email': 'floguy@gmail.com',
2598- 'next': '/'
2599- })
2600- o = FreeThreadedComment.objects.latest(
2601- 'date_submitted').get_base_data(show_dates=False)
2602- self.assertEquals(o, {
2603- 'website': u'http://www.eflorenzano.com/',
2604- 'comment': u'test4',
2605- 'name': u'eric',
2606- 'parent': parent,
2607- 'markup': u'plaintext',
2608- 'content_object': topic,
2609- 'is_public': True,
2610- 'ip_address': u'127.0.0.1',
2611- 'email': u'floguy@gmail.com',
2612- 'is_approved': False
2613- })
2614-
2615- def test_freecomment_child_json_create(self):
2616-
2617- topic = TestModel.objects.create(name='Test2')
2618- content_type = ContentType.objects.get_for_model(topic)
2619-
2620- parent = FreeThreadedComment.objects.create_for_object(topic,
2621- ip_address='127.0.0.1',
2622- comment='My test free comment!',
2623- )
2624-
2625- url = reverse('tc_free_comment_parent_ajax', kwargs={
2626- 'content_type': content_type.id,
2627- 'object_id': topic.id,
2628- 'parent_id': parent.id,
2629- 'ajax': 'json'
2630- })
2631-
2632- response = self.client.post(url, {
2633- 'comment': 'test5',
2634- 'name': 'eric',
2635- 'website': 'http://www.eflorenzano.com/',
2636- 'email': 'floguy@gmail.com'
2637- })
2638- tmp = loads(response.content)
2639- o = FreeThreadedComment.objects.latest(
2640- 'date_submitted').get_base_data(show_dates=False)
2641- self.assertEquals(o, {
2642- 'website': u'http://www.eflorenzano.com/',
2643- 'comment': u'test5',
2644- 'name': u'eric',
2645- 'parent': parent,
2646- 'markup': u'plaintext',
2647- 'content_object': topic,
2648- 'is_public': True,
2649- 'ip_address': u'127.0.0.1',
2650- 'email': u'floguy@gmail.com',
2651- 'is_approved': False
2652- })
2653-
2654- def test_freecomment_child_xml_create(self):
2655-
2656- topic = TestModel.objects.create(name='Test2')
2657- content_type = ContentType.objects.get_for_model(topic)
2658-
2659- parent = FreeThreadedComment.objects.create_for_object(topic,
2660- ip_address='127.0.0.1',
2661- comment='My test free comment!',
2662- )
2663-
2664- url = reverse('tc_free_comment_parent_ajax', kwargs={
2665- 'content_type': content_type.id,
2666- 'object_id': topic.id,
2667- 'parent_id': parent.id,
2668- 'ajax': 'xml'
2669- })
2670-
2671- response = self.client.post(url, {
2672- 'comment': 'test6', 'name': 'eric',
2673- 'website': 'http://www.eflorenzano.com/',
2674- 'email': 'floguy@gmail.com'
2675- })
2676- tmp = parseString(response.content)
2677- o = FreeThreadedComment.objects.latest(
2678- 'date_submitted').get_base_data(show_dates=False)
2679- self.assertEquals(o, {
2680- 'website': u'http://www.eflorenzano.com/',
2681- 'comment': u'test6',
2682- 'name': u'eric',
2683- 'parent': parent,
2684- 'markup': u'plaintext',
2685- 'content_object': topic,
2686- 'is_public': True,
2687- 'ip_address': u'127.0.0.1',
2688- 'email': u'floguy@gmail.com',
2689- 'is_approved': False
2690- })
2691-
2692- def create_user_and_login(self):
2693- user = User.objects.create_user(
2694- 'testuser',
2695- 'testuser@gmail.com',
2696- 'password',
2697- )
2698- user.is_active = True
2699- user.save()
2700- self.client.login(username='testuser', password='password')
2701- return user
2702-
2703- def test_comment_create(self):
2704-
2705- user = self.create_user_and_login()
2706-
2707- topic = TestModel.objects.create(name='Test2')
2708- content_type = ContentType.objects.get_for_model(topic)
2709-
2710- url = reverse('tc_comment', kwargs={
2711- 'content_type': content_type.id,
2712- 'object_id': topic.id
2713- })
2714-
2715- response = self.client.post(url, {
2716- 'comment': 'test7',
2717- 'next': '/'
2718- })
2719- o = ThreadedComment.objects.latest(
2720- 'date_submitted').get_base_data(show_dates=False)
2721- self.assertEquals(o, {
2722- 'comment': u'test7',
2723- 'is_approved': False,
2724- 'parent': None,
2725- 'markup': u'plaintext',
2726- 'content_object': topic,
2727- 'user': user,
2728- 'is_public': True,
2729- 'ip_address': u'127.0.0.1',
2730- })
2731-
2732- def test_comment_preview(self):
2733-
2734- user = self.create_user_and_login()
2735-
2736- topic = TestModel.objects.create(name='Test2')
2737- content_type = ContentType.objects.get_for_model(topic)
2738-
2739- url = reverse('tc_comment', kwargs={
2740- 'content_type': content_type.id,
2741- 'object_id': topic.id
2742- })
2743-
2744- response = self.client.post(url, {
2745- 'comment': 'test7',
2746- 'next': '/',
2747- 'preview': 'True'
2748- })
2749- self.assertEquals(len(response.content) > 0, True)
2750-
2751- def test_comment_edit(self):
2752-
2753- user = self.create_user_and_login()
2754-
2755- topic = TestModel.objects.create(name='Test2')
2756- comment = ThreadedComment.objects.create_for_object(topic,
2757- user=user,
2758- ip_address=u'127.0.0.1',
2759- comment='My test comment!',
2760- )
2761-
2762- url = reverse('tc_comment_edit', kwargs={
2763- 'edit_id': comment.pk,
2764- })
2765-
2766- response = self.client.post(url, {
2767- 'comment': 'test7_edited',
2768- 'next': '/',
2769- })
2770- o = ThreadedComment.objects.latest(
2771- 'date_submitted').get_base_data(show_dates=False)
2772- self.assertEquals(o, {
2773- 'comment': u'test7_edited',
2774- 'is_approved': False,
2775- 'parent': None,
2776- 'markup': u'plaintext',
2777- 'content_object': topic,
2778- 'user': user,
2779- 'is_public': True,
2780- 'ip_address': u'127.0.0.1',
2781- })
2782-
2783- def test_comment_edit_with_preview(self):
2784-
2785- user = self.create_user_and_login()
2786-
2787- topic = TestModel.objects.create(name='Test2')
2788- comment = ThreadedComment.objects.create_for_object(topic,
2789- user=user,
2790- ip_address=u'127.0.0.1',
2791- comment='My test comment!',
2792- )
2793-
2794- url = reverse('tc_comment_edit', kwargs={
2795- 'edit_id': comment.pk,
2796- })
2797-
2798- response = self.client.post(url, {
2799- 'comment': 'test7_edited',
2800- 'next': '/',
2801- 'preview': 'True'
2802- })
2803-
2804- self.assertEquals(len(response.content) > 0, True)
2805- o = ThreadedComment.objects.latest(
2806- 'date_submitted').get_base_data(show_dates=False)
2807- self.assertEquals(o, {
2808- 'comment': u'My test comment!',
2809- 'is_approved': False,
2810- 'parent': None,
2811- 'markup': u'plaintext',
2812- 'content_object': topic,
2813- 'user': user,
2814- 'is_public': True,
2815- 'ip_address': u'127.0.0.1',
2816- })
2817-
2818- def test_comment_json_create(self):
2819-
2820- user = self.create_user_and_login()
2821-
2822- topic = TestModel.objects.create(name='Test2')
2823- content_type = ContentType.objects.get_for_model(topic)
2824-
2825- url = reverse('tc_comment_ajax', kwargs={
2826- 'content_type': content_type.id,
2827- 'object_id': topic.id,
2828- 'ajax': 'json'
2829- })
2830-
2831- response = self.client.post(url, {
2832- 'comment': 'test8'
2833- })
2834- tmp = loads(response.content)
2835- o = ThreadedComment.objects.latest(
2836- 'date_submitted').get_base_data(show_dates=False)
2837- self.assertEquals(o, {
2838- 'comment': u'test8',
2839- 'is_approved': False,
2840- 'parent': None,
2841- 'markup': u'plaintext',
2842- 'content_object': topic,
2843- 'user': user,
2844- 'is_public': True,
2845- 'ip_address': u'127.0.0.1',
2846- })
2847-
2848- def test_comment_json_edit(self):
2849-
2850- user = self.create_user_and_login()
2851-
2852- topic = TestModel.objects.create(name='Test2')
2853- comment = ThreadedComment.objects.create_for_object(topic,
2854- user=user,
2855- ip_address=u'127.0.0.1',
2856- comment='My test comment!',
2857- )
2858-
2859- url = reverse('tc_comment_edit_ajax', kwargs={
2860- 'edit_id': comment.pk,
2861- 'ajax': 'json',
2862- })
2863-
2864- response = self.client.post(url, {
2865- 'comment': 'test8_edited'
2866- })
2867- tmp = loads(response.content)
2868- o = ThreadedComment.objects.latest(
2869- 'date_submitted').get_base_data(show_dates=False)
2870- self.assertEquals(o, {
2871- 'comment': u'test8_edited',
2872- 'is_approved': False,
2873- 'parent': None,
2874- 'markup': u'plaintext',
2875- 'content_object': topic,
2876- 'user': user,
2877- 'is_public': True,
2878- 'ip_address': u'127.0.0.1',
2879- })
2880-
2881- def test_comment_xml_create(self):
2882-
2883- user = self.create_user_and_login()
2884-
2885- topic = TestModel.objects.create(name='Test2')
2886- content_type = ContentType.objects.get_for_model(topic)
2887-
2888- url = reverse('tc_comment_ajax', kwargs={
2889- 'content_type': content_type.id,
2890- 'object_id': topic.id,
2891- 'ajax': 'xml'
2892- })
2893-
2894- response = self.client.post(url, {
2895- 'comment': 'test9'
2896- })
2897- tmp = parseString(response.content)
2898- o = ThreadedComment.objects.latest(
2899- 'date_submitted').get_base_data(show_dates=False)
2900- self.assertEquals(o, {
2901- 'comment': u'test9',
2902- 'is_approved': False,
2903- 'parent': None,
2904- 'markup': u'plaintext',
2905- 'content_object': topic,
2906- 'user': user,
2907- 'is_public': True,
2908- 'ip_address': u'127.0.0.1',
2909- })
2910-
2911- def test_comment_xml_edit(self):
2912-
2913- user = self.create_user_and_login()
2914-
2915- topic = TestModel.objects.create(name='Test2')
2916- comment = ThreadedComment.objects.create_for_object(topic,
2917- user=user,
2918- ip_address=u'127.0.0.1',
2919- comment='My test comment!',
2920- )
2921-
2922- url = reverse('tc_comment_edit_ajax', kwargs={
2923- 'edit_id': comment.pk,
2924- 'ajax': 'xml',
2925- })
2926-
2927- response = self.client.post(url, {
2928- 'comment': 'test8_edited'
2929- })
2930- tmp = parseString(response.content)
2931- o = ThreadedComment.objects.latest(
2932- 'date_submitted').get_base_data(show_dates=False)
2933- self.assertEquals(o, {
2934- 'comment': u'test8_edited',
2935- 'is_approved': False,
2936- 'parent': None,
2937- 'markup': u'plaintext',
2938- 'content_object': topic,
2939- 'user': user,
2940- 'is_public': True,
2941- 'ip_address': u'127.0.0.1',
2942- })
2943-
2944- def test_comment_child_create(self):
2945-
2946- user = self.create_user_and_login()
2947-
2948- topic = TestModel.objects.create(name='Test2')
2949- content_type = ContentType.objects.get_for_model(topic)
2950-
2951- parent = ThreadedComment.objects.create_for_object(topic,
2952- user=user,
2953- ip_address=u'127.0.0.1',
2954- comment='My test comment!',
2955- )
2956-
2957- url = reverse('tc_comment_parent', kwargs={
2958- 'content_type': content_type.id,
2959- 'object_id': topic.id,
2960- 'parent_id': parent.id
2961- })
2962-
2963- response = self.client.post(url, {
2964- 'comment': 'test10',
2965- 'next': '/'
2966- })
2967- o = ThreadedComment.objects.latest(
2968- 'date_submitted').get_base_data(show_dates=False)
2969- self.assertEquals(o, {
2970- 'comment': u'test10',
2971- 'is_approved': False,
2972- 'parent': parent,
2973- 'markup': u'plaintext',
2974- 'content_object': topic,
2975- 'user': user,
2976- 'is_public': True,
2977- 'ip_address': u'127.0.0.1',
2978- })
2979-
2980- def test_comment_child_json_create(self):
2981-
2982- user = self.create_user_and_login()
2983-
2984- topic = TestModel.objects.create(name='Test2')
2985- content_type = ContentType.objects.get_for_model(topic)
2986-
2987- parent = ThreadedComment.objects.create_for_object(topic,
2988- user=user,
2989- ip_address=u'127.0.0.1',
2990- comment='My test comment!',
2991- )
2992-
2993- url = reverse('tc_comment_parent_ajax', kwargs={
2994- 'content_type': content_type.id,
2995- 'object_id': topic.id,
2996- 'parent_id': parent.id,
2997- 'ajax': 'json'
2998- })
2999-
3000- response = self.client.post(url, {
3001- 'comment': 'test11'
3002- })
3003- tmp = loads(response.content)
3004- o = ThreadedComment.objects.latest(
3005- 'date_submitted').get_base_data(show_dates=False)
3006- self.assertEquals(o, {
3007- 'comment': u'test11',
3008- 'is_approved': False,
3009- 'parent': parent,
3010- 'markup': u'plaintext',
3011- 'content_object': topic,
3012- 'user': user,
3013- 'is_public': True,
3014- 'ip_address': u'127.0.0.1',
3015- })
3016-
3017- def test_comment_child_xml_create(self):
3018-
3019- user = self.create_user_and_login()
3020-
3021- topic = TestModel.objects.create(name='Test2')
3022- content_type = ContentType.objects.get_for_model(topic)
3023-
3024- parent = ThreadedComment.objects.create_for_object(topic,
3025- user=user,
3026- ip_address=u'127.0.0.1',
3027- comment='My test comment!',
3028- )
3029-
3030- url = reverse('tc_comment_parent_ajax', kwargs={
3031- 'content_type': content_type.id,
3032- 'object_id': topic.id,
3033- 'parent_id': parent.id,
3034- 'ajax': 'xml'
3035- })
3036-
3037- response = self.client.post(url, {
3038- 'comment': 'test12'
3039- })
3040- tmp = parseString(response.content)
3041- o = ThreadedComment.objects.latest(
3042- 'date_submitted').get_base_data(show_dates=False)
3043- self.assertEquals(o, {
3044- 'comment': u'test12',
3045- 'is_approved': False,
3046- 'parent': parent,
3047- 'markup': u'plaintext',
3048- 'content_object': topic,
3049- 'user': user,
3050- 'is_public': True,
3051- 'ip_address': u'127.0.0.1',
3052- })
3053-
3054- def test_freecomment_delete(self):
3055-
3056- user = User.objects.create_user(
3057- 'testuser',
3058- 'testuser@gmail.com',
3059- 'password',
3060- )
3061- user.is_active = True
3062- user.save()
3063- self.client.login(username='testuser', password='password')
3064-
3065- topic = TestModel.objects.create(name='Test2')
3066-
3067- comment = FreeThreadedComment.objects.create_for_object(topic,
3068- ip_address=u'127.0.0.1',
3069- comment='My test comment!',
3070- )
3071- deleted_id = comment.id
3072-
3073- url = reverse('tc_free_comment_delete', kwargs={
3074- 'object_id': comment.id,
3075- })
3076-
3077- response = self.client.post(url, {'next': '/'})
3078- o = response['Location'].split(
3079- '?')[-1] == 'next=/freecomment/%d/delete/' % deleted_id
3080- self.assertEquals(o, True)
3081-
3082- # become super user and try deleting comment
3083- user.is_superuser = True
3084- user.save()
3085-
3086- response = self.client.post(url, {'next': '/'})
3087- self.assertEquals(response['Location'], 'http://testserver/')
3088- self.assertRaises(
3089- FreeThreadedComment.DoesNotExist,
3090- lambda: FreeThreadedComment.objects.get(id=deleted_id)
3091- )
3092-
3093- # re-create comment
3094- comment.save()
3095-
3096- response = self.client.get(url, {'next': '/'})
3097- self.assertEquals(len(response.content) > 0, True)
3098-
3099- o = FreeThreadedComment.objects.get(id=deleted_id) != None
3100- self.assertEquals(o, True)
3101-
3102- def test_comment_delete(self):
3103-
3104- some_other_guy = User.objects.create_user(
3105- 'some_other_guy',
3106- 'somewhere@overthemoon.com',
3107- 'password1',
3108- )
3109- user = User.objects.create_user(
3110- 'testuser',
3111- 'testuser@gmail.com',
3112- 'password',
3113- )
3114- user.is_active = True
3115- user.save()
3116- self.client.login(username='testuser', password='password')
3117-
3118- topic = TestModel.objects.create(name='Test2')
3119-
3120- comment = ThreadedComment.objects.create_for_object(topic,
3121- user=some_other_guy,
3122- ip_address=u'127.0.0.1',
3123- comment='My test comment!',
3124- )
3125- deleted_id = comment.id
3126-
3127- url = reverse('tc_comment_delete', kwargs={
3128- 'object_id': comment.id,
3129- })
3130- response = self.client.post(url, {'next': '/'})
3131- self.assertEquals(response['Location'].split(
3132- '?')[-1], 'next=/comment/%s/delete/' % deleted_id)
3133-
3134- user.is_superuser = True
3135- user.save()
3136-
3137- response = self.client.post(url, {'next': '/'})
3138- self.assertEquals(response['Location'], 'http://testserver/')
3139- self.assertRaises(
3140- ThreadedComment.DoesNotExist,
3141- lambda: ThreadedComment.objects.get(id=deleted_id)
3142- )
3143-
3144- # re-create comment
3145- comment.save()
3146-
3147- response = self.client.get(url, {'next': '/'})
3148- self.assertEquals(len(response.content) > 0, True)
3149-
3150- o = ThreadedComment.objects.get(id=deleted_id) != None
3151- self.assertEquals(o, True)
3152
3153=== modified file 'threadedcomments/views.py'
3154--- threadedcomments/views.py 2016-12-13 18:28:51 +0000
3155+++ threadedcomments/views.py 2018-04-19 17:49:06 +0000
3156@@ -1,7 +1,7 @@
3157 from django.http import HttpResponseRedirect, Http404
3158 from django.contrib.auth.decorators import login_required
3159 from django.contrib.contenttypes.models import ContentType
3160-from django.shortcuts import get_object_or_404, render_to_response
3161+from django.shortcuts import get_object_or_404, render
3162 from django.template import RequestContext, Context, Template
3163 from django.utils.http import urlquote
3164 from django.conf import settings
3165@@ -53,11 +53,10 @@
3166 context['comment'] = new_comment
3167 else:
3168 context['comment'] = None
3169- return render_to_response(
3170- 'threadedcomments/preview_comment.html',
3171- extra_context,
3172- context_instance=RequestContext(request, context, context_processors)
3173- )
3174+ return render(request,
3175+ 'threadedcomments/preview_comment.html',
3176+ extra_context,
3177+ )
3178
3179
3180 def free_comment(request, content_type=None, object_id=None, edit_id=None, parent_id=None, add_messages=False, ajax=False, model=FreeThreadedComment, form_class=FreeThreadedCommentForm, context_processors=[], extra_context={}):
3181@@ -151,7 +150,7 @@
3182 return True
3183 return False
3184
3185-
3186+# Todo: Next one is not used so far and may need adjustments to the render()
3187 def comment_delete(request, object_id, model=ThreadedComment, extra_context={}, context_processors=[], permission_callback=can_delete_comment):
3188 """Deletes the specified comment, which can be either a
3189 ``FreeThreadedComment`` or a ``ThreadedComment``.
3190@@ -175,17 +174,15 @@
3191 else:
3192 is_free_threaded_comment = True
3193 is_threaded_comment = False
3194- return render_to_response(
3195- 'threadedcomments/confirm_delete.html',
3196- extra_context,
3197- context_instance=RequestContext(
3198- request,
3199- {
3200- 'comment': tc,
3201- 'is_free_threaded_comment': is_free_threaded_comment,
3202- 'is_threaded_comment': is_threaded_comment,
3203- 'next': _get_next(request),
3204- },
3205- context_processors
3206+
3207+ extra_context.update(
3208+ {'comment': tc,
3209+ 'is_free_threaded_comment': is_free_threaded_comment,
3210+ 'is_threaded_comment': is_threaded_comment,
3211+ 'next': _get_next(request),
3212+ }
3213 )
3214- )
3215+ return render(request,
3216+ 'threadedcomments/confirm_delete.html',
3217+ extra_context,
3218+ )
3219
3220=== removed directory 'tracking'
3221=== removed file 'tracking/AUTHORS'
3222--- tracking/AUTHORS 2016-06-05 20:58:46 +0000
3223+++ tracking/AUTHORS 1970-01-01 00:00:00 +0000
3224@@ -1,6 +0,0 @@
3225-django-tracking was written by Josh VanderLinden
3226-
3227-There have been many contributions, including:
3228-
3229-ramusus (https://github.com/ramusus/django-tracking)
3230-jezdez (https://bitbucket.org/jezdez/django-tracking)
3231
3232=== removed file 'tracking/CHANGES'
3233--- tracking/CHANGES 2016-06-05 20:58:46 +0000
3234+++ tracking/CHANGES 1970-01-01 00:00:00 +0000
3235@@ -1,44 +0,0 @@
3236-=======================
3237-django-tracking changes
3238-=======================
3239-
3240-0.3.5
3241------
3242-
3243-- Using Django's GIS utilities a little better
3244-- Added caching in the middleware to reduce hits to the database
3245-- Added some logging
3246-
3247-0.3.3
3248------
3249-
3250-- Improved the setup.py script
3251-
3252-0.3.2
3253------
3254-
3255-- Merged changes from ramusus to better deal with the unicode problems
3256-
3257-0.3.1
3258------
3259-
3260-- Trying to handle some unicode problems
3261-- Added a sample base.html template
3262-- Code cleaning
3263-
3264-0.3.0
3265------
3266-
3267-- Fixed several bugs dealing with performance
3268-- Improved stability
3269-- Added some German translations
3270-- Removed dependency on GeoIP C API and Python API in favor of Django's
3271- built-in GIS utilities
3272-- Tweaked the active users map
3273-
3274-0.2.5
3275------
3276-
3277-- Did some code optimization
3278-- Added the ability to have a dynamically updating Google Map to show where your
3279- visitors are coming from
3280
3281=== removed file 'tracking/LICENSE'
3282--- tracking/LICENSE 2016-06-05 20:58:46 +0000
3283+++ tracking/LICENSE 1970-01-01 00:00:00 +0000
3284@@ -1,19 +0,0 @@
3285-Copyright (c) 2008-2009 Josh VanderLinden
3286-
3287-Permission is hereby granted, free of charge, to any person obtaining a copy
3288-of this software and associated documentation files (the "Software"), to deal
3289-in the Software without restriction, including without limitation the rights
3290-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3291-copies of the Software, and to permit persons to whom the Software is
3292-furnished to do so, subject to the following conditions:
3293-
3294-The above copyright notice and this permission notice shall be included in
3295-all copies or substantial portions of the Software.
3296-
3297-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3298-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3299-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3300-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3301-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3302-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
3303-THE SOFTWARE.
3304\ No newline at end of file
3305
3306=== removed file 'tracking/__init__.py'
3307--- tracking/__init__.py 2016-12-13 18:28:51 +0000
3308+++ tracking/__init__.py 1970-01-01 00:00:00 +0000
3309@@ -1,8 +0,0 @@
3310-import listeners
3311-
3312-VERSION = (0, 4, 0)
3313-
3314-
3315-def get_version():
3316- 'Returns the version as a human-format string.'
3317- return '.'.join([str(i) for i in VERSION])
3318
3319=== removed file 'tracking/admin.py'
3320--- tracking/admin.py 2016-12-13 18:28:51 +0000
3321+++ tracking/admin.py 1970-01-01 00:00:00 +0000
3322@@ -1,5 +0,0 @@
3323-from django.contrib import admin
3324-from tracking.models import BannedIP, UntrackedUserAgent
3325-
3326-admin.site.register(BannedIP)
3327-admin.site.register(UntrackedUserAgent)
3328
3329=== removed file 'tracking/listeners.py'
3330--- tracking/listeners.py 2016-12-13 18:28:51 +0000
3331+++ tracking/listeners.py 1970-01-01 00:00:00 +0000
3332@@ -1,35 +0,0 @@
3333-import logging
3334-
3335-log = logging.getLogger('tracking.listeners')
3336-
3337-try:
3338- from django.core.cache import cache
3339- from django.db.models.signals import post_save, post_delete
3340-
3341- from tracking.models import UntrackedUserAgent, BannedIP
3342-except ImportError:
3343- pass
3344-else:
3345-
3346- def refresh_untracked_user_agents(sender, instance, created=False, **kwargs):
3347- """Updates the cache of user agents that we don't track."""
3348-
3349- log.debug('Updating untracked user agents cache')
3350- cache.set('_tracking_untracked_uas',
3351- UntrackedUserAgent.objects.all(),
3352- 3600)
3353-
3354- def refresh_banned_ips(sender, instance, created=False, **kwargs):
3355- """Updates the cache of banned IP addresses."""
3356-
3357- log.debug('Updating banned IP cache')
3358- cache.set('_tracking_banned_ips',
3359- [b.ip_address for b in BannedIP.objects.all()],
3360- 3600)
3361-
3362- post_save.connect(refresh_untracked_user_agents, sender=UntrackedUserAgent)
3363- post_delete.connect(refresh_untracked_user_agents,
3364- sender=UntrackedUserAgent)
3365-
3366- post_save.connect(refresh_banned_ips, sender=BannedIP)
3367- post_delete.connect(refresh_banned_ips, sender=BannedIP)
3368
3369=== removed directory 'tracking/locale'
3370=== removed directory 'tracking/locale/de'
3371=== removed directory 'tracking/locale/de/LC_MESSAGES'
3372=== removed file 'tracking/locale/de/LC_MESSAGES/django.mo'
3373Binary files tracking/locale/de/LC_MESSAGES/django.mo 2016-06-05 20:58:46 +0000 and tracking/locale/de/LC_MESSAGES/django.mo 1970-01-01 00:00:00 +0000 differ
3374=== removed file 'tracking/locale/de/LC_MESSAGES/django.po'
3375--- tracking/locale/de/LC_MESSAGES/django.po 2016-06-05 20:58:46 +0000
3376+++ tracking/locale/de/LC_MESSAGES/django.po 1970-01-01 00:00:00 +0000
3377@@ -1,110 +0,0 @@
3378-# SOME DESCRIPTIVE TITLE.
3379-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3380-# This file is distributed under the same license as the PACKAGE package.
3381-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
3382-#
3383-#, fuzzy
3384-msgid ""
3385-msgstr ""
3386-"Project-Id-Version: PACKAGE VERSION\n"
3387-"Report-Msgid-Bugs-To: \n"
3388-"POT-Creation-Date: 2010-03-24 12:45+0100\n"
3389-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
3390-"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
3391-"Language-Team: LANGUAGE <LL@li.org>\n"
3392-"MIME-Version: 1.0\n"
3393-"Content-Type: text/plain; charset=UTF-8\n"
3394-"Content-Transfer-Encoding: 8bit\n"
3395-
3396-#: models.py:77
3397-msgid "unknown"
3398-msgstr "unbekannt"
3399-
3400-#: models.py:121
3401-msgid "keyword"
3402-msgstr "Schlagwort"
3403-
3404-#: models.py:121
3405-msgid ""
3406-"Part or all of a user-agent string. For example, \"Googlebot\" here will be "
3407-"found in \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/"
3408-"bot.html)\" and that visitor will not be tracked."
3409-msgstr ""
3410-"Ein Teil oder der ganze Name eines User Agent. \"Googlebot\" zum Beispiel "
3411-"wird auch in \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google."
3412-"com/bot.html)\" gefunden werden und verhindern, dass dieser \"User\" "
3413-"getrackt wird."
3414-
3415-#: models.py:128
3416-msgid "Untracked User-Agent"
3417-msgstr "Nicht-getrackter User-Agent"
3418-
3419-#: models.py:129
3420-msgid "Untracked User-Agents"
3421-msgstr "Nicht-getrackte User-Agents"
3422-
3423-#: models.py:132
3424-msgid "The IP address that should be banned"
3425-msgstr "Die IP-Adresse ist schon verboten"
3426-
3427-#: models.py:139
3428-msgid "Banned IP"
3429-msgstr "Verbotene IP"
3430-
3431-#: models.py:140
3432-msgid "Banned IPs"
3433-msgstr "Verbotene IPs"
3434-
3435-#: views.py:79
3436-#, python-format
3437-msgid "%(minutes)i minute"
3438-msgid_plural "%(minutes)i minutes"
3439-msgstr[0] "%(minutes)i Minute"
3440-msgstr[1] "%(minutes)i Minuten"
3441-
3442-#: views.py:85
3443-#, python-format
3444-msgid "%(seconds)i second"
3445-msgid_plural "%(seconds)i seconds"
3446-msgstr[0] "%(seconds)i Sekunde"
3447-msgstr[1] "%(seconds)i Sekunden"
3448-
3449-#: templates/tracking/_active_users.html:2
3450-#, python-format
3451-msgid "1 active user"
3452-msgid_plural "%(counter)s active users"
3453-msgstr[0] "1 aktiver Benutzer"
3454-msgstr[1] "%(counter)s aktive Benutzer"
3455-
3456-#: templates/tracking/_active_users.html:5
3457-#, python-format
3458-msgid "1 guest user"
3459-msgid_plural "%(counter)s guest users"
3460-msgstr[0] "1 Gastbenutzer"
3461-msgstr[1] "%(counter)s Gastbenutzer"
3462-
3463-#: templates/tracking/_active_users.html:15
3464-#, python-format
3465-msgid "1 registered user"
3466-msgid_plural "%(counter)s registered users"
3467-msgstr[0] "1 registrierter Benutzer"
3468-msgstr[1] "%(counter)s registrierte Benutzer"
3469-
3470-#: templates/tracking/visitor_map.html:4
3471-msgid "Active Visitors Map"
3472-msgstr "Aktive Benutzer Karte"
3473-
3474-#: templates/tracking/visitor_map.html:15
3475-msgid "Active Users"
3476-msgstr "Aktive Benutzer"
3477-
3478-#: templates/tracking/visitor_map.html:18
3479-msgid ""
3480-"Below is a map and a list of recently active users on this site. It updates "
3481-"itself every 5 seconds or so, adding and removing pins in the map as "
3482-"necessary. Stick around for a few minutes to see it in action!"
3483-msgstr ""
3484-"Unten sehen Sie eine Karte und eine Liste der Benutzer, die gerade auf "
3485-"dieser Seite aktiv sind. Ungefähr alle 5 Sekunden werden die Daten "
3486-"aktualisert und rendern die Karte neu. Bleiben Sie ein paar Minuten, um es "
3487-"live in Aktion zu erleben!"
3488
3489=== removed directory 'tracking/media'
3490=== removed directory 'tracking/media/tracking'
3491=== removed directory 'tracking/media/tracking/img'
3492=== removed file 'tracking/media/tracking/img/Flags.zip'
3493Binary files tracking/media/tracking/img/Flags.zip 2016-06-05 20:58:46 +0000 and tracking/media/tracking/img/Flags.zip 1970-01-01 00:00:00 +0000 differ
3494=== removed file 'tracking/media/tracking/img/famfamfam_flag_icons.zip'
3495Binary files tracking/media/tracking/img/famfamfam_flag_icons.zip 2016-06-05 20:58:46 +0000 and tracking/media/tracking/img/famfamfam_flag_icons.zip 1970-01-01 00:00:00 +0000 differ
3496=== removed directory 'tracking/media/tracking/js'
3497=== removed file 'tracking/media/tracking/js/jquery-1.4.4.min.js'
3498--- tracking/media/tracking/js/jquery-1.4.4.min.js 2016-06-05 20:58:46 +0000
3499+++ tracking/media/tracking/js/jquery-1.4.4.min.js 1970-01-01 00:00:00 +0000
3500@@ -1,167 +0,0 @@
3501-/*!
3502- * jQuery JavaScript Library v1.4.4
3503- * http://jquery.com/
3504- *
3505- * Copyright 2010, John Resig
3506- * Dual licensed under the MIT or GPL Version 2 licenses.
3507- * http://jquery.org/license
3508- *
3509- * Includes Sizzle.js
3510- * http://sizzlejs.com/
3511- * Copyright 2010, The Dojo Foundation
3512- * Released under the MIT, BSD, and GPL Licenses.
3513- *
3514- * Date: Thu Nov 11 19:04:53 2010 -0500
3515- */
3516-(function(E,B){function ka(a,b,d){if(d===B&&a.nodeType===1){d=a.getAttribute("data-"+b);if(typeof d==="string"){try{d=d==="true"?true:d==="false"?false:d==="null"?null:!c.isNaN(d)?parseFloat(d):Ja.test(d)?c.parseJSON(d):d}catch(e){}c.data(a,b,d)}else d=B}return d}function U(){return false}function ca(){return true}function la(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ka(a){var b,d,e,f,h,l,k,o,x,r,A,C=[];f=[];h=c.data(this,this.nodeType?"events":"__events__");if(typeof h==="function")h=
3517-h.events;if(!(a.liveFired===this||!h||!h.live||a.button&&a.type==="click")){if(a.namespace)A=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var J=h.live.slice(0);for(k=0;k<J.length;k++){h=J[k];h.origType.replace(X,"")===a.type?f.push(h.selector):J.splice(k--,1)}f=c(a.target).closest(f,a.currentTarget);o=0;for(x=f.length;o<x;o++){r=f[o];for(k=0;k<J.length;k++){h=J[k];if(r.selector===h.selector&&(!A||A.test(h.namespace))){l=r.elem;e=null;if(h.preType==="mouseenter"||
3518-h.preType==="mouseleave"){a.type=h.preType;e=c(a.relatedTarget).closest(h.selector)[0]}if(!e||e!==l)C.push({elem:l,handleObj:h,level:r.level})}}}o=0;for(x=C.length;o<x;o++){f=C[o];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;a.handleObj=f.handleObj;A=f.handleObj.origHandler.apply(f.elem,arguments);if(A===false||a.isPropagationStopped()){d=f.level;if(A===false)b=false;if(a.isImmediatePropagationStopped())break}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(La,
3519-"`").replace(Ma,"&")}function ma(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Na.test(b))return c.filter(b,e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function na(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,
3520-e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var l in e[h])c.event.add(this,h,e[h][l],e[h][l].data)}}})}function Oa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function oa(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?Pa:Qa,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,
3521-"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function da(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Ra.test(a)?e(a,h):da(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?e(a,""):c.each(b,function(f,h){da(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(pa.concat.apply([],pa.slice(0,b)),function(){d[this]=a});return d}function qa(a){if(!ea[a]){var b=c("<"+
3522-a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";ea[a]=d}return ea[a]}function fa(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var t=E.document,c=function(){function a(){if(!b.isReady){try{t.documentElement.doScroll("left")}catch(j){setTimeout(a,1);return}b.ready()}}var b=function(j,s){return new b.fn.init(j,s)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,l=/\S/,k=/^\s+/,o=/\s+$/,x=/\W/,r=/\d/,A=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,
3523-C=/^[\],:{}\s]*$/,J=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,I=/(?:^|:|,)(?:\s*\[)+/g,L=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,i=/(msie) ([\w.]+)/,n=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,q=[],u,y=Object.prototype.toString,F=Object.prototype.hasOwnProperty,M=Array.prototype.push,N=Array.prototype.slice,O=String.prototype.trim,D=Array.prototype.indexOf,R={};b.fn=b.prototype={init:function(j,
3524-s){var v,z,H;if(!j)return this;if(j.nodeType){this.context=this[0]=j;this.length=1;return this}if(j==="body"&&!s&&t.body){this.context=t;this[0]=t.body;this.selector="body";this.length=1;return this}if(typeof j==="string")if((v=h.exec(j))&&(v[1]||!s))if(v[1]){H=s?s.ownerDocument||s:t;if(z=A.exec(j))if(b.isPlainObject(s)){j=[t.createElement(z[1])];b.fn.attr.call(j,s,true)}else j=[H.createElement(z[1])];else{z=b.buildFragment([v[1]],[H]);j=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,
3525-j)}else{if((z=t.getElementById(v[2]))&&z.parentNode){if(z.id!==v[2])return f.find(j);this.length=1;this[0]=z}this.context=t;this.selector=j;return this}else if(!s&&!x.test(j)){this.selector=j;this.context=t;j=t.getElementsByTagName(j);return b.merge(this,j)}else return!s||s.jquery?(s||f).find(j):b(s).find(j);else if(b.isFunction(j))return f.ready(j);if(j.selector!==B){this.selector=j.selector;this.context=j.context}return b.makeArray(j,this)},selector:"",jquery:"1.4.4",length:0,size:function(){return this.length},
3526-toArray:function(){return N.call(this,0)},get:function(j){return j==null?this.toArray():j<0?this.slice(j)[0]:this[j]},pushStack:function(j,s,v){var z=b();b.isArray(j)?M.apply(z,j):b.merge(z,j);z.prevObject=this;z.context=this.context;if(s==="find")z.selector=this.selector+(this.selector?" ":"")+v;else if(s)z.selector=this.selector+"."+s+"("+v+")";return z},each:function(j,s){return b.each(this,j,s)},ready:function(j){b.bindReady();if(b.isReady)j.call(t,b);else q&&q.push(j);return this},eq:function(j){return j===
3527--1?this.slice(j):this.slice(j,+j+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(j){return this.pushStack(b.map(this,function(s,v){return j.call(s,v,s)}))},end:function(){return this.prevObject||b(null)},push:M,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var j,s,v,z,H,G=arguments[0]||{},K=1,Q=arguments.length,ga=false;
3528-if(typeof G==="boolean"){ga=G;G=arguments[1]||{};K=2}if(typeof G!=="object"&&!b.isFunction(G))G={};if(Q===K){G=this;--K}for(;K<Q;K++)if((j=arguments[K])!=null)for(s in j){v=G[s];z=j[s];if(G!==z)if(ga&&z&&(b.isPlainObject(z)||(H=b.isArray(z)))){if(H){H=false;v=v&&b.isArray(v)?v:[]}else v=v&&b.isPlainObject(v)?v:{};G[s]=b.extend(ga,v,z)}else if(z!==B)G[s]=z}return G};b.extend({noConflict:function(j){E.$=e;if(j)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(j){j===true&&b.readyWait--;
3529-if(!b.readyWait||j!==true&&!b.isReady){if(!t.body)return setTimeout(b.ready,1);b.isReady=true;if(!(j!==true&&--b.readyWait>0))if(q){var s=0,v=q;for(q=null;j=v[s++];)j.call(t,b);b.fn.trigger&&b(t).trigger("ready").unbind("ready")}}},bindReady:function(){if(!p){p=true;if(t.readyState==="complete")return setTimeout(b.ready,1);if(t.addEventListener){t.addEventListener("DOMContentLoaded",u,false);E.addEventListener("load",b.ready,false)}else if(t.attachEvent){t.attachEvent("onreadystatechange",u);E.attachEvent("onload",
3530-b.ready);var j=false;try{j=E.frameElement==null}catch(s){}t.documentElement.doScroll&&j&&a()}}},isFunction:function(j){return b.type(j)==="function"},isArray:Array.isArray||function(j){return b.type(j)==="array"},isWindow:function(j){return j&&typeof j==="object"&&"setInterval"in j},isNaN:function(j){return j==null||!r.test(j)||isNaN(j)},type:function(j){return j==null?String(j):R[y.call(j)]||"object"},isPlainObject:function(j){if(!j||b.type(j)!=="object"||j.nodeType||b.isWindow(j))return false;if(j.constructor&&
3531-!F.call(j,"constructor")&&!F.call(j.constructor.prototype,"isPrototypeOf"))return false;for(var s in j);return s===B||F.call(j,s)},isEmptyObject:function(j){for(var s in j)return false;return true},error:function(j){throw j;},parseJSON:function(j){if(typeof j!=="string"||!j)return null;j=b.trim(j);if(C.test(j.replace(J,"@").replace(w,"]").replace(I,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(j):(new Function("return "+j))();else b.error("Invalid JSON: "+j)},noop:function(){},globalEval:function(j){if(j&&
3532-l.test(j)){var s=t.getElementsByTagName("head")[0]||t.documentElement,v=t.createElement("script");v.type="text/javascript";if(b.support.scriptEval)v.appendChild(t.createTextNode(j));else v.text=j;s.insertBefore(v,s.firstChild);s.removeChild(v)}},nodeName:function(j,s){return j.nodeName&&j.nodeName.toUpperCase()===s.toUpperCase()},each:function(j,s,v){var z,H=0,G=j.length,K=G===B||b.isFunction(j);if(v)if(K)for(z in j){if(s.apply(j[z],v)===false)break}else for(;H<G;){if(s.apply(j[H++],v)===false)break}else if(K)for(z in j){if(s.call(j[z],
3533-z,j[z])===false)break}else for(v=j[0];H<G&&s.call(v,H,v)!==false;v=j[++H]);return j},trim:O?function(j){return j==null?"":O.call(j)}:function(j){return j==null?"":j.toString().replace(k,"").replace(o,"")},makeArray:function(j,s){var v=s||[];if(j!=null){var z=b.type(j);j.length==null||z==="string"||z==="function"||z==="regexp"||b.isWindow(j)?M.call(v,j):b.merge(v,j)}return v},inArray:function(j,s){if(s.indexOf)return s.indexOf(j);for(var v=0,z=s.length;v<z;v++)if(s[v]===j)return v;return-1},merge:function(j,
3534-s){var v=j.length,z=0;if(typeof s.length==="number")for(var H=s.length;z<H;z++)j[v++]=s[z];else for(;s[z]!==B;)j[v++]=s[z++];j.length=v;return j},grep:function(j,s,v){var z=[],H;v=!!v;for(var G=0,K=j.length;G<K;G++){H=!!s(j[G],G);v!==H&&z.push(j[G])}return z},map:function(j,s,v){for(var z=[],H,G=0,K=j.length;G<K;G++){H=s(j[G],G,v);if(H!=null)z[z.length]=H}return z.concat.apply([],z)},guid:1,proxy:function(j,s,v){if(arguments.length===2)if(typeof s==="string"){v=j;j=v[s];s=B}else if(s&&!b.isFunction(s)){v=
3535-s;s=B}if(!s&&j)s=function(){return j.apply(v||this,arguments)};if(j)s.guid=j.guid=j.guid||s.guid||b.guid++;return s},access:function(j,s,v,z,H,G){var K=j.length;if(typeof s==="object"){for(var Q in s)b.access(j,Q,s[Q],z,H,v);return j}if(v!==B){z=!G&&z&&b.isFunction(v);for(Q=0;Q<K;Q++)H(j[Q],s,z?v.call(j[Q],Q,H(j[Q],s)):v,G);return j}return K?H(j[0],s):B},now:function(){return(new Date).getTime()},uaMatch:function(j){j=j.toLowerCase();j=L.exec(j)||g.exec(j)||i.exec(j)||j.indexOf("compatible")<0&&n.exec(j)||
3536-[];return{browser:j[1]||"",version:j[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(j,s){R["[object "+s+"]"]=s.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=m.version}if(b.browser.webkit)b.browser.safari=true;if(D)b.inArray=function(j,s){return D.call(s,j)};if(!/\s/.test("\u00a0")){k=/^[\s\xA0]+/;o=/[\s\xA0]+$/}f=b(t);if(t.addEventListener)u=function(){t.removeEventListener("DOMContentLoaded",u,
3537-false);b.ready()};else if(t.attachEvent)u=function(){if(t.readyState==="complete"){t.detachEvent("onreadystatechange",u);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=t.documentElement,b=t.createElement("script"),d=t.createElement("div"),e="script"+c.now();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],l=t.createElement("select"),
3538-k=l.appendChild(t.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:k.selected,deleteExpando:true,optDisabled:false,checkClone:false,
3539-scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};l.disabled=true;c.support.optDisabled=!k.disabled;b.type="text/javascript";try{b.appendChild(t.createTextNode("window."+e+"=1;"))}catch(o){}a.insertBefore(b,a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}try{delete b.test}catch(x){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function r(){c.support.noCloneEvent=
3540-false;d.detachEvent("onclick",r)});d.cloneNode(true).fireEvent("onclick")}d=t.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=t.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var r=t.createElement("div");r.style.width=r.style.paddingLeft="1px";t.body.appendChild(r);c.boxModel=c.support.boxModel=r.offsetWidth===2;if("zoom"in r.style){r.style.display="inline";r.style.zoom=
3541-1;c.support.inlineBlockNeedsLayout=r.offsetWidth===2;r.style.display="";r.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=r.offsetWidth!==2}r.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var A=r.getElementsByTagName("td");c.support.reliableHiddenOffsets=A[0].offsetHeight===0;A[0].style.display="";A[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&A[0].offsetHeight===0;r.innerHTML="";t.body.removeChild(r).style.display=
3542-"none"});a=function(r){var A=t.createElement("div");r="on"+r;var C=r in A;if(!C){A.setAttribute(r,"return;");C=typeof A[r]==="function"}return C};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();var ra={},Ja=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?ra:a;var e=a.nodeType,f=e?a[c.expando]:null,h=
3543-c.cache;if(!(e&&!f&&typeof b==="string"&&d===B)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==B)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?ra:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);
3544-else if(d)delete f[e];else for(var l in a)delete a[l]}},acceptData:function(a){if(a.nodeName){var b=c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){var d=null;if(typeof a==="undefined"){if(this.length){var e=this[0].attributes,f;d=c.data(this[0]);for(var h=0,l=e.length;h<l;h++){f=e[h].name;if(f.indexOf("data-")===0){f=f.substr(5);ka(this[0],f,d[f])}}}return d}else if(typeof a==="object")return this.each(function(){c.data(this,
3545-a)});var k=a.split(".");k[1]=k[1]?"."+k[1]:"";if(b===B){d=this.triggerHandler("getData"+k[1]+"!",[k[0]]);if(d===B&&this.length){d=c.data(this[0],a);d=ka(this[0],a,d)}return d===B&&k[1]?this.data(k[0]):d}else return this.each(function(){var o=c(this),x=[k[0],b];o.triggerHandler("setData"+k[1]+"!",x);c.data(this,a,b);o.triggerHandler("changeData"+k[1]+"!",x)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=
3546-c.data(a,b);if(!d)return e||[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===B)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
3547-a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var sa=/[\n\t]/g,ha=/\s+/,Sa=/\r/g,Ta=/^(?:href|src|style)$/,Ua=/^(?:button|input)$/i,Va=/^(?:button|input|object|select|textarea)$/i,Wa=/^a(?:rea)?$/i,ta=/^(?:radio|checkbox)$/i;c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",
3548-colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(x){var r=c(this);r.addClass(a.call(this,x,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===
3549-1)if(f.className){for(var h=" "+f.className+" ",l=f.className,k=0,o=b.length;k<o;k++)if(h.indexOf(" "+b[k]+" ")<0)l+=" "+b[k];f.className=c.trim(l)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(o){var x=c(this);x.removeClass(a.call(this,o,x.attr("class")))});if(a&&typeof a==="string"||a===B)for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(sa," "),
3550-l=0,k=b.length;l<k;l++)h=h.replace(" "+b[l]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,l=c(this),k=b,o=a.split(ha);f=o[h++];){k=e?k:!l.hasClass(f);l[k?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,
3551-"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(sa," ").indexOf(a)>-1)return true;return false},val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";
3552-if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var l=f[h];if(l.selected&&(c.support.optDisabled?!l.disabled:l.getAttribute("disabled")===null)&&(!l.parentNode.disabled||!c.nodeName(l.parentNode,"optgroup"))){a=c(l).val();if(b)return a;d.push(a)}}return d}if(ta.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Sa,"")}return B}var k=c.isFunction(a);return this.each(function(o){var x=c(this),r=a;if(this.nodeType===1){if(k)r=
3553-a.call(this,o,x.val());if(r==null)r="";else if(typeof r==="number")r+="";else if(c.isArray(r))r=c.map(r,function(C){return C==null?"":C+""});if(c.isArray(r)&&ta.test(this.type))this.checked=c.inArray(x.val(),r)>=0;else if(c.nodeName(this,"select")){var A=c.makeArray(r);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),A)>=0});if(!A.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},
3554-attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return B;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==B;b=e&&c.props[b]||b;var h=Ta.test(b);if((b in a||a[b]!==B)&&e&&!h){if(f){b==="type"&&Ua.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&
3555-b.specified?b.value:Va.test(a.nodeName)||Wa.test(a.nodeName)&&a.href?0:B;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return B;a=!c.support.hrefNormalized&&e&&h?a.getAttribute(b,2):a.getAttribute(b);return a===null?B:a}});var X=/\.(.*)$/,ia=/^(?:textarea|input|select)$/i,La=/\./g,Ma=/ /g,Xa=/[^\w\s.|`]/g,Ya=function(a){return a.replace(Xa,"\\$&")},ua={focusin:0,focusout:0};
3556-c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;else if(!d)return;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var l=a.nodeType?"events":"__events__",k=h[l],o=h.handle;if(typeof k==="function"){o=k.handle;k=k.events}else if(!k){a.nodeType||(h[l]=h=function(){});h.events=k={}}if(!o)h.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,
3557-arguments):B};o.elem=a;b=b.split(" ");for(var x=0,r;l=b[x++];){h=f?c.extend({},f):{handler:d,data:e};if(l.indexOf(".")>-1){r=l.split(".");l=r.shift();h.namespace=r.slice(0).sort().join(".")}else{r=[];h.namespace=""}h.type=l;if(!h.guid)h.guid=d.guid;var A=k[l],C=c.event.special[l]||{};if(!A){A=k[l]=[];if(!C.setup||C.setup.call(a,e,r,o)===false)if(a.addEventListener)a.addEventListener(l,o,false);else a.attachEvent&&a.attachEvent("on"+l,o)}if(C.add){C.add.call(a,h);if(!h.handler.guid)h.handler.guid=
3558-d.guid}A.push(h);c.event.global[l]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,l=0,k,o,x,r,A,C,J=a.nodeType?"events":"__events__",w=c.data(a),I=w&&w[J];if(w&&I){if(typeof I==="function"){w=I;I=I.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in I)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[l++];){r=f;k=f.indexOf(".")<0;o=[];if(!k){o=f.split(".");f=o.shift();x=RegExp("(^|\\.)"+
3559-c.map(o.slice(0).sort(),Ya).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(A=I[f])if(d){r=c.event.special[f]||{};for(h=e||0;h<A.length;h++){C=A[h];if(d.guid===C.guid){if(k||x.test(C.namespace)){e==null&&A.splice(h--,1);r.remove&&r.remove.call(a,C)}if(e!=null)break}}if(A.length===0||e!=null&&A.length===1){if(!r.teardown||r.teardown.call(a,o)===false)c.removeEvent(a,f,w.handle);delete I[f]}}else for(h=0;h<A.length;h++){C=A[h];if(k||x.test(C.namespace)){c.event.remove(a,r,C.handler,h);A.splice(h--,1)}}}if(c.isEmptyObject(I)){if(b=
3560-w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,J);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===
3561-8)return B;a.result=B;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){var l;e=a.target;var k=f.replace(X,""),o=c.nodeName(e,"a")&&k===
3562-"click",x=c.event.special[k]||{};if((!x._default||x._default.call(d,a)===false)&&!o&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[k]){if(l=e["on"+k])e["on"+k]=null;c.event.triggered=true;e[k]()}}catch(r){}if(l)e["on"+k]=l;c.event.triggered=false}}},handle:function(a){var b,d,e,f;d=[];var h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+
3563-d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var l=d.length;f<l;f++){var k=d[f];if(b||e.test(k.namespace)){a.handler=k.handler;a.data=k.data;a.handleObj=k;k=k.handler.apply(this,h);if(k!==B){a.result=k;if(k===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
3564-fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||t;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=t.documentElement;d=t.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
3565-d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==B)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ka,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
3566-Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=t.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
3567-c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ca;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ca;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ca;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
3568-var va=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},wa=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?wa:va,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?wa:va)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
3569-"form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=B;return la("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=B;return la("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
3570-xa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ia.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=xa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===B||f===e))if(e!=null||f){a.type="change";a.liveFired=
3571-B;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",xa(a))}},setup:function(){if(this.type===
3572-"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ia.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ia.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}t.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){ua[b]++===0&&t.addEventListener(a,d,true)},teardown:function(){--ua[b]===
3573-0&&t.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=B}var l=b==="one"?c.proxy(f,function(o){c(this).unbind(o,l);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var k=this.length;h<k;h++)c.event.add(this[h],d,l,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
3574-a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
3575-1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var ya={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var l,k=0,o,x,r=h||this.selector;h=h?this:c(this.context);if(typeof d===
3576-"object"&&!d.preventDefault){for(l in d)h[b](l,e,d[l],r);return this}if(c.isFunction(e)){f=e;e=B}for(d=(d||"").split(" ");(l=d[k++])!=null;){o=X.exec(l);x="";if(o){x=o[0];l=l.replace(X,"")}if(l==="hover")d.push("mouseenter"+x,"mouseleave"+x);else{o=l;if(l==="focus"||l==="blur"){d.push(ya[l]+x);l+=x}else l=(ya[l]||l)+x;if(b==="live"){x=0;for(var A=h.length;x<A;x++)c.event.add(h[x],"live."+Y(l,r),{data:e,selector:r,handler:f,origType:l,origHandler:f,preType:o})}else h.unbind("live."+Y(l,r),f)}}return this}});
3577-c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
3578-(function(){function a(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1&&!q){y.sizcache=n;y.sizset=p}if(y.nodeName.toLowerCase()===i){F=y;break}y=y[g]}m[p]=F}}}function b(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1){if(!q){y.sizcache=n;y.sizset=p}if(typeof i!=="string"){if(y===i){F=true;break}}else if(k.filter(i,
3579-[y]).length>0){F=y;break}}y=y[g]}m[p]=F}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,l=true;[0,0].sort(function(){l=false;return 0});var k=function(g,i,n,m){n=n||[];var p=i=i||t;if(i.nodeType!==1&&i.nodeType!==9)return[];if(!g||typeof g!=="string")return n;var q,u,y,F,M,N=true,O=k.isXML(i),D=[],R=g;do{d.exec("");if(q=d.exec(R)){R=q[3];D.push(q[1]);if(q[2]){F=q[3];
3580-break}}}while(q);if(D.length>1&&x.exec(g))if(D.length===2&&o.relative[D[0]])u=L(D[0]+D[1],i);else for(u=o.relative[D[0]]?[i]:k(D.shift(),i);D.length;){g=D.shift();if(o.relative[g])g+=D.shift();u=L(g,u)}else{if(!m&&D.length>1&&i.nodeType===9&&!O&&o.match.ID.test(D[0])&&!o.match.ID.test(D[D.length-1])){q=k.find(D.shift(),i,O);i=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]}if(i){q=m?{expr:D.pop(),set:C(m)}:k.find(D.pop(),D.length===1&&(D[0]==="~"||D[0]==="+")&&i.parentNode?i.parentNode:i,O);u=q.expr?k.filter(q.expr,
3581-q.set):q.set;if(D.length>0)y=C(u);else N=false;for(;D.length;){q=M=D.pop();if(o.relative[M])q=D.pop();else M="";if(q==null)q=i;o.relative[M](y,q,O)}}else y=[]}y||(y=u);y||k.error(M||g);if(f.call(y)==="[object Array]")if(N)if(i&&i.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&k.contains(i,y[g])))n.push(u[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&n.push(u[g]);else n.push.apply(n,y);else C(y,n);if(F){k(F,p,n,m);k.uniqueSort(n)}return n};k.uniqueSort=function(g){if(w){h=
3582-l;g.sort(w);if(h)for(var i=1;i<g.length;i++)g[i]===g[i-1]&&g.splice(i--,1)}return g};k.matches=function(g,i){return k(g,null,null,i)};k.matchesSelector=function(g,i){return k(i,null,null,[g]).length>0};k.find=function(g,i,n){var m;if(!g)return[];for(var p=0,q=o.order.length;p<q;p++){var u,y=o.order[p];if(u=o.leftMatch[y].exec(g)){var F=u[1];u.splice(1,1);if(F.substr(F.length-1)!=="\\"){u[1]=(u[1]||"").replace(/\\/g,"");m=o.find[y](u,i,n);if(m!=null){g=g.replace(o.match[y],"");break}}}}m||(m=i.getElementsByTagName("*"));
3583-return{set:m,expr:g}};k.filter=function(g,i,n,m){for(var p,q,u=g,y=[],F=i,M=i&&i[0]&&k.isXML(i[0]);g&&i.length;){for(var N in o.filter)if((p=o.leftMatch[N].exec(g))!=null&&p[2]){var O,D,R=o.filter[N];D=p[1];q=false;p.splice(1,1);if(D.substr(D.length-1)!=="\\"){if(F===y)y=[];if(o.preFilter[N])if(p=o.preFilter[N](p,F,n,y,m,M)){if(p===true)continue}else q=O=true;if(p)for(var j=0;(D=F[j])!=null;j++)if(D){O=R(D,p,j,F);var s=m^!!O;if(n&&O!=null)if(s)q=true;else F[j]=false;else if(s){y.push(D);q=true}}if(O!==
3584-B){n||(F=y);g=g.replace(o.match[N],"");if(!q)return[];break}}}if(g===u)if(q==null)k.error(g);else break;u=g}return F};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var o=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
3585-POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,i){var n=typeof i==="string",m=n&&!/\W/.test(i);n=n&&!m;if(m)i=i.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=n||q&&q.nodeName.toLowerCase()===
3586-i?q||false:q===i}n&&k.filter(i,g,true)},">":function(g,i){var n,m=typeof i==="string",p=0,q=g.length;if(m&&!/\W/.test(i))for(i=i.toLowerCase();p<q;p++){if(n=g[p]){n=n.parentNode;g[p]=n.nodeName.toLowerCase()===i?n:false}}else{for(;p<q;p++)if(n=g[p])g[p]=m?n.parentNode:n.parentNode===i;m&&k.filter(i,g,true)}},"":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=i=i.toLowerCase();q=a}q("parentNode",i,p,g,m,n)},"~":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=
3587-i=i.toLowerCase();q=a}q("previousSibling",i,p,g,m,n)}},find:{ID:function(g,i,n){if(typeof i.getElementById!=="undefined"&&!n)return(g=i.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,i){if(typeof i.getElementsByName!=="undefined"){for(var n=[],m=i.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&n.push(m[p]);return n.length===0?null:n}},TAG:function(g,i){return i.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,i,n,m,p,q){g=" "+g[1].replace(/\\/g,
3588-"")+" ";if(q)return g;q=0;for(var u;(u=i[q])!=null;q++)if(u)if(p^(u.className&&(" "+u.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))n||m.push(u);else if(n)i[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var i=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=i[1]+(i[2]||1)-0;g[3]=i[3]-0}g[0]=e++;return g},ATTR:function(g,i,n,
3589-m,p,q){i=g[1].replace(/\\/g,"");if(!q&&o.attrMap[i])g[1]=o.attrMap[i];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,i,n,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,i);else{g=k.filter(g[3],i,n,true^p);n||m.push.apply(m,g);return false}else if(o.match.POS.test(g[0])||o.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
3590-true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,i,n){return!!k(n[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
3591-g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,i){return i===0},last:function(g,i,n,m){return i===m.length-1},even:function(g,i){return i%2===0},odd:function(g,i){return i%2===1},lt:function(g,i,n){return i<n[3]-0},gt:function(g,i,n){return i>n[3]-0},nth:function(g,i,n){return n[3]-
3592-0===i},eq:function(g,i,n){return n[3]-0===i}},filter:{PSEUDO:function(g,i,n,m){var p=i[1],q=o.filters[p];if(q)return q(g,n,i,m);else if(p==="contains")return(g.textContent||g.innerText||k.getText([g])||"").indexOf(i[3])>=0;else if(p==="not"){i=i[3];n=0;for(m=i.length;n<m;n++)if(i[n]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,i){var n=i[1],m=g;switch(n){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(n===
3593-"first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":n=i[2];var p=i[3];if(n===1&&p===0)return true;var q=i[0],u=g.parentNode;if(u&&(u.sizcache!==q||!g.nodeIndex)){var y=0;for(m=u.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++y;u.sizcache=q}m=g.nodeIndex-p;return n===0?m===0:m%n===0&&m/n>=0}},ID:function(g,i){return g.nodeType===1&&g.getAttribute("id")===i},TAG:function(g,i){return i==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
3594-i},CLASS:function(g,i){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(i)>-1},ATTR:function(g,i){var n=i[1];n=o.attrHandle[n]?o.attrHandle[n](g):g[n]!=null?g[n]:g.getAttribute(n);var m=n+"",p=i[2],q=i[4];return n==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&n!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,i,n,m){var p=o.setFilters[i[2]];
3595-if(p)return p(g,n,i,m)}}},x=o.match.POS,r=function(g,i){return"\\"+(i-0+1)},A;for(A in o.match){o.match[A]=RegExp(o.match[A].source+/(?![^\[]*\])(?![^\(]*\))/.source);o.leftMatch[A]=RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[A].source.replace(/\\(\d+)/g,r))}var C=function(g,i){g=Array.prototype.slice.call(g,0);if(i){i.push.apply(i,g);return i}return g};try{Array.prototype.slice.call(t.documentElement.childNodes,0)}catch(J){C=function(g,i){var n=0,m=i||[];if(f.call(g)==="[object Array]")Array.prototype.push.apply(m,
3596-g);else if(typeof g.length==="number")for(var p=g.length;n<p;n++)m.push(g[n]);else for(;g[n];n++)m.push(g[n]);return m}}var w,I;if(t.documentElement.compareDocumentPosition)w=function(g,i){if(g===i){h=true;return 0}if(!g.compareDocumentPosition||!i.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(i)&4?-1:1};else{w=function(g,i){var n,m,p=[],q=[];n=g.parentNode;m=i.parentNode;var u=n;if(g===i){h=true;return 0}else if(n===m)return I(g,i);else if(n){if(!m)return 1}else return-1;
3597-for(;u;){p.unshift(u);u=u.parentNode}for(u=m;u;){q.unshift(u);u=u.parentNode}n=p.length;m=q.length;for(u=0;u<n&&u<m;u++)if(p[u]!==q[u])return I(p[u],q[u]);return u===n?I(g,q[u],-1):I(p[u],i,1)};I=function(g,i,n){if(g===i)return n;for(g=g.nextSibling;g;){if(g===i)return-1;g=g.nextSibling}return 1}}k.getText=function(g){for(var i="",n,m=0;g[m];m++){n=g[m];if(n.nodeType===3||n.nodeType===4)i+=n.nodeValue;else if(n.nodeType!==8)i+=k.getText(n.childNodes)}return i};(function(){var g=t.createElement("div"),
3598-i="script"+(new Date).getTime(),n=t.documentElement;g.innerHTML="<a name='"+i+"'/>";n.insertBefore(g,n.firstChild);if(t.getElementById(i)){o.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:B:[]};o.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}n.removeChild(g);
3599-n=g=null})();(function(){var g=t.createElement("div");g.appendChild(t.createComment(""));if(g.getElementsByTagName("*").length>0)o.find.TAG=function(i,n){var m=n.getElementsByTagName(i[1]);if(i[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")o.attrHandle.href=function(i){return i.getAttribute("href",2)};g=null})();t.querySelectorAll&&
3600-function(){var g=k,i=t.createElement("div");i.innerHTML="<p class='TEST'></p>";if(!(i.querySelectorAll&&i.querySelectorAll(".TEST").length===0)){k=function(m,p,q,u){p=p||t;m=m.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!u&&!k.isXML(p))if(p.nodeType===9)try{return C(p.querySelectorAll(m),q)}catch(y){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var F=p.getAttribute("id"),M=F||"__sizzle__";F||p.setAttribute("id",M);try{return C(p.querySelectorAll("#"+M+" "+m),q)}catch(N){}finally{F||
3601-p.removeAttribute("id")}}return g(m,p,q,u)};for(var n in g)k[n]=g[n];i=null}}();(function(){var g=t.documentElement,i=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,n=false;try{i.call(t.documentElement,"[test!='']:sizzle")}catch(m){n=true}if(i)k.matchesSelector=function(p,q){q=q.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(p))try{if(n||!o.match.PSEUDO.test(q)&&!/!=/.test(q))return i.call(p,q)}catch(u){}return k(q,null,null,[p]).length>0}})();(function(){var g=
3602-t.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){o.order.splice(1,0,"CLASS");o.find.CLASS=function(i,n,m){if(typeof n.getElementsByClassName!=="undefined"&&!m)return n.getElementsByClassName(i[1])};g=null}}})();k.contains=t.documentElement.contains?function(g,i){return g!==i&&(g.contains?g.contains(i):true)}:t.documentElement.compareDocumentPosition?
3603-function(g,i){return!!(g.compareDocumentPosition(i)&16)}:function(){return false};k.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var L=function(g,i){for(var n,m=[],p="",q=i.nodeType?[i]:i;n=o.match.PSEUDO.exec(g);){p+=n[0];g=g.replace(o.match.PSEUDO,"")}g=o.relative[g]?g+"*":g;n=0;for(var u=q.length;n<u;n++)k(g,q[n],m);return k.filter(p,m)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=k.getText;c.isXMLDoc=k.isXML;
3604-c.contains=k.contains})();var Za=/Until$/,$a=/^(?:parents|prevUntil|prevAll)/,ab=/,/,Na=/^.[^:#\[\.,]*$/,bb=Array.prototype.slice,cb=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var l=0;l<d;l++)if(b[l]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},
3605-not:function(a){return this.pushStack(ma(this,a,false),"not",a)},filter:function(a){return this.pushStack(ma(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){var d=[],e,f,h=this[0];if(c.isArray(a)){var l,k={},o=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:o})}h=
3606-h.parentNode;o++}}return d}l=cb.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(l?l.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):
3607-c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,
3608-2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,
3609-b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Za.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||ab.test(e))&&$a.test(a))f=f.reverse();return this.pushStack(f,a,bb.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===B||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&
3610-e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var za=/ jQuery\d+="(?:\d+|null)"/g,$=/^\s+/,Aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Ba=/<([\w:]+)/,db=/<tbody/i,eb=/<|&#?\w+;/,Ca=/<(?:script|object|embed|option|style)/i,Da=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/\=([^="'>\s]+\/)>/g,P={option:[1,
3611-"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};P.optgroup=P.option;P.tbody=P.tfoot=P.colgroup=P.caption=P.thead;P.th=P.td;if(!c.support.htmlSerialize)P._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
3612-c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==B)return this.empty().append((this[0]&&this[0].ownerDocument||t).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
3613-wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
3614-prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
3615-this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
3616-return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(za,"").replace(fb,'="$1">').replace($,"")],e)[0]}else return this.cloneNode(true)});if(a===true){na(this,b);na(this.find("*"),b.find("*"))}return b},html:function(a){if(a===B)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(za,""):null;
3617-else if(typeof a==="string"&&!Ca.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!P[(Ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Aa,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=
3618-c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){var e,f,h,l=a[0],k=[];if(!c.support.checkClone&&arguments.length===3&&typeof l==="string"&&Da.test(l))return this.each(function(){c(this).domManip(a,
3619-b,d,true)});if(c.isFunction(l))return this.each(function(x){var r=c(this);a[0]=l.call(this,x,b?r.html():B);r.domManip(a,b,d)});if(this[0]){e=l&&l.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);h=e.fragment;if(f=h.childNodes.length===1?h=h.firstChild:h.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var o=this.length;f<o;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):
3620-this[f]:this[f],f>0||e.cacheable||this.length>1?h.cloneNode(true):h)}k.length&&c.each(k,Oa)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:t;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===t&&!Ca.test(a[0])&&(c.support.checkClone||!Da.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",
3621-prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=d.length;f<h;f++){var l=(f>0?this.clone(true):this).get();c(d[f])[b](l);e=e.concat(l)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||t;if(typeof b.createElement==="undefined")b=b.ownerDocument||
3622-b[0]&&b[0].ownerDocument||t;for(var f=[],h=0,l;(l=a[h])!=null;h++){if(typeof l==="number")l+="";if(l){if(typeof l==="string"&&!eb.test(l))l=b.createTextNode(l);else if(typeof l==="string"){l=l.replace(Aa,"<$1></$2>");var k=(Ba.exec(l)||["",""])[1].toLowerCase(),o=P[k]||P._default,x=o[0],r=b.createElement("div");for(r.innerHTML=o[1]+l+o[2];x--;)r=r.lastChild;if(!c.support.tbody){x=db.test(l);k=k==="table"&&!x?r.firstChild&&r.firstChild.childNodes:o[1]==="<table>"&&!x?r.childNodes:[];for(o=k.length-
3623-1;o>=0;--o)c.nodeName(k[o],"tbody")&&!k[o].childNodes.length&&k[o].parentNode.removeChild(k[o])}!c.support.leadingWhitespace&&$.test(l)&&r.insertBefore(b.createTextNode($.exec(l)[0]),r.firstChild);l=r.childNodes}if(l.nodeType)f.push(l);else f=c.merge(f,l)}}if(d)for(h=0;f[h];h++)if(e&&c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));
3624-d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,l=0,k;(k=a[l])!=null;l++)if(!(k.nodeName&&c.noData[k.nodeName.toLowerCase()]))if(d=k[c.expando]){if((b=e[d])&&b.events)for(var o in b.events)f[o]?c.event.remove(k,o):c.removeEvent(k,o,b.handle);if(h)delete k[c.expando];else k.removeAttribute&&k.removeAttribute(c.expando);delete e[d]}}});var Ea=/alpha\([^)]*\)/i,gb=/opacity=([^)]*)/,hb=/-([a-z])/ig,ib=/([A-Z])/g,Fa=/^-?\d+(?:px)?$/i,
3625-jb=/^-?\d/,kb={position:"absolute",visibility:"hidden",display:"block"},Pa=["Left","Right"],Qa=["Top","Bottom"],W,Ga,aa,lb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===B)return this;return c.access(this,a,b,true,function(d,e,f){return f!==B?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,
3626-zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),l=a.style,k=c.cssHooks[h];b=c.cssProps[h]||h;if(d!==B){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!k||!("set"in k)||(d=k.set(a,d))!==B)try{l[b]=d}catch(o){}}}else{if(k&&"get"in k&&(f=k.get(a,false,e))!==B)return f;return l[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),
3627-h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==B)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=e[f]},camelCase:function(a){return a.replace(hb,lb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=oa(d,b,f);else c.swap(d,kb,function(){h=oa(d,b,f)});if(h<=0){h=W(d,b,b);if(h==="0px"&&aa)h=aa(d,b,b);
3628-if(h!=null)return h===""||h==="auto"?"0px":h}if(h<0||h==null){h=d.style[b];return h===""||h==="auto"?"0px":h}return typeof h==="string"?h:h+"px"}},set:function(d,e){if(Fa.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return gb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=
3629-d.filter||"";d.filter=Ea.test(f)?f.replace(Ea,e):d.filter+" "+e}};if(t.defaultView&&t.defaultView.getComputedStyle)Ga=function(a,b,d){var e;d=d.replace(ib,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return B;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};if(t.documentElement.currentStyle)aa=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],h=a.style;if(!Fa.test(f)&&jb.test(f)){d=h.left;
3630-e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f===""?"auto":f};W=Ga||aa;if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var mb=c.now(),nb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
3631-ob=/^(?:select|textarea)/i,pb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,qb=/^(?:GET|HEAD)$/,Ra=/\[\]$/,T=/\=\?(&|$)/,ja=/\?/,rb=/([?&])_=[^&]*/,sb=/^(\w+:)?\/\/([^\/?#]+)/,tb=/%20/g,ub=/#.*$/,Ha=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ha)return Ha.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b===
3632-"object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(l,k){if(k==="success"||k==="notmodified")h.html(f?c("<div>").append(l.responseText.replace(nb,"")).find(f):l.responseText);d&&h.each(d,[l.responseText,k,l])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
3633-!this.disabled&&(this.checked||ob.test(this.nodeName)||pb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
3634-getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
3635-script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),l=qb.test(h);b.url=b.url.replace(ub,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ja.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
3636-!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+mb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var k=E[d];E[d]=function(m){if(c.isFunction(k))k(m);else{E[d]=B;try{delete E[d]}catch(p){}}f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);r&&r.removeChild(A)}}if(b.dataType==="script"&&b.cache===null)b.cache=
3637-false;if(b.cache===false&&l){var o=c.now(),x=b.url.replace(rb,"$1_="+o);b.url=x+(x===b.url?(ja.test(b.url)?"&":"?")+"_="+o:"")}if(b.data&&l)b.url+=(ja.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");o=(o=sb.exec(b.url))&&(o[1]&&o[1].toLowerCase()!==location.protocol||o[2].toLowerCase()!==location.host);if(b.dataType==="script"&&h==="GET"&&o){var r=t.getElementsByTagName("head")[0]||t.documentElement,A=t.createElement("script");if(b.scriptCharset)A.charset=b.scriptCharset;
3638-A.src=b.url;if(!d){var C=false;A.onload=A.onreadystatechange=function(){if(!C&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){C=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);A.onload=A.onreadystatechange=null;r&&A.parentNode&&r.removeChild(A)}}}r.insertBefore(A,r.firstChild);return B}var J=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!l||a&&a.contentType)w.setRequestHeader("Content-Type",
3639-b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}o||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(I){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
3640-c.triggerGlobal(b,"ajaxSend",[w,b]);var L=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){J||c.handleComplete(b,w,e,f);J=true;if(w)w.onreadystatechange=c.noop}else if(!J&&w&&(w.readyState===4||m==="timeout")){J=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
3641-c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&Function.prototype.call.call(g,w);L("abort")}}catch(i){}b.async&&b.timeout>0&&setTimeout(function(){w&&!J&&L("timeout")},b.timeout);try{w.send(l||b.data==null?null:b.data)}catch(n){c.handleError(b,w,null,n);c.handleComplete(b,w,e,f)}b.async||L();return w}},param:function(a,b){var d=[],e=function(h,l){l=c.isFunction(l)?l():l;d[d.length]=
3642-encodeURIComponent(h)+"="+encodeURIComponent(l)};if(b===B)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)da(f,a[f],b,e);return d.join("&").replace(tb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",
3643-[b,a])},handleComplete:function(a,b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),
3644-e=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});
3645-if(E.ActiveXObject)c.ajaxSettings.xhr=function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var ea={},vb=/^(?:toggle|show|hide)$/,wb=/^([+\-]=)?([\d+.\-]+)(.*)$/,ba,pa=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",
3646-3),a,b,d);else{d=0;for(var e=this.length;d<e;d++){a=this[d];b=a.style.display;if(!c.data(a,"olddisplay")&&b==="none")b=a.style.display="";b===""&&c.css(a,"display")==="none"&&c.data(a,"olddisplay",qa(a.nodeName))}for(d=0;d<e;d++){a=this[d];b=a.style.display;if(b===""||b==="none")a.style.display=c.data(a,"olddisplay")||""}return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",
3647-d)}for(a=0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,
3648-d,e);if(c.isEmptyObject(a))return this.each(f.complete);return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),l,k=this.nodeType===1,o=k&&c(this).is(":hidden"),x=this;for(l in a){var r=c.camelCase(l);if(l!==r){a[r]=a[l];delete a[l];l=r}if(a[l]==="hide"&&o||a[l]==="show"&&!o)return h.complete.call(this);if(k&&(l==="height"||l==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(qa(this.nodeName)===
3649-"inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[l])){(h.specialEasing=h.specialEasing||{})[l]=a[l][1];a[l]=a[l][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(A,C){var J=new c.fx(x,h,A);if(vb.test(C))J[C==="toggle"?o?"show":"hide":C](a);else{var w=wb.exec(C),I=J.cur()||0;if(w){var L=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(x,A,(L||1)+g);I=(L||
3650-1)/J.cur()*I;c.style(x,A,I+g)}if(w[1])L=(w[1]==="-="?-1:1)*L+I;J.custom(I,L,g)}else J.custom(I,C,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
3651-d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
3652-Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(l){return f.step(l)}
3653-var f=this,h=c.fx;this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;e.elem=this.elem;if(e()&&c.timers.push(e)&&!ba)ba=setInterval(h.tick,h.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
3654-this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(k,o){f.style["overflow"+o]=h.overflow[k]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
3655-this.options.show)for(var l in this.options.curAnim)c.style(this.elem,l,this.options.orig[l]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
3656-c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(ba);ba=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
3657-b.elem}).length};var xb=/^t(?:able|d|h)$/i,Ia=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in t.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(l){c.offset.setOffset(this,a,l)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=fa(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
3658-h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(x){c.offset.setOffset(this,a,x)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d,e=b.offsetParent,f=b.ownerDocument,h=f.documentElement,l=f.body;d=(f=f.defaultView)?f.getComputedStyle(b,null):b.currentStyle;
3659-for(var k=b.offsetTop,o=b.offsetLeft;(b=b.parentNode)&&b!==l&&b!==h;){if(c.offset.supportsFixedPosition&&d.position==="fixed")break;d=f?f.getComputedStyle(b,null):b.currentStyle;k-=b.scrollTop;o-=b.scrollLeft;if(b===e){k+=b.offsetTop;o+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&xb.test(b.nodeName))){k+=parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}e=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"){k+=
3660-parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}d=d}if(d.position==="relative"||d.position==="static"){k+=l.offsetTop;o+=l.offsetLeft}if(c.offset.supportsFixedPosition&&d.position==="fixed"){k+=Math.max(h.scrollTop,l.scrollTop);o+=Math.max(h.scrollLeft,l.scrollLeft)}return{top:k,left:o}};c.offset={initialize:function(){var a=t.body,b=t.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
3661-height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
3662-f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
3663-"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),l=c.css(a,"top"),k=c.css(a,"left"),o=e==="absolute"&&c.inArray("auto",[l,k])>-1;e={};var x={};if(o)x=f.position();l=o?x.top:parseInt(l,10)||0;k=o?x.left:parseInt(k,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+l;if(b.left!=null)e.left=b.left-h.left+k;"using"in b?b.using.call(a,
3664-e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Ia.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||t.body;a&&!Ia.test(a.nodeName)&&
3665-c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==B)return this.each(function(){if(h=fa(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=fa(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
3666-c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(l){var k=c(this);k[d](e.call(this,l,k[d]()))});if(c.isWindow(f))return f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b];else if(f.nodeType===9)return Math.max(f.documentElement["client"+
3667-b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]);else if(e===B){f=c.css(f,d);var h=parseFloat(f);return c.isNaN(h)?f:h}else return this.css(d,typeof e==="string"?e:e+"px")}})})(window);
3668
3669=== removed file 'tracking/middleware.py'
3670--- tracking/middleware.py 2016-12-13 18:28:51 +0000
3671+++ tracking/middleware.py 1970-01-01 00:00:00 +0000
3672@@ -1,195 +0,0 @@
3673-from datetime import datetime, timedelta
3674-import logging
3675-import re
3676-import traceback
3677-
3678-from django.conf import settings
3679-from django.contrib.auth.models import AnonymousUser
3680-from django.core.cache import cache
3681-from django.core.urlresolvers import reverse, NoReverseMatch
3682-from django.db.utils import DatabaseError
3683-from django.http import Http404
3684-
3685-from tracking import utils
3686-from tracking.models import Visitor, UntrackedUserAgent, BannedIP
3687-
3688-title_re = re.compile('<title>(.*?)</title>')
3689-log = logging.getLogger('tracking.middleware')
3690-
3691-
3692-class VisitorTrackingMiddleware(object):
3693- """Keeps track of your active users. Anytime a visitor accesses a valid
3694- URL, their unique record will be updated with the page they're on and the
3695- last time they requested a page.
3696-
3697- Records are considered to be unique when the session key and IP
3698- address are unique together. Sometimes the same user used to have
3699- two different records, so I added a check to see if the session key
3700- had changed for the same IP and user agent in the last 5 minutes
3701-
3702- """
3703-
3704- @property
3705- def prefixes(self):
3706- """Returns a list of URL prefixes that we should not track."""
3707-
3708- if not hasattr(self, '_prefixes'):
3709- self._prefixes = getattr(settings, 'NO_TRACKING_PREFIXES', [])
3710-
3711- if not getattr(settings, '_FREEZE_TRACKING_PREFIXES', False):
3712- for name in ('MEDIA_URL', 'STATIC_URL'):
3713- url = getattr(settings, name)
3714- if url and url != '/':
3715- self._prefixes.append(url)
3716-
3717- try:
3718- # finally, don't track requests to the tracker update pages
3719- self._prefixes.append(
3720- reverse('tracking-refresh-active-users'))
3721- except NoReverseMatch:
3722- # django-tracking hasn't been included in the URLconf if we
3723- # get here, which is not a bad thing
3724- pass
3725-
3726- settings.NO_TRACKING_PREFIXES = self._prefixes
3727- settings._FREEZE_TRACKING_PREFIXES = True
3728-
3729- return self._prefixes
3730-
3731- def process_request(self, request):
3732- # don't process AJAX requests
3733- if request.is_ajax():
3734- return
3735-
3736- # create some useful variables
3737- ip_address = utils.get_ip(request)
3738- user_agent = unicode(request.META.get(
3739- 'HTTP_USER_AGENT', '')[:255], errors='ignore')
3740-
3741- # retrieve untracked user agents from cache
3742- ua_key = '_tracking_untracked_uas'
3743- untracked = cache.get(ua_key)
3744- if untracked is None:
3745- log.info('Updating untracked user agent cache')
3746- untracked = UntrackedUserAgent.objects.all()
3747- cache.set(ua_key, untracked, 3600)
3748-
3749- # see if the user agent is not supposed to be tracked
3750- for ua in untracked:
3751- # if the keyword is found in the user agent, stop tracking
3752- if user_agent.find(ua.keyword) != -1:
3753- log.debug('Not tracking UA "%s" because of keyword: %s' %
3754- (user_agent, ua.keyword))
3755- return
3756-
3757- if hasattr(request, 'session') and request.session.session_key:
3758- # use the current session key if we can
3759- session_key = request.session.session_key
3760- else:
3761- # otherwise just fake a session key
3762- session_key = '%s:%s' % (ip_address, user_agent)
3763- session_key = session_key[:40]
3764-
3765- # ensure that the request.path does not begin with any of the prefixes
3766- for prefix in self.prefixes:
3767- if request.path.startswith(prefix):
3768- log.debug('Not tracking request to: %s' % request.path)
3769- return
3770-
3771- # if we get here, the URL needs to be tracked
3772- # determine what time it is
3773- now = datetime.now()
3774-
3775- attrs = {
3776- 'session_key': session_key,
3777- 'ip_address': ip_address
3778- }
3779-
3780- # for some reason, Visitor.objects.get_or_create was not working here
3781- try:
3782- visitor = Visitor.objects.get(**attrs)
3783- except Visitor.DoesNotExist:
3784- # see if there's a visitor with the same IP and user agent
3785- # within the last 5 minutes
3786- cutoff = now - timedelta(minutes=5)
3787- visitors = Visitor.objects.filter(
3788- ip_address=ip_address,
3789- user_agent=user_agent,
3790- last_update__gte=cutoff
3791- )
3792-
3793- if len(visitors):
3794- visitor = visitors[0]
3795- visitor.session_key = session_key
3796- log.debug('Using existing visitor for IP %s / UA %s: %s' %
3797- (ip_address, user_agent, visitor.id))
3798- else:
3799- # it's probably safe to assume that the visitor is brand new
3800- visitor = Visitor(**attrs)
3801- log.debug('Created a new visitor: %s' % attrs)
3802- except:
3803- return
3804-
3805- # determine whether or not the user is logged in
3806- user = request.user
3807- if isinstance(user, AnonymousUser):
3808- user = None
3809-
3810- # update the tracking information
3811- visitor.user = user
3812- visitor.user_agent = user_agent
3813-
3814- # if the visitor record is new, or the visitor hasn't been here for
3815- # at least an hour, update their referrer URL
3816- one_hour_ago = now - timedelta(hours=1)
3817- if not visitor.last_update or visitor.last_update <= one_hour_ago:
3818- visitor.referrer = utils.u_clean(
3819- request.META.get('HTTP_REFERER', 'unknown')[:255])
3820-
3821- # reset the number of pages they've been to
3822- visitor.page_views = 0
3823- visitor.session_start = now
3824-
3825- visitor.url = request.path
3826- visitor.page_views += 1
3827- visitor.last_update = now
3828- try:
3829- visitor.save()
3830- except DatabaseError:
3831- log.error('There was a problem saving visitor information:\n%s\n\n%s' % (
3832- traceback.format_exc(), locals()))
3833-
3834-
3835-class VisitorCleanUpMiddleware:
3836- """Clean up old visitor tracking records in the database."""
3837-
3838- def process_request(self, request):
3839- timeout = utils.get_cleanup_timeout()
3840-
3841- if str(timeout).isdigit():
3842- log.debug('Cleaning up visitors older than %s hours' % timeout)
3843- timeout = datetime.now() - timedelta(hours=int(timeout))
3844- Visitor.objects.filter(last_update__lte=timeout).delete()
3845-
3846-
3847-class BannedIPMiddleware:
3848- """Raises an Http404 error for any page request from a banned IP. IP
3849- addresses may be added to the list of banned IPs via the Django admin.
3850-
3851- The banned users do not actually receive the 404 error--instead they get
3852- an "Internal Server Error", effectively eliminating any access to the site.
3853-
3854- """
3855-
3856- def process_request(self, request):
3857- key = '_tracking_banned_ips'
3858- ips = cache.get(key)
3859- if ips is None:
3860- # compile a list of all banned IP addresses
3861- log.info('Updating banned IPs cache')
3862- ips = [b.ip_address for b in BannedIP.objects.all()]
3863- cache.set(key, ips, 3600)
3864-
3865- # check to see if the current user's IP address is in that list
3866- if utils.get_ip(request) in ips:
3867- raise Http404
3868
3869=== removed directory 'tracking/migrations'
3870=== removed file 'tracking/migrations/0001_initial.py'
3871--- tracking/migrations/0001_initial.py 2016-12-13 18:28:51 +0000
3872+++ tracking/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
3873@@ -1,65 +0,0 @@
3874-# -*- coding: utf-8 -*-
3875-from __future__ import unicode_literals
3876-
3877-from django.db import models, migrations
3878-from django.conf import settings
3879-
3880-
3881-class Migration(migrations.Migration):
3882-
3883- dependencies = [
3884- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
3885- ]
3886-
3887- operations = [
3888- migrations.CreateModel(
3889- name='BannedIP',
3890- fields=[
3891- ('id', models.AutoField(verbose_name='ID',
3892- serialize=False, auto_created=True, primary_key=True)),
3893- ('ip_address', models.GenericIPAddressField(
3894- help_text='The IP address that should be banned', verbose_name=b'IP Address')),
3895- ],
3896- options={
3897- 'ordering': ('ip_address',),
3898- 'verbose_name': 'Banned IP',
3899- 'verbose_name_plural': 'Banned IPs',
3900- },
3901- ),
3902- migrations.CreateModel(
3903- name='UntrackedUserAgent',
3904- fields=[
3905- ('id', models.AutoField(verbose_name='ID',
3906- serialize=False, auto_created=True, primary_key=True)),
3907- ('keyword', models.CharField(help_text='Part or all of a user-agent string. For example, "Googlebot" here will be found in "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" and that visitor will not be tracked.', max_length=100, verbose_name='keyword')),
3908- ],
3909- options={
3910- 'ordering': ('keyword',),
3911- 'verbose_name': 'Untracked User-Agent',
3912- 'verbose_name_plural': 'Untracked User-Agents',
3913- },
3914- ),
3915- migrations.CreateModel(
3916- name='Visitor',
3917- fields=[
3918- ('id', models.AutoField(verbose_name='ID',
3919- serialize=False, auto_created=True, primary_key=True)),
3920- ('session_key', models.CharField(max_length=40)),
3921- ('ip_address', models.CharField(max_length=20)),
3922- ('user_agent', models.CharField(max_length=255)),
3923- ('referrer', models.CharField(max_length=255)),
3924- ('url', models.CharField(max_length=255)),
3925- ('page_views', models.PositiveIntegerField(default=0)),
3926- ('session_start', models.DateTimeField()),
3927- ('last_update', models.DateTimeField()),
3928- ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True)),
3929- ],
3930- options={
3931- 'ordering': ('-last_update',),
3932- },
3933- ),
3934- migrations.AlterUniqueTogether(
3935- name='visitor',
3936- unique_together=set([('session_key', 'ip_address')]),
3937- ),
3938- ]
3939
3940=== removed file 'tracking/migrations/__init__.py'
3941=== removed file 'tracking/models.py'
3942--- tracking/models.py 2016-12-13 18:28:51 +0000
3943+++ tracking/models.py 1970-01-01 00:00:00 +0000
3944@@ -1,133 +0,0 @@
3945-from datetime import datetime, timedelta
3946-import logging
3947-import traceback
3948-
3949-from django.contrib.gis.geoip import HAS_GEOIP
3950-
3951-if HAS_GEOIP:
3952- from django.contrib.gis.geoip import GeoIP, GeoIPException
3953-
3954-from django.conf import settings
3955-from django.contrib.auth.models import User
3956-from django.db import models
3957-from django.utils.translation import ugettext, ugettext_lazy as _
3958-from tracking import utils
3959-
3960-USE_GEOIP = getattr(settings, 'TRACKING_USE_GEOIP', False)
3961-CACHE_TYPE = getattr(settings, 'GEOIP_CACHE_TYPE', 4)
3962-
3963-log = logging.getLogger('tracking.models')
3964-
3965-
3966-class VisitorManager(models.Manager):
3967-
3968- def active(self, timeout=None):
3969- """Retrieves only visitors who have been active within the timeout
3970- period."""
3971- if not timeout:
3972- timeout = utils.get_timeout()
3973-
3974- now = datetime.now()
3975- cutoff = now - timedelta(minutes=timeout)
3976-
3977- return self.get_queryset().filter(last_update__gte=cutoff)
3978-
3979-
3980-class Visitor(models.Model):
3981- session_key = models.CharField(max_length=40)
3982- ip_address = models.CharField(max_length=20)
3983- user = models.ForeignKey(User, null=True)
3984- user_agent = models.CharField(max_length=255)
3985- referrer = models.CharField(max_length=255)
3986- url = models.CharField(max_length=255)
3987- page_views = models.PositiveIntegerField(default=0)
3988- session_start = models.DateTimeField()
3989- last_update = models.DateTimeField()
3990-
3991- objects = VisitorManager()
3992-
3993- def _time_on_site(self):
3994- """Attempts to determine the amount of time a visitor has spent on the
3995- site based upon their information that's in the database."""
3996- if self.session_start:
3997- seconds = (self.last_update - self.session_start).seconds
3998-
3999- hours = seconds / 3600
4000- seconds -= hours * 3600
4001- minutes = seconds / 60
4002- seconds -= minutes * 60
4003-
4004- return u'%i:%02i:%02i' % (hours, minutes, seconds)
4005- else:
4006- return ugettext(u'unknown')
4007- time_on_site = property(_time_on_site)
4008-
4009- def _get_geoip_data(self):
4010- """Attempts to retrieve MaxMind GeoIP data based upon the visitor's
4011- IP."""
4012-
4013- if not HAS_GEOIP or not USE_GEOIP:
4014- # go no further when we don't need to
4015- log.debug('Bailing out. HAS_GEOIP: %s; TRACKING_USE_GEOIP: %s' % (
4016- HAS_GEOIP, USE_GEOIP))
4017- return None
4018-
4019- if not hasattr(self, '_geoip_data'):
4020- self._geoip_data = None
4021- try:
4022- gip = GeoIP(cache=CACHE_TYPE)
4023- self._geoip_data = gip.city(self.ip_address)
4024- except GeoIPException:
4025- # don't even bother...
4026- log.error('Error getting GeoIP data for IP "%s": %s' %
4027- (self.ip_address, traceback.format_exc()))
4028-
4029- return self._geoip_data
4030-
4031- geoip_data = property(_get_geoip_data)
4032-
4033- def _get_geoip_data_json(self):
4034- """Cleans out any dirty unicode characters to make the geoip data safe
4035- for JSON encoding."""
4036- clean = {}
4037- if not self.geoip_data:
4038- return {}
4039-
4040- for key, value in self.geoip_data.items():
4041- clean[key] = utils.u_clean(value)
4042- return clean
4043-
4044- geoip_data_json = property(_get_geoip_data_json)
4045-
4046- class Meta:
4047- ordering = ('-last_update',)
4048- unique_together = ('session_key', 'ip_address',)
4049- app_label = 'tracking'
4050-
4051-
4052-class UntrackedUserAgent(models.Model):
4053- keyword = models.CharField(_('keyword'), max_length=100, help_text=_(
4054- 'Part or all of a user-agent string. For example, "Googlebot" here will be found in "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" and that visitor will not be tracked.'))
4055-
4056- def __unicode__(self):
4057- return self.keyword
4058-
4059- class Meta:
4060- ordering = ('keyword',)
4061- verbose_name = _('Untracked User-Agent')
4062- verbose_name_plural = _('Untracked User-Agents')
4063- app_label = 'tracking'
4064-
4065-
4066-class BannedIP(models.Model):
4067- ip_address = models.GenericIPAddressField(
4068- 'IP Address', help_text=_('The IP address that should be banned'))
4069-
4070- def __unicode__(self):
4071- return self.ip_address
4072-
4073- class Meta:
4074- ordering = ('ip_address',)
4075- verbose_name = _('Banned IP')
4076- verbose_name_plural = _('Banned IPs')
4077- app_label = 'tracking'
4078
4079=== removed directory 'tracking/templates'
4080=== removed file 'tracking/templates/base.html'
4081--- tracking/templates/base.html 2016-06-05 20:58:46 +0000
4082+++ tracking/templates/base.html 1970-01-01 00:00:00 +0000
4083@@ -1,21 +0,0 @@
4084-<html>
4085-<head>
4086-<title>My Site - {% block title %}Welcome{% endblock %}</title>
4087-<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery-1.4.4.min.js"></script>
4088-<style type="text/css">
4089-#active-users-map {
4090- height: 400px;
4091- width: 600px;
4092-}
4093-</style>
4094-{% block extra-head %}{% endblock %}
4095-</head>
4096-<body>
4097-<h1><a href="/">My Site</a></h1>
4098-{% block content %}{% endblock %}
4099-
4100-{% block footer %}
4101-<p>Copyright &copy; {% now "Y" %} Me. All rights reserved.</p>
4102-{% endblock %}
4103-</body>
4104-</html>
4105
4106=== removed directory 'tracking/templates/tracking'
4107=== removed file 'tracking/templates/tracking/_active_users.html'
4108--- tracking/templates/tracking/_active_users.html 2016-06-05 20:58:46 +0000
4109+++ tracking/templates/tracking/_active_users.html 1970-01-01 00:00:00 +0000
4110@@ -1,21 +0,0 @@
4111-{% load i18n %}<div class="total">
4112- {% blocktrans count active.count as counter %}1 active user{% plural %}{{ counter }} active users{% endblocktrans %}
4113-</div>
4114-<div class="guests">
4115- {% blocktrans count guests.count as counter %}1 guest user{% plural %}{{ counter }} guest users{% endblocktrans %}
4116- {% if user.is_superuser %}
4117- {% for visitor in guests %}
4118- {% if forloop.first %}<ul class="active-guests">{% endif %}
4119- <li>{{ visitor.ip_address }}</li>
4120- {% if forloop.last %}</ul>{% endif %}
4121- {% endfor %}
4122- {% endif %}
4123-</div>
4124-<div class="registered">
4125- {% blocktrans count registered.count as counter %}1 registered user{% plural %}{{ counter }} registered users{% endblocktrans %}
4126- {% for visitor in registered %}
4127- {% if forloop.first %}<ul class="active-users">{% endif %}
4128- <li>{{ visitor.user }}</li>
4129- {% if forloop.last %}</ul>{% endif %}
4130- {% endfor %}
4131-</div>
4132\ No newline at end of file
4133
4134=== removed file 'tracking/templates/tracking/_active_users.js'
4135--- tracking/templates/tracking/_active_users.js 2016-06-06 18:26:47 +0000
4136+++ tracking/templates/tracking/_active_users.js 1970-01-01 00:00:00 +0000
4137@@ -1,155 +0,0 @@
4138-//<![CDATA[
4139-var AUmap;
4140-var markerList = new Array();
4141-var blurbs = new Array();
4142-
4143-$(document).ready(function () {
4144- if (GBrowserIsCompatible()) {
4145- // set up Google Maps
4146- origin = new GLatLng(35.232253,-95.273437);
4147- AUmap = new GMap2(document.getElementById("active-users-map"));
4148- AUmap.setCenter(origin, 2);
4149- AUmap.addControl(new GSmallMapControl());
4150- AUmap.addControl(new GMapTypeControl());
4151- AUmap.enableScrollWheelZoom();
4152-
4153- // now fetch the active users
4154- createMarkers();
4155-
4156- // refresh the markers every 5 seconds or so
4157- setInterval('createMarkers()', 5000);
4158-
4159- $('.active-user h3').live('click', function () {
4160- var p = $(this).parent().get(0);
4161- var num = parseInt(p.id.replace('au-', ''));
4162- var marker = markerList[num];
4163-
4164- AUmap.openInfoWindowHtml(marker.getLatLng(), blurbs[num]);
4165- AUmap.panTo(marker.getLatLng());
4166- });
4167- } else {
4168- alert('This page requires a modern browser which supports Google Maps!');
4169- }
4170-});
4171-$(document).unload(GUnload);
4172-
4173-$('html').ajaxSend(function(event, xhr, settings) {
4174- function getCookie(name) {
4175- var cookieValue = null;
4176- if (document.cookie && document.cookie != '') {
4177- var cookies = document.cookie.split(';');
4178- for (var i = 0; i < cookies.length; i++) {
4179- var cookie = jQuery.trim(cookies[i]);
4180- // Does this cookie string begin with the name we want?
4181- if (cookie.substring(0, name.length + 1) == (name + '=')) {
4182- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
4183- break;
4184- }
4185- }
4186- }
4187- return cookieValue;
4188- }
4189- if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
4190- // Only send the token to relative URLs i.e. locally.
4191- xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
4192- }
4193-});
4194-
4195-function createMarkers() {
4196- // Pull back all active users and create markers for them
4197- $.ajax({
4198- url : "{% url 'tracking-get-active-users' %}",
4199- type : 'POST',
4200- dataType : 'json',
4201- data: {},
4202- success : function (json) {
4203- var userList = $('#active-users-list');
4204-
4205- $.each(json.users, function (i, user) {
4206- var url = '<a href="' + user.url + '">' + user.url + '</a>';
4207- if (!markerList[user.id] && user.geoip && user.geoip.city && user.geoip.city != 'None') {
4208- // determine which flag to use
4209- var img = '<img src="/static/images/flags/' +
4210- user.geoip.country_code.toLowerCase() +
4211- '.png" class="flag" />';
4212-
4213- var ref = '';
4214- if (user.referrer != 'unknown') {
4215- ref = '<div><strong>From</strong> <a href="' + user.referrer +
4216- '">' + user.referrer + '</a></div>';
4217- }
4218-
4219- // come up with some HTML to put in the list
4220- var listHtml = '<div id="au-' + user.id + '" ' +
4221- 'class="active-user location-info"><h3>' +
4222- user.geoip.city + '</h3><div>' + img +
4223- user.geoip.region + ', ' +
4224- user.geoip.country_name + '</div>' +
4225- '<div><strong>Viewing</strong> <span id="auu-' + user.id +'">' +
4226- url + '</span></div>' +
4227- '<div><strong>Using</strong> ' + user.user_agent + '</div>' + ref +
4228- '<div><strong>Has viewed</strong> <span id="pv-' + user.id +
4229- '">' + user.page_views + '</span> page(s)</div>' +
4230- '<div><strong>Updated</strong> <span id="lu-' + user.id + '">' +
4231- user.friendly_time + '</span> ago</div></div>';
4232- userList.prepend(listHtml);
4233-
4234- // add a marker to the map
4235- var point = new GLatLng(user.geoip.latitude,
4236- user.geoip.longitude);
4237-
4238- AUmap.addOverlay(createMarker(point, user, img));
4239- } else {
4240- $('#auu-' + user.id).html(url);
4241- $('#lu-' + user.id).html(user.friendly_time);
4242- $('#pv-' + user.id).html(user.page_views);
4243-
4244- // Send recently-active users to the top of the list
4245- if (user.last_update <= 10) {
4246- $('#au-' + user.id).prependTo(userList);
4247- }
4248- }
4249- });
4250-
4251- // Clean up old markers
4252- $.each(markerList, function (i, marker) {
4253- if (marker) {
4254- var inList = false;
4255- $.each(json.users, function (j, juser) {
4256- if (i == juser.id) {
4257- inList = true;
4258- return;
4259- }
4260- });
4261- if (!inList) {
4262- AUmap.removeOverlay(marker);
4263- $('#au-' + i).remove();
4264- markerList[i] = null;
4265- }
4266- }
4267- });
4268- }
4269- });
4270-}
4271-
4272-function createMarker(point, user, img) {
4273- // Add a marker overlay to the map and store various bits of info about it
4274- var marker = new GMarker(point);
4275- marker.value = user.id;
4276-
4277- var myHtml = '<div class="mapOverlay"><h3>' + user.geoip.city + '</h3>';
4278- myHtml += '<div>' + img + user.geoip.region;
4279- myHtml += ', ' + user.geoip.country_name + '</div></div>';
4280-
4281- // Add a listener to pop up an info box when the mouse goes over a marker
4282- GEvent.addListener(marker, "mouseover", function() {
4283- AUmap.openInfoWindowHtml(point, myHtml);
4284- });
4285-
4286- // Keep track of the marker's data
4287- markerList[user.id] = marker;
4288- blurbs[user.id] = myHtml;
4289-
4290- return marker;
4291-}
4292-//]]>
4293
4294=== removed file 'tracking/templates/tracking/refresh_active_users.js'
4295--- tracking/templates/tracking/refresh_active_users.js 2016-06-05 20:58:46 +0000
4296+++ tracking/templates/tracking/refresh_active_users.js 1970-01-01 00:00:00 +0000
4297@@ -1,26 +0,0 @@
4298-var trackingUsers;
4299-var refreshTimeout = 5000;
4300-
4301-$(document).ready(function () {
4302- trackingUsers = $('#tracking-active-users');
4303- if (trackingUsers) {
4304- refreshActiveUsers();
4305- }
4306-});
4307-
4308-function refreshActiveUsers() {
4309- $.ajax({
4310- 'url': updateActiveURL,
4311- 'type': 'GET',
4312- 'data': {},
4313- 'dataType': 'json',
4314- 'error': function (xhr, status, msg) {
4315- // do nothing
4316- },
4317- 'success': function (json) {
4318- trackingUsers.html(json.users)
4319- }
4320- });
4321-
4322- setTimeout(refreshActiveUsers, refreshTimeout);
4323-}
4324\ No newline at end of file
4325
4326=== removed file 'tracking/templates/tracking/visitor_map.html'
4327--- tracking/templates/tracking/visitor_map.html 2016-06-05 20:58:46 +0000
4328+++ tracking/templates/tracking/visitor_map.html 1970-01-01 00:00:00 +0000
4329@@ -1,24 +0,0 @@
4330-{% extends template %}
4331-{% load i18n %}
4332-
4333-{% block title %}{% trans "Active Visitors Map" %}{% endblock %}
4334-
4335-{% block extra-head %}
4336-{{ block.super }}
4337-<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={{ GOOGLE_MAPS_KEY }}" type="text/javascript"></script>
4338-<script type="text/javascript">
4339-{% include 'tracking/_active_users.js' %}
4340-</script>
4341-{% endblock %}
4342-
4343-{% block content %}
4344-<h2>{% trans "Active Users" %}</h2>
4345-
4346-<p>
4347- {% trans "Below is a map and a list of recently active users on this site. It updates itself every 5 seconds or so, adding and removing pins in the map as necessary. Stick around for a few minutes to see it in action!" %}
4348-</p>
4349-
4350-<div id="active-users-map"></div>
4351-
4352-<div id="active-users-list"></div>
4353-{% endblock %}
4354
4355=== removed directory 'tracking/templatetags'
4356=== removed file 'tracking/templatetags/__init__.py'
4357=== removed file 'tracking/templatetags/tracking_tags.py'
4358--- tracking/templatetags/tracking_tags.py 2016-12-13 18:28:51 +0000
4359+++ tracking/templatetags/tracking_tags.py 1970-01-01 00:00:00 +0000
4360@@ -1,53 +0,0 @@
4361-from django import template
4362-from tracking.models import Visitor
4363-
4364-register = template.Library()
4365-
4366-
4367-class VisitorsOnSite(template.Node):
4368- """Injects the number of active users on your site as an integer into the
4369- context."""
4370-
4371- def __init__(self, varname, same_page=False):
4372- self.varname = varname
4373- self.same_page = same_page
4374-
4375- def render(self, context):
4376- if self.same_page:
4377- try:
4378- request = context['request']
4379- count = Visitor.objects.active().filter(url=request.path).count()
4380- except KeyError:
4381- raise template.TemplateSyntaxError(
4382- "Please add 'django.core.context_processors.request' to your TEMPLATE_CONTEXT_PROCESSORS if you want to see how many users are on the same page.")
4383- else:
4384- count = Visitor.objects.active().count()
4385-
4386- context[self.varname] = count
4387- return ''
4388-
4389-
4390-def visitors_on_site(parser, token):
4391- """Determines the number of active users on your site and puts it into the
4392- context."""
4393- try:
4394- tag, a, varname = token.split_contents()
4395- except ValueError:
4396- raise template.TemplateSyntaxError(
4397- 'visitors_on_site usage: {% visitors_on_site as visitors %}')
4398-
4399- return VisitorsOnSite(varname)
4400-register.tag(visitors_on_site)
4401-
4402-
4403-def visitors_on_page(parser, token):
4404- """Determines the number of active users on the same page and puts it into
4405- the context."""
4406- try:
4407- tag, a, varname = token.split_contents()
4408- except ValueError:
4409- raise template.TemplateSyntaxError(
4410- 'visitors_on_page usage: {% visitors_on_page as visitors %}')
4411-
4412- return VisitorsOnSite(varname, same_page=True)
4413-register.tag(visitors_on_page)
4414
4415=== removed file 'tracking/urls.py'
4416--- tracking/urls.py 2016-12-13 18:28:51 +0000
4417+++ tracking/urls.py 1970-01-01 00:00:00 +0000
4418@@ -1,16 +0,0 @@
4419-from django.conf.urls.defaults import *
4420-from django.conf import settings
4421-from tracking import views
4422-
4423-urlpatterns = patterns('',
4424- url(r'^refresh/$', views.update_active_users,
4425- name='tracking-refresh-active-users'),
4426- url(r'^refresh/json/$', views.get_active_users,
4427- name='tracking-get-active-users'),
4428- )
4429-
4430-if getattr(settings, 'TRACKING_USE_GEOIP', False):
4431- urlpatterns += patterns('',
4432- url(r'^map/$', views.display_map,
4433- name='tracking-visitor-map'),
4434- )
4435
4436=== removed file 'tracking/utils.py'
4437--- tracking/utils.py 2016-12-13 18:28:51 +0000
4438+++ tracking/utils.py 1970-01-01 00:00:00 +0000
4439@@ -1,74 +0,0 @@
4440-from django.conf import settings
4441-import re
4442-import unicodedata
4443-
4444-# this is not intended to be an all-knowing IP address regex
4445-IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
4446-
4447-
4448-def get_ip(request):
4449- """Retrieves the remote IP address from the request data.
4450-
4451- If the user is
4452- behind a proxy, they may have a comma-separated list of IP addresses, so
4453- we need to account for that. In such a case, only the first IP in the
4454- list will be retrieved. Also, some hosts that use a proxy will put the
4455- REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
4456- IP from the proper place.
4457-
4458- """
4459-
4460- # if neither header contain a value, just use local loopback
4461- ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
4462- request.META.get('REMOTE_ADDR', '127.0.0.1'))
4463- if ip_address:
4464- # make sure we have one and only one IP
4465- try:
4466- ip_address = IP_RE.match(ip_address)
4467- if ip_address:
4468- ip_address = ip_address.group(0)
4469- else:
4470- # no IP, probably from some dirty proxy or other device
4471- # throw in some bogus IP
4472- ip_address = '10.0.0.1'
4473- except IndexError:
4474- pass
4475-
4476- return ip_address
4477-
4478-
4479-def get_timeout():
4480- """Gets any specified timeout from the settings file, or use 10 minutes by
4481- default."""
4482- return getattr(settings, 'TRACKING_TIMEOUT', 10)
4483-
4484-
4485-def get_cleanup_timeout():
4486- """
4487- Gets any specified visitor clean-up timeout from the settings file, or
4488- use 24 hours by default
4489- """
4490- return getattr(settings, 'TRACKING_CLEANUP_TIMEOUT', 24)
4491-
4492-
4493-def u_clean(s):
4494- """A strange attempt at cleaning up unicode."""
4495-
4496- uni = ''
4497- try:
4498- # try this first
4499- uni = str(s).decode('iso-8859-1')
4500- except UnicodeDecodeError:
4501- try:
4502- # try utf-8 next
4503- uni = str(s).decode('utf-8')
4504- except UnicodeDecodeError:
4505- # last resort method... one character at a time (ugh)
4506- if s and type(s) in (str, unicode):
4507- for c in s:
4508- try:
4509- uni += unicodedata.normalize('NFKC', unicode(c))
4510- except UnicodeDecodeError:
4511- uni += '-'
4512-
4513- return uni.encode('ascii', 'xmlcharrefreplace')
4514
4515=== removed file 'tracking/views.py'
4516--- tracking/views.py 2016-12-13 18:28:51 +0000
4517+++ tracking/views.py 1970-01-01 00:00:00 +0000
4518@@ -1,115 +0,0 @@
4519-from datetime import datetime
4520-import logging
4521-import traceback
4522-
4523-from django.conf import settings
4524-from django.http import Http404, HttpResponse
4525-from django.shortcuts import render_to_response
4526-from django.template import RequestContext, Context, loader
4527-from django.utils.simplejson import JSONEncoder
4528-from django.utils.translation import ungettext
4529-from django.views.decorators.cache import never_cache
4530-from tracking.models import Visitor
4531-from tracking.utils import u_clean as uc
4532-
4533-DEFAULT_TRACKING_TEMPLATE = getattr(settings, 'DEFAULT_TRACKING_TEMPLATE',
4534- 'tracking/visitor_map.html')
4535-log = logging.getLogger('tracking.views')
4536-
4537-
4538-def update_active_users(request):
4539- """Returns a list of all active users."""
4540- if request.is_ajax():
4541- active = Visitor.objects.active()
4542- user = getattr(request, 'user', None)
4543-
4544- info = {
4545- 'active': active,
4546- 'registered': active.filter(user__isnull=False),
4547- 'guests': active.filter(user__isnull=True),
4548- 'user': user
4549- }
4550-
4551- # render the list of active users
4552- t = loader.get_template('tracking/_active_users.html')
4553- c = Context(info)
4554- users = {'users': t.render(c)}
4555-
4556- return HttpResponse(content=JSONEncoder().encode(users))
4557-
4558- # if the request was not made via AJAX, raise a 404
4559- raise Http404
4560-
4561-
4562-@never_cache
4563-def get_active_users(request):
4564- """Retrieves a list of active users which is returned as plain JSON for
4565- easier manipulation with JavaScript."""
4566- if request.is_ajax():
4567- active = Visitor.objects.active().reverse()
4568- now = datetime.now()
4569-
4570- # we don't put the session key or IP address here for security reasons
4571- try:
4572- data = {'users': [{
4573- 'id': v.id,
4574- #'user': uc(v.user),
4575- 'user_agent': uc(v.user_agent),
4576- 'referrer': uc(v.referrer),
4577- 'url': uc(v.url),
4578- 'page_views': v.page_views,
4579- 'geoip': v.geoip_data_json,
4580- 'last_update': (now - v.last_update).seconds,
4581- 'friendly_time': ', '.join(friendly_time((now - v.last_update).seconds)),
4582- } for v in active]}
4583- except:
4584- log.error('There was a problem putting all of the visitor data together:\n%s\n\n%s' % (
4585- traceback.format_exc(), locals()))
4586- return HttpResponse(content='{}', mimetype='text/javascript')
4587-
4588- response = HttpResponse(content=JSONEncoder().encode(data),
4589- mimetype='text/javascript')
4590- response['Content-Length'] = len(response.content)
4591-
4592- return response
4593-
4594- # if the request was not made via AJAX, raise a 404
4595- raise Http404
4596-
4597-
4598-def friendly_time(last_update):
4599- minutes = last_update / 60
4600- seconds = last_update % 60
4601-
4602- friendly_time = []
4603- if minutes > 0:
4604- friendly_time.append(ungettext(
4605- '%(minutes)i minute',
4606- '%(minutes)i minutes',
4607- minutes
4608- ) % {'minutes': minutes})
4609- if seconds > 0:
4610- friendly_time.append(ungettext(
4611- '%(seconds)i second',
4612- '%(seconds)i seconds',
4613- seconds
4614- ) % {'seconds': seconds})
4615-
4616- return friendly_time or 0
4617-
4618-
4619-def display_map(request, template_name=DEFAULT_TRACKING_TEMPLATE,
4620- extends_template='base.html'):
4621- """Displays a map of recently active users.
4622-
4623- Requires a Google Maps API key and GeoIP in order to be most
4624- effective.
4625-
4626- """
4627-
4628- GOOGLE_MAPS_KEY = getattr(settings, 'GOOGLE_MAPS_KEY', None)
4629-
4630- return render_to_response(template_name,
4631- {'GOOGLE_MAPS_KEY': GOOGLE_MAPS_KEY,
4632- 'template': extends_template},
4633- context_instance=RequestContext(request))
4634
4635=== modified file 'urls.py'
4636--- urls.py 2017-12-23 09:15:02 +0000
4637+++ urls.py 2018-04-19 17:49:06 +0000
4638@@ -4,7 +4,6 @@
4639 from django.contrib import admin
4640 admin.autodiscover()
4641
4642-from mainpage.views import mainpage
4643 from news.feeds import NewsPostsFeed
4644 from django.views.generic.base import RedirectView
4645 from django.views.generic import TemplateView
4646@@ -12,6 +11,7 @@
4647 from registration.backends.hmac.views import RegistrationView
4648 from mainpage.forms import RegistrationWithCaptchaForm
4649
4650+
4651 urlpatterns = [
4652 # Creating a sitemap.xml
4653 url(r'^sitemap\.xml/', include('sitemap_urls')),
4654@@ -28,13 +28,14 @@
4655 url(r'^accounts/', include('registration.backends.hmac.urls')),
4656 url('^', include('django.contrib.auth.urls')),
4657
4658- # Feed for Mainpage
4659+ # Feed for news
4660 url(r'^feeds/news/$', NewsPostsFeed()),
4661
4662 # Formerly 3rd party
4663 url(r'^notification/', include('notification.urls')),
4664
4665- url(r'^messages/', include('django_messages.urls')),
4666+ url(r'^messages/', include('django_messages_wl.urls')),
4667+
4668 url(r'^threadedcomments/', include('threadedcomments.urls')),
4669
4670 # Redirect old urls to docs to docs/wl
4671@@ -47,15 +48,9 @@
4672 url(r'^forum/', include('pybb.urls')),
4673
4674 # WL specific:
4675- url(r'^$', mainpage, name='mainpage'),
4676- url(r'^locale/$', 'mainpage.views.view_locale'),
4677- url(r'^changelog/$', 'mainpage.views.changelog', name='changelog'),
4678- url(r'^developers/$', 'mainpage.views.developers', name='developers'),
4679- url(r'^legal_notice/$', 'mainpage.views.legal_notice', name='legal_notice'),
4680- url(r'^legal_notice_thanks/$', 'mainpage.views.legal_notice_thanks',
4681- name='legal_notice_thanks'),
4682+ url(r'^', include('mainpage.urls')),
4683 url(r'^help/(?P<path>.*)', RedirectView.as_view(url='/encyclopedia/%(path)s',
4684- permanent=True)), # to not break old links
4685+ permanent=True)), # to not break old links
4686 url(r'^encyclopedia/', include('wlhelp.urls')),
4687 url(r'^webchat/', include('wlwebchat.urls')),
4688 url(r'^images/', include('wlimages.urls')),
4689
4690=== modified file 'wiki/__init__.py'
4691--- wiki/__init__.py 2016-02-26 19:00:22 +0000
4692+++ wiki/__init__.py 2018-04-19 17:49:06 +0000
4693@@ -1,3 +0,0 @@
4694-#from wiki.templatetags.restructuredtext import restructuredtext
4695-
4696-#restructuredtext('`Available as 1.0 since September, 2007 <http://www.modwsgi.org/>`')
4697
4698=== added file 'wiki/apps.py'
4699--- wiki/apps.py 1970-01-01 00:00:00 +0000
4700+++ wiki/apps.py 2018-04-19 17:49:06 +0000
4701@@ -0,0 +1,12 @@
4702+from django.apps import AppConfig
4703+from django.db.models import signals
4704+
4705+
4706+class WikiConfig(AppConfig):
4707+
4708+ name = 'wiki'
4709+ verbose_name = 'Wiki'
4710+
4711+ def ready(self):
4712+ from wiki.management import create_notice_types
4713+ signals.post_migrate.connect(create_notice_types, sender=self)
4714
4715=== modified file 'wiki/management.py'
4716--- wiki/management.py 2017-04-26 19:54:03 +0000
4717+++ wiki/management.py 2018-04-19 17:49:06 +0000
4718@@ -1,11 +1,11 @@
4719-from django.db.models import signals
4720
4721 from django.utils.translation import ugettext_noop as _
4722
4723 try:
4724 from notification import models as notification
4725
4726- def create_notice_types(app, created_models, verbosity, **kwargs):
4727+ def create_notice_types(sender, **kwargs):
4728+ print("Creating noticetypes for wiki ...")
4729 notification.create_notice_type('wiki_revision_reverted',
4730 _('Article Revision Reverted'),
4731 _('your revision has been reverted'))
4732@@ -13,9 +13,5 @@
4733 _('Observed Article Changed'),
4734 _('an article you observe has changed'))
4735
4736- # TODO (Franku): post_syncdb is deprecated since Django 1.7
4737- # See: https://docs.djangoproject.com/en/1.8/ref/signals/#post-syncdb
4738- signals.post_syncdb.connect(create_notice_types,
4739- sender=notification)
4740 except ImportError:
4741 print 'Skipping creation of NoticeTypes as notification app not found'
4742
4743=== modified file 'wiki/models.py'
4744--- wiki/models.py 2017-12-10 13:18:47 +0000
4745+++ wiki/models.py 2018-04-19 17:49:06 +0000
4746@@ -1,5 +1,5 @@
4747 from datetime import datetime
4748-from django.core.urlresolvers import reverse
4749+from django.urls import reverse
4750
4751 # Google Diff Match Patch library
4752 # http://code.google.com/p/google-diff-match-patch
4753
4754=== removed file 'wiki/static_urls.py'
4755--- wiki/static_urls.py 2016-12-13 18:28:51 +0000
4756+++ wiki/static_urls.py 1970-01-01 00:00:00 +0000
4757@@ -1,10 +0,0 @@
4758-from django.conf.urls import *
4759-from django.conf import settings
4760-
4761-
4762-urlpatterns = patterns('',
4763- url(r'^site_media/(?P<path>.*)$',
4764- 'django.views.static.serve',
4765- {'document_root': settings.STATIC_MEDIA_PATH},
4766- name='wiki_static_media'),
4767- )
4768
4769=== modified file 'wiki/templatetags/switchcase.py'
4770--- wiki/templatetags/switchcase.py 2016-12-13 18:28:51 +0000
4771+++ wiki/templatetags/switchcase.py 2018-04-19 17:49:06 +0000
4772@@ -72,8 +72,8 @@
4773 # Resolve the value; if it's a non-existant variable don't even bother
4774 # checking the values of the cases since they'll never match.
4775 try:
4776- value = template.resolve_variable(self.value, context)
4777- except VariableDoesNotExist:
4778+ value = template.Variable(self.value).resolve(context)
4779+ except template.VariableDoesNotExist:
4780 return ''
4781
4782 # Check each case, and if it matches return the rendered content
4783@@ -99,8 +99,8 @@
4784
4785 """
4786 try:
4787- return template.resolve_variable(self.value, context) == otherval
4788- except VariableDoesNotExist:
4789+ return template.Variable(self.value).resolve(context) == otherval
4790+ except template.VariableDoesNotExist:
4791 # If the variable doesn't exist, it doesn't equal anything.
4792 return False
4793
4794
4795=== modified file 'wiki/templatetags/wiki_extras.py'
4796--- wiki/templatetags/wiki_extras.py 2017-02-24 20:12:28 +0000
4797+++ wiki/templatetags/wiki_extras.py 2018-04-19 17:49:06 +0000
4798@@ -52,18 +52,3 @@
4799 'content': getattr(article, content_attr),
4800 'markup': getattr(article, markup_attr)
4801 }
4802-
4803-
4804-@register.inclusion_tag('wiki/article_teaser.html')
4805-def show_teaser(article):
4806- """Show a teaser box for the summary of the article."""
4807- return {'article': article}
4808-
4809-
4810-@register.inclusion_tag('wiki/wiki_title.html')
4811-def wiki_title(group):
4812- """Display a <h1> title for the wiki, with a link to the group main
4813- page."""
4814- return {'group_name': group.name,
4815- 'group_type': group._meta.verbose_name.title(),
4816- 'group_url': group.get_absolute_url()}
4817
4818=== modified file 'wiki/views.py'
4819--- wiki/views.py 2017-12-10 19:04:12 +0000
4820+++ wiki/views.py 2018-04-19 17:49:06 +0000
4821@@ -5,10 +5,10 @@
4822 from django.conf import settings
4823 from django.core.cache import cache
4824 from django.template import RequestContext
4825-from django.core.urlresolvers import reverse
4826+from django.urls import reverse
4827 from django.http import (Http404, HttpResponseRedirect,
4828 HttpResponseNotAllowed, HttpResponse, HttpResponseForbidden)
4829-from django.shortcuts import get_object_or_404, render_to_response, redirect
4830+from django.shortcuts import get_object_or_404, render, redirect
4831 from django.contrib.contenttypes.models import ContentType
4832 from django.contrib import messages
4833 from wiki.forms import ArticleForm
4834@@ -155,9 +155,8 @@
4835 if extra_context is not None:
4836 template_params.update(extra_context)
4837
4838- return render_to_response('/'.join([template_dir, template_name]),
4839- template_params,
4840- context_instance=RequestContext(request))
4841+ return render(request, '/'.join([template_dir, template_name]),
4842+ template_params,)
4843 return HttpResponseNotAllowed(['GET'])
4844
4845
4846@@ -224,9 +223,8 @@
4847 if extra_context is not None:
4848 template_params.update(extra_context)
4849
4850- return render_to_response('/'.join([template_dir, template_name]),
4851- template_params,
4852- context_instance=RequestContext(request))
4853+ return render(request, '/'.join([template_dir, template_name]),
4854+ template_params,)
4855 return HttpResponseNotAllowed(['GET'])
4856
4857
4858@@ -279,7 +277,7 @@
4859 form.cache_old_content()
4860 if form.is_valid():
4861
4862- if request.user.is_authenticated():
4863+ if request.user.is_authenticated:
4864 form.editor = request.user
4865
4866 if ((article is None) and (group_slug is not None)):
4867@@ -337,9 +335,8 @@
4868 if extra_context is not None:
4869 template_params.update(extra_context)
4870
4871- return render_to_response('/'.join([template_dir, template_name]),
4872- template_params,
4873- context_instance=RequestContext(request))
4874+ return render(request, '/'.join([template_dir, template_name]),
4875+ template_params,)
4876
4877
4878 def view_changeset(request, title, revision,
4879@@ -403,9 +400,8 @@
4880 if extra_context is not None:
4881 template_params.update(extra_context)
4882
4883- return render_to_response('/'.join([template_dir, template_name]),
4884- template_params,
4885- context_instance=RequestContext(request))
4886+ return render(request, '/'.join([template_dir, template_name]),
4887+ template_params,)
4888 return HttpResponseNotAllowed(['GET'])
4889
4890
4891@@ -449,9 +445,8 @@
4892 if extra_context is not None:
4893 template_params.update(extra_context)
4894
4895- return render_to_response('/'.join([template_dir, template_name]),
4896- template_params,
4897- context_instance=RequestContext(request))
4898+ return render(request, '/'.join([template_dir, template_name]),
4899+ template_params,)
4900
4901 return HttpResponseNotAllowed(['GET'])
4902
4903@@ -497,7 +492,7 @@
4904 art = Article.objects.exclude(pk=article.pk).get(title=old_title)
4905 except Article.DoesNotExist:
4906 # No existing article found -> reverting possible
4907- if request.user.is_authenticated():
4908+ if request.user.is_authenticated:
4909 article.revert_to(revision, get_real_ip(request), request.user)
4910 else:
4911 article.revert_to(revision, get_real_ip(request))
4912@@ -541,9 +536,8 @@
4913 if extra_context is not None:
4914 template_params.update(extra_context)
4915
4916- return render_to_response('/'.join([template_dir, template_name]),
4917- template_params,
4918- context_instance=RequestContext(request))
4919+ return render(request, '/'.join([template_dir, template_name]),
4920+ template_params,)
4921 return HttpResponseNotAllowed(['GET'])
4922
4923
4924@@ -680,6 +674,5 @@
4925 context = {'found_links': found_links,
4926 'found_old_links': found_old_links,
4927 'name': title}
4928- return render_to_response('wiki/backlinks.html',
4929- context,
4930- context_instance=RequestContext(request))
4931+ return render(request, 'wiki/backlinks.html',
4932+ context,)
4933
4934=== modified file 'wl_utils.py'
4935--- wl_utils.py 2016-12-13 18:28:51 +0000
4936+++ wl_utils.py 2018-04-19 17:49:06 +0000
4937@@ -13,23 +13,15 @@
4938 # Initial implemenation details about AutoOneToOneField:
4939 # http://softwaremaniacs.org/blog/2007/03/07/auto-one-to-one-field/
4940 #
4941-# This doesn't worked anymore with django 1.8
4942-# changed according to:
4943-# https://github.com/skorokithakis/django-annoying/issues/36
4944-
4945-
4946-# SingleRelatedObjectDescriptor gets renamed with Django 1.9
4947-try:
4948- from django.db.models.fields.related import SingleRelatedObjectDescriptor
4949-except ImportError:
4950- from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor
4951+
4952+
4953+from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor
4954
4955 from django.db.models import OneToOneField
4956-from django.db.models.fields.related import SingleRelatedObjectDescriptor
4957 from django.db import models
4958
4959
4960-class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
4961+class AutoReverseOneToOneDescriptor(ReverseOneToOneDescriptor):
4962 """The descriptor that handles the object creation for an
4963 AutoOneToOneField."""
4964
4965@@ -37,7 +29,7 @@
4966 model = getattr(self.related, 'related_model', self.related.model)
4967
4968 try:
4969- return (super(AutoSingleRelatedObjectDescriptor, self)
4970+ return (super(AutoReverseOneToOneDescriptor, self)
4971 .__get__(instance, instance_type))
4972 except model.DoesNotExist:
4973 obj = model(**{self.related.field.name: instance})
4974@@ -47,7 +39,7 @@
4975 # Don't return obj directly, otherwise it won't be added
4976 # to Django's cache, and the first 2 calls to obj.relobj
4977 # will return 2 different in-memory objects
4978- return (super(AutoSingleRelatedObjectDescriptor, self)
4979+ return (super(AutoReverseOneToOneDescriptor, self)
4980 .__get__(instance, instance_type))
4981
4982
4983@@ -57,4 +49,4 @@
4984
4985 def contribute_to_related_class(self, cls, related):
4986 setattr(cls, related.get_accessor_name(),
4987- AutoSingleRelatedObjectDescriptor(related))
4988+ AutoReverseOneToOneDescriptor(related))
4989
4990=== modified file 'wlggz/views.py'
4991--- wlggz/views.py 2016-12-13 18:28:51 +0000
4992+++ wlggz/views.py 2018-04-19 17:49:06 +0000
4993@@ -1,10 +1,9 @@
4994 # Create your views here.
4995
4996
4997-from django.core.urlresolvers import reverse
4998+from django.urls import reverse
4999 from django.contrib.auth.decorators import login_required
5000-from django.shortcuts import render_to_response
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches