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
=== modified file 'check_input/__init__.py'
--- check_input/__init__.py 2017-11-24 11:11:43 +0000
+++ check_input/__init__.py 2018-04-19 17:49:06 +0000
@@ -1,1 +0,0 @@
1default_app_config = 'check_input.apps.CheckInput'
20
=== added directory 'django_messages_wl'
=== added file 'django_messages_wl/__init__.py'
=== added file 'django_messages_wl/apps.py'
--- django_messages_wl/apps.py 1970-01-01 00:00:00 +0000
+++ django_messages_wl/apps.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,9 @@
1from django_messages.apps import DjangoMessagesConfig
2from django.db.models import signals
3
4
5class WLDjangoMessagesConfig(DjangoMessagesConfig):
6
7 def ready(self):
8 from django_messages_wl.management import create_notice_types
9 signals.post_migrate.connect(create_notice_types, sender=self)
0\ No newline at end of file10\ No newline at end of file
111
=== added file 'django_messages_wl/management.py'
--- django_messages_wl/management.py 1970-01-01 00:00:00 +0000
+++ django_messages_wl/management.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,18 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4from django.conf import settings
5from django.utils.translation import ugettext_lazy as _
6
7
8if 'notification' in settings.INSTALLED_APPS and getattr(settings, 'DJANGO_MESSAGES_NOTIFY', True):
9 from notification import models as notification
10
11 def create_notice_types(sender, **kwargs):
12 print('Creating wl specific noticetypes for django-messages ...')
13 notification.create_notice_type('messages_received', _(
14 'Message Received'), _('you have received a message'), default=2)
15 notification.create_notice_type('messages_reply_received', _(
16 'Reply Received'), _('you have received a reply to a message'), default=2)
17else:
18 print('Skipping creation of NoticeTypes as notification app not found')
019
=== added file 'django_messages_wl/urls.py'
--- django_messages_wl/urls.py 1970-01-01 00:00:00 +0000
+++ django_messages_wl/urls.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,9 @@
1from django.conf.urls import *
2from django_messages.urls import urlpatterns
3from . import views
4
5
6urlpatterns += [
7 url(r'^django_messages_wl/get_usernames/', views.get_usernames),
8]
9
010
=== added file 'django_messages_wl/views.py'
--- django_messages_wl/views.py 1970-01-01 00:00:00 +0000
+++ django_messages_wl/views.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,27 @@
1from django.contrib.auth.models import User
2from django.http import HttpResponse
3import json
4
5
6def get_usernames(request):
7 """AJAX Callback for JS autocomplete.
8
9 This is used for autocompletion of usernames when writing PMs.
10 The path.name of this function has to be used in each place:
11 1. Argument of source of the JS widget
12 2. urls.py
13
14 """
15 if request.is_ajax():
16 q = request.GET.get('term', '')
17
18 usernames = User.objects.filter(username__icontains=q)
19 results = []
20 for user in usernames:
21 name_json = {'value': user.username}
22 results.append(name_json)
23 data = json.dumps(results)
24 else:
25 data = 'fail'
26 mimetype = 'application/json'
27 return HttpResponse(data, mimetype)
028
=== modified file 'djangoratings/fields.py'
--- djangoratings/fields.py 2016-12-13 18:28:51 +0000
+++ djangoratings/fields.py 2018-04-19 17:49:06 +0000
@@ -115,7 +115,7 @@
115 key=self.field.key,115 key=self.field.key,
116 )116 )
117117
118 if not (user and user.is_authenticated()):118 if not (user and user.is_authenticated):
119 if not ip_address:119 if not ip_address:
120 raise ValueError('``user`` or ``ip_address`` must be present.')120 raise ValueError('``user`` or ``ip_address`` must be present.')
121 kwargs['user__isnull'] = True121 kwargs['user__isnull'] = True
@@ -169,7 +169,7 @@
169 raise InvalidRating('%s is not a valid choice for %s' %169 raise InvalidRating('%s is not a valid choice for %s' %
170 (score, self.field.name))170 (score, self.field.name))
171171
172 is_anonymous = (user is None or not user.is_authenticated())172 is_anonymous = (user is None or not user.is_authenticated)
173 if is_anonymous and not self.field.allow_anonymous:173 if is_anonymous and not self.field.allow_anonymous:
174 raise AuthRequired("user must be a user, not '%r'" % (user,))174 raise AuthRequired("user must be a user, not '%r'" % (user,))
175175
176176
=== modified file 'djangoratings/management/commands/update_recommendations.py'
--- djangoratings/management/commands/update_recommendations.py 2016-12-13 18:28:51 +0000
+++ djangoratings/management/commands/update_recommendations.py 2018-04-19 17:49:06 +0000
@@ -1,9 +1,9 @@
1from django.core.management.base import NoArgsCommand, CommandError1from django.core.management.base import BaseCommand, CommandError
22
3from djangoratings.models import SimilarUser3from djangoratings.models import SimilarUser
44
55
6class Command(NoArgsCommand):6class Command(BaseCommand):
77
8 def handle_noargs(self, **options):8 def handle(self, *args, **options):
9 SimilarUser.objects.update_recommendations()9 SimilarUser.objects.update_recommendations()
1010
=== modified file 'djangoratings/templatetags/ratings.py'
--- djangoratings/templatetags/ratings.py 2016-12-13 18:28:51 +0000
+++ djangoratings/templatetags/ratings.py 2018-04-19 17:49:06 +0000
@@ -20,8 +20,8 @@
2020
21 def render(self, context):21 def render(self, context):
22 try:22 try:
23 request = template.resolve_variable(self.request, context)23 request = django.template.Variable(self.request).resolve(context)
24 obj = template.resolve_variable(self.obj, context)24 obj = django.template.Variable(self.obj).resolve(context)
25 field = getattr(obj, self.field_name)25 field = getattr(obj, self.field_name)
26 except (template.VariableDoesNotExist, AttributeError):26 except (template.VariableDoesNotExist, AttributeError):
27 return ''27 return ''
@@ -63,8 +63,8 @@
6363
64 def render(self, context):64 def render(self, context):
65 try:65 try:
66 user = template.resolve_variable(self.request, context)66 user = django.template.Variable(self.request).resolve(context)
67 obj = template.resolve_variable(self.obj, context)67 obj = django.template.Variable(self.obj).resolve(context)
68 field = getattr(obj, self.field_name)68 field = getattr(obj, self.field_name)
69 except template.VariableDoesNotExist:69 except template.VariableDoesNotExist:
70 return ''70 return ''
7171
=== modified file 'local_settings.py.sample'
--- local_settings.py.sample 2017-11-25 11:11:17 +0000
+++ local_settings.py.sample 2018-04-19 17:49:06 +0000
@@ -27,6 +27,13 @@
27 'PASSWORD': '', # Not used with sqlite3.27 'PASSWORD': '', # Not used with sqlite3.
28 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.28 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
29 'PORT': '', # Set to empty string for default. Not used with sqlite3.29 'PORT': '', # Set to empty string for default. Not used with sqlite3.
30 # Next is only used for mysql. Explanations:
31 # https://docs.djangoproject.com/en/1.11/ref/databases/#connecting-to-the-database
32 # 'init_command': is recommended for MySQL >= 5.6
33 # 'OPTIONS': {
34 # 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
35 # 'isolation_level': 'read committed',
36 #},
30 }37 }
31}38}
3239
3340
=== modified file 'local_urls.py.sample'
--- local_urls.py.sample 2016-06-04 14:17:40 +0000
+++ local_urls.py.sample 2018-04-19 17:49:06 +0000
@@ -1,14 +1,14 @@
1from django.conf.urls import *1from django.conf.urls import *
2from django.conf import settings2from django.conf import settings
33from django.views.static import serve
44
5local_urlpatterns = [5local_urlpatterns = [
6 url(r'^wlmedia/(?P<path>.*)$',6 url(r'^wlmedia/(?P<path>.*)$',
7 'django.views.static.serve',7 serve,
8 {'document_root': settings.STATIC_MEDIA_PATH},8 {'document_root': settings.STATIC_MEDIA_PATH},
9 name='static_media'),9 name='static_media'),
10 url(r'^media/(?P<path>.*)$',10 url(r'^media/(?P<path>.*)$',
11 'django.views.static.serve',11 serve,
12 {'document_root': settings.STATIC_MEDIA_PATH},12 {'document_root': settings.STATIC_MEDIA_PATH},
13 name='static_media_pybb'),13 name='static_media_pybb'),
14]14]
1515
=== removed file 'mainpage/templatetags/online_users.py'
--- mainpage/templatetags/online_users.py 2016-12-13 18:28:51 +0000
+++ mainpage/templatetags/online_users.py 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
1#!/usr/bin/env python -tt
2# encoding: utf-8
3#
4# File: online_user.py
5#
6# Created by Holger Rapp on 2009-02-19.
7# Copyright (c) 2009 HolgerRapp@gmx.net. All rights reserved.
8#
9# Last Modified: 2009-02-20 22:37:16
10#
11
12from django import template
13from django.contrib.auth.models import User
14from django.db.models import Count
15import datetime
16from tracking.models import Visitor
17
18register = template.Library()
19
20
21@register.inclusion_tag('mainpage/online_users.html')
22def online_users(num):
23 """Show user that has been login an hour ago."""
24 users = [l.user for l in Visitor.objects.active().exclude(user=None)]
25
26 # There might still be duplicates, so we make a set and order it into
27 # a list again
28 users = sorted(list(set(users)), key=lambda user: user.username)
29
30 return {
31 'users': users,
32 }
330
=== modified file 'mainpage/templatetags/wl_extras.py'
--- mainpage/templatetags/wl_extras.py 2018-02-17 11:22:19 +0000
+++ mainpage/templatetags/wl_extras.py 2018-04-19 17:49:06 +0000
@@ -2,6 +2,7 @@
2# encoding: utf-82# encoding: utf-8
33
4from django import template4from django import template
5from django.utils.safestring import mark_safe
56
6register = template.Library()7register = template.Library()
78
@@ -22,14 +23,6 @@
22 return settings.LOGO_FILE23 return settings.LOGO_FILE
2324
2425
25@register.simple_tag
26def all_users():
27 """Provide a list of all users."""
28
29 from django.contrib.auth.models import User
30 return [str(u.username) for u in User.objects.all()]
31
32
33@register.inclusion_tag('mainpage/forum_navigation.html')26@register.inclusion_tag('mainpage/forum_navigation.html')
34def forum_navigation():27def forum_navigation():
35 """Makes the forum list available to the navigation.28 """Makes the forum list available to the navigation.
3629
=== modified file 'mainpage/urls.py'
--- mainpage/urls.py 2016-12-13 18:28:51 +0000
+++ mainpage/urls.py 2018-04-19 17:49:06 +0000
@@ -1,8 +1,12 @@
1from django.conf.urls import *1from django.conf.urls import *
2from widelands.mainpage import views2from mainpage import views
33
4urlpatterns = patterns('',4urlpatterns = [
5 # Example:5 url(r'^$', views.mainpage, name='mainpage'),
6 url(r'^$', views.mainpage, name='mainpage'),6 url(r'^locale/$', views.view_locale),
77 url(r'^changelog/$', views.changelog, name='changelog'),
8 )8 url(r'^developers/$', views.developers, name='developers'),
9 url(r'^legal_notice/$', views.legal_notice, name='legal_notice'),
10 url(r'^legal_notice_thanks/$', views.legal_notice_thanks,
11 name='legal_notice_thanks'),
12]
913
=== modified file 'mainpage/views.py'
--- mainpage/views.py 2018-02-11 13:50:52 +0000
+++ mainpage/views.py 2018-04-19 17:49:06 +0000
@@ -1,4 +1,3 @@
1from django.template import RequestContext
2from settings import WIDELANDS_SVN_DIR, INQUIRY_RECIPIENTS1from settings import WIDELANDS_SVN_DIR, INQUIRY_RECIPIENTS
3from templatetags.wl_markdown import do_wl_markdown2from templatetags.wl_markdown import do_wl_markdown
4from operator import itemgetter3from operator import itemgetter
54
=== modified file 'news/feeds.py'
--- news/feeds.py 2016-12-13 18:28:51 +0000
+++ news/feeds.py 2018-04-19 17:49:06 +0000
@@ -1,6 +1,6 @@
1from django.contrib.syndication.views import Feed, FeedDoesNotExist1from django.contrib.syndication.views import Feed, FeedDoesNotExist
2from django.core.exceptions import ObjectDoesNotExist2from django.core.exceptions import ObjectDoesNotExist
3from django.core.urlresolvers import reverse3from django.urls import reverse
4from news.models import Post, Category4from news.models import Post, Category
55
6# Validated through http://validator.w3.org/feed/6# Validated through http://validator.w3.org/feed/
77
=== modified file 'news/models.py'
--- news/models.py 2017-09-12 18:04:34 +0000
+++ news/models.py 2018-04-19 17:49:06 +0000
@@ -4,7 +4,7 @@
4from django.contrib.auth.models import User4from django.contrib.auth.models import User
5from tagging.fields import TagField5from tagging.fields import TagField
6from news.managers import PublicManager6from news.managers import PublicManager
7from django.core.urlresolvers import reverse7from django.urls import reverse
8import datetime8import datetime
9import settings9import settings
10import tagging10import tagging
1111
=== modified file 'notification/management/commands/emit_notices.py'
--- notification/management/commands/emit_notices.py 2017-04-20 20:22:28 +0000
+++ notification/management/commands/emit_notices.py 2018-04-19 17:49:06 +0000
@@ -1,15 +1,15 @@
11
2import logging2import logging
33
4from django.core.management.base import NoArgsCommand4from django.core.management.base import BaseCommand
55
6from notification.engine import send_all6from notification.engine import send_all
77
88
9class Command(NoArgsCommand):9class Command(BaseCommand):
10 help = 'Emit queued notices.'10 help = 'Emit queued notices.'
1111
12 def handle_noargs(self, **options):12 def handle(self, *args, **options):
13 # Franku: Uncomment for debugging purposes13 # Franku: Uncomment for debugging purposes
14 # logging.basicConfig(level=logging.DEBUG, format='%(message)s')14 # logging.basicConfig(level=logging.DEBUG, format='%(message)s')
15 logging.info('-' * 72)15 logging.info('-' * 72)
1616
=== modified file 'notification/models.py'
--- notification/models.py 2018-02-05 12:32:22 +0000
+++ notification/models.py 2018-04-19 17:49:06 +0000
@@ -8,8 +8,7 @@
8from django.db import models8from django.db import models
9from django.db.models.query import QuerySet9from django.db.models.query import QuerySet
10from django.conf import settings10from django.conf import settings
11from django.core.urlresolvers import reverse11from django.urls import reverse
12from django.template import Context
13from django.template.loader import render_to_string12from django.template.loader import render_to_string
1413
15from django.core.exceptions import ImproperlyConfigured14from django.core.exceptions import ImproperlyConfigured
@@ -132,7 +131,7 @@
132def create_notice_type(label, display, description, default=2, verbosity=1):131def create_notice_type(label, display, description, default=2, verbosity=1):
133 """Creates a new NoticeType.132 """Creates a new NoticeType.
134133
135 This is intended to be used by other apps as a post_syncdb134 This is intended to be used by other apps as a post_migrate
136 manangement step.135 manangement step.
137136
138 """137 """
@@ -186,15 +185,14 @@
186185
187 """186 """
188 format_templates = {}187 format_templates = {}
188
189 for format in formats:189 for format in formats:
190 # conditionally turn off autoescaping for .txt extensions in format190 # Switch off escaping for .txt templates was done here, but now it
191 if format.endswith('.txt'):191 # resides in the templates
192 context.autoescape = False
193 else:
194 context.autoescape = True
195 format_templates[format] = render_to_string((192 format_templates[format] = render_to_string((
196 'notification/%s/%s' % (label, format),193 'notification/%s/%s' % (label, format),
197 'notification/%s' % format), context_instance=context)194 'notification/%s' % format), context)
195
198 return format_templates196 return format_templates
199197
200198
@@ -232,8 +230,8 @@
232 current_language = get_language()230 current_language = get_language()
233231
234 formats = (232 formats = (
235 'short.txt',233 'short.txt', # used for subject
236 'full.txt',234 'full.txt', # used for email body
237 ) # TODO make formats configurable235 ) # TODO make formats configurable
238236
239 for user in users:237 for user in users:
@@ -250,22 +248,20 @@
250 activate(language)248 activate(language)
251249
252 # update context with user specific translations250 # update context with user specific translations
253 context = Context({251 context = {
254 'user': user,252 'user': user,
255 'notices_url': notices_url,
256 'current_site': current_site,253 'current_site': current_site,
257 'subject': notice_type.display,254 'subject': notice_type.display
258 'description': notice_type.description,255 }
259 })
260 context.update(extra_context)256 context.update(extra_context)
261257
262 # get prerendered format messages258 # get prerendered format messages and subjects
263 messages = get_formatted_messages(formats, label, context)259 messages = get_formatted_messages(formats, label, context)
264260
265 # Strip newlines from subject261 # Create the subject
266 subject = ''.join(render_to_string('notification/email_subject.txt', {262 # Use 'email_subject.txt' to add Strings in every emails subject
267 'message': messages['short.txt'],263 subject = render_to_string('notification/email_subject.txt',
268 }, context).splitlines())264 {'message': messages['short.txt'],}).replace('\n', '')
269 265
270 # Strip leading newlines. Make writing the email templates easier:266 # Strip leading newlines. Make writing the email templates easier:
271 # Each linebreak in the templates results in a linebreak in the emails267 # Each linebreak in the templates results in a linebreak in the emails
@@ -273,12 +269,14 @@
273 # email will contain an empty line at the top.269 # email will contain an empty line at the top.
274 body = render_to_string('notification/email_body.txt', {270 body = render_to_string('notification/email_body.txt', {
275 'message': messages['full.txt'],271 'message': messages['full.txt'],
276 }, context).lstrip()272 'notices_url': notices_url,
273 }).lstrip()
277274
278 if should_send(user, notice_type, '1') and user.email: # Email275 if should_send(user, notice_type, '1') and user.email: # Email
279 recipients.append(user.email)276 recipients.append(user.email)
277
280 send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, recipients)278 send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, recipients)
281279
282 # reset environment to original language280 # reset environment to original language
283 activate(current_language)281 activate(current_language)
284 except NoticeType.DoesNotExist:282 except NoticeType.DoesNotExist:
285283
=== modified file 'notification/views.py'
--- notification/views.py 2017-05-03 19:12:49 +0000
+++ notification/views.py 2018-04-19 17:49:06 +0000
@@ -1,5 +1,4 @@
1from django.shortcuts import render_to_response1from django.shortcuts import render
2from django.template import RequestContext
3from django.contrib.auth.decorators import login_required2from django.contrib.auth.decorators import login_required
4from collections import OrderedDict3from collections import OrderedDict
5from notification.models import *4from notification.models import *
@@ -28,7 +27,7 @@
2827
29 app_tables[app].append({'notice_type': notice_type, 'html_values': checkbox_values})28 app_tables[app].append({'notice_type': notice_type, 'html_values': checkbox_values})
3029
31 return render_to_response('notification/notice_settings.html', {30 return render(request, 'notification/notice_settings.html', {
32 'column_headers': [medium_display for medium_id, medium_display in NOTICE_MEDIA],31 'column_headers': [medium_display for medium_id, medium_display in NOTICE_MEDIA],
33 'app_tables': OrderedDict(sorted(app_tables.items(), key=lambda t: t[0]))32 'app_tables': OrderedDict(sorted(app_tables.items(), key=lambda t: t[0]))
34 }, context_instance=RequestContext(request))33 })
3534
=== added file 'online_users_middleware.py'
--- online_users_middleware.py 1970-01-01 00:00:00 +0000
+++ online_users_middleware.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,56 @@
1from django.conf import settings
2from django.core.cache import cache
3from django.contrib.auth.models import User
4from django.utils.deprecation import MiddlewareMixin
5from django.contrib.auth import user_logged_out
6from django.dispatch import receiver
7
8ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 60 * 15)
9ONLINE_MAX = getattr(settings, 'ONLINE_MAX', 50)
10
11
12def get_online_now(self):
13 return User.objects.filter(id__in=self.online_now_ids or [])
14
15
16@receiver(user_logged_out)
17def logout(sender, **kwargs):
18 cache.delete('online-%s' % kwargs['user'].id)
19
20
21class OnlineNowMiddleware(MiddlewareMixin):
22 """Maintains a list of users who have interacted with the website recently.
23
24 Their user IDs are available as ``online_now_ids`` on the request
25 object, and their corresponding users are available (lazily) as the
26 ``online_now`` property on the request object.
27
28 """
29
30 def process_request(self, request):
31 # First get the index
32 uids = cache.get('online-now', [])
33
34 # Perform the multiget on the individual online uid keys
35 online_keys = ['online-%s' % (u,) for u in uids]
36 fresh = cache.get_many(online_keys).keys()
37 online_now_ids = [int(k.replace('online-', '')) for k in fresh]
38
39 # If the user is authenticated, add their id to the list
40 if request.user.is_authenticated():
41 uid = request.user.id
42 # If their uid is already in the list, we want to bump it
43 # to the top, so we remove the earlier entry.
44 if uid in online_now_ids:
45 online_now_ids.remove(uid)
46 online_now_ids.append(uid)
47 if len(online_now_ids) > ONLINE_MAX:
48 del online_now_ids[0]
49
50 # Attach our modifications to the request object
51 request.__class__.online_now_ids = online_now_ids
52 request.__class__.online_now = property(get_online_now)
53
54 # Set the new cache
55 cache.set('online-%s' % (request.user.pk,), True, ONLINE_THRESHOLD)
56 cache.set('online-now', online_now_ids, ONLINE_THRESHOLD)
057
=== modified file 'pip_requirements.txt'
--- pip_requirements.txt 2018-01-12 19:52:31 +0000
+++ pip_requirements.txt 2018-04-19 17:49:06 +0000
@@ -1,15 +1,15 @@
1# Python requirements for widelands-website at 22.06.20171# Python requirements for widelands-website at 22.06.2017
22
3BeautifulSoup==3.2.03BeautifulSoup==3.2.0
4Django==1.84Django==1.11.12
5django-contrib-comments==1.8.05django-contrib-comments==1.8.0
6django-haystack==2.6.16django-haystack==2.8.1
7# django-messages is very old on pypi7# django-messages is very old on pypi
8# Do not install newer versions because our notifications app is affected8# Do not install newer versions because our notifications app is affected
9-e git://github.com/arneb/django-messages.git@2d8dabb755e0b5ace876bde25f45d07c2051ac37#egg=django_messages9-e git://github.com/arneb/django-messages.git@2d8dabb755e0b5ace876bde25f45d07c2051ac37#egg=django_messages
10django-nocaptcha-recaptcha==0.0.1910django-nocaptcha-recaptcha==0.0.19
11-e git://github.com/zyga/django-pagination.git#egg=linaro_django_pagination11dj-pagination==2.3.2
12django-registration==2.212django-registration==2.4.1
13django-tagging==0.4.513django-tagging==0.4.5
14gunicorn==19.7.114gunicorn==19.7.1
15Markdown==2.6.815Markdown==2.6.8
1616
=== added file 'pybb/apps.py'
--- pybb/apps.py 1970-01-01 00:00:00 +0000
+++ pybb/apps.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,12 @@
1from django.apps import AppConfig
2from django.db.models import signals
3
4
5class PybbConfig(AppConfig):
6
7 name = 'pybb'
8 verbose_name = 'Pybb'
9
10 def ready(self):
11 from pybb.management.pybb_notifications import create_notice_types
12 signals.post_migrate.connect(create_notice_types, sender=self)
013
=== modified file 'pybb/feeds.py'
--- pybb/feeds.py 2016-12-13 18:28:51 +0000
+++ pybb/feeds.py 2018-04-19 17:49:06 +0000
@@ -1,5 +1,5 @@
1from django.contrib.syndication.views import Feed1from django.contrib.syndication.views import Feed
2from django.core.urlresolvers import reverse2from django.urls import reverse
3from django.core.exceptions import ObjectDoesNotExist3from django.core.exceptions import ObjectDoesNotExist
4from django.utils.feedgenerator import Atom1Feed4from django.utils.feedgenerator import Atom1Feed
5from pybb.models import Post, Topic, Forum5from pybb.models import Post, Topic, Forum
66
=== modified file 'pybb/forms.py'
--- pybb/forms.py 2017-11-23 16:12:01 +0000
+++ pybb/forms.py 2018-04-19 17:49:06 +0000
@@ -28,13 +28,6 @@
28 self.ip = kwargs.pop('ip', None)28 self.ip = kwargs.pop('ip', None)
29 super(AddPostForm, self).__init__(*args, **kwargs)29 super(AddPostForm, self).__init__(*args, **kwargs)
3030
31 # TODO(Franku): This doesn't work anymore with django 1.8 Use 'field_order'
32 # with django 1.9
33 self.fields.keyOrder = ['name',
34 'body',
35 'markup',
36 'attachment']
37
38 if self.topic:31 if self.topic:
39 self.fields['name'].widget = forms.HiddenInput()32 self.fields['name'].widget = forms.HiddenInput()
40 self.fields['name'].required = False33 self.fields['name'].required = False
4134
=== modified file 'pybb/management/commands/pybb_resave_post.py'
--- pybb/management/commands/pybb_resave_post.py 2016-12-13 18:28:51 +0000
+++ pybb/management/commands/pybb_resave_post.py 2018-04-19 17:49:06 +0000
@@ -1,4 +1,3 @@
1from optparse import make_option
21
3from django.core.management.base import BaseCommand, CommandError2from django.core.management.base import BaseCommand, CommandError
4from django.contrib.auth.models import User3from django.contrib.auth.models import User
54
=== modified file 'pybb/management/pybb_notifications.py'
--- pybb/management/pybb_notifications.py 2017-04-29 19:52:28 +0000
+++ pybb/management/pybb_notifications.py 2018-04-19 17:49:06 +0000
@@ -1,11 +1,11 @@
1from django.db.models import signals
21
3from django.utils.translation import ugettext_noop as _2from django.utils.translation import ugettext_noop as _
43
5try:4try:
6 from notification import models as notification5 from notification import models as notification
76
8 def create_notice_types(app, created_models, verbosity, **kwargs):7 def create_notice_types(sender, **kwargs):
8 print("Creating noticetypes for pybb ...")
9 notification.create_notice_type('forum_new_topic',9 notification.create_notice_type('forum_new_topic',
10 _('Forum New Topic'),10 _('Forum New Topic'),
11 _('a new topic has been added to the forum'),11 _('a new topic has been added to the forum'),
@@ -13,10 +13,5 @@
13 notification.create_notice_type('forum_new_post',13 notification.create_notice_type('forum_new_post',
14 _('Forum New Post'),14 _('Forum New Post'),
15 _('a new comment has been posted to a topic you observe'))15 _('a new comment has been posted to a topic you observe'))
16
17 # TODO (Franku): post_syncdb is deprecated since Django 1.7
18 # See: https://docs.djangoproject.com/en/1.8/ref/signals/#post-syncdb
19 signals.post_syncdb.connect(create_notice_types,
20 sender=notification)
21except ImportError:16except ImportError:
22 print 'Skipping creation of NoticeTypes as notification app not found'17 print 'Skipping creation of NoticeTypes as notification app not found'
2318
=== modified file 'pybb/models.py'
--- pybb/models.py 2017-09-12 18:04:34 +0000
+++ pybb/models.py 2018-04-19 17:49:06 +0000
@@ -5,7 +5,7 @@
55
6from django.db import models6from django.db import models
7from django.contrib.auth.models import User7from django.contrib.auth.models import User
8from django.core.urlresolvers import reverse8from django.urls import reverse
9from django.utils.html import strip_tags9from django.utils.html import strip_tags
10from django.utils.translation import ugettext_lazy as _10from django.utils.translation import ugettext_lazy as _
11from django.conf import settings11from django.conf import settings
1212
=== modified file 'pybb/templatetags/pybb_extras.py'
--- pybb/templatetags/pybb_extras.py 2017-06-22 06:33:22 +0000
+++ pybb/templatetags/pybb_extras.py 2018-04-19 17:49:06 +0000
@@ -5,9 +5,7 @@
5from pprint import pprint5from pprint import pprint
66
7from django import template7from django import template
8from django.core.urlresolvers import reverse
9from django.utils.safestring import mark_safe8from django.utils.safestring import mark_safe
10from django.template import RequestContext
11from django.template.defaultfilters import stringfilter9from django.template.defaultfilters import stringfilter
12from django.utils.encoding import smart_unicode10from django.utils.encoding import smart_unicode
13from django.utils.html import escape11from django.utils.html import escape
@@ -132,7 +130,7 @@
132 else:130 else:
133 return topic.updated <= read.time131 return topic.updated <= read.time
134132
135 if not user.is_authenticated():133 if not user.is_authenticated:
136 return False134 return False
137 else:135 else:
138 if isinstance(topic, Topic):136 if isinstance(topic, Topic):
139137
=== modified file 'pybb/unread.py'
--- pybb/unread.py 2016-12-13 18:28:51 +0000
+++ pybb/unread.py 2018-04-19 17:49:06 +0000
@@ -2,7 +2,7 @@
22
33
4def cache_unreads(qs, user):4def cache_unreads(qs, user):
5 if not len(qs) or not user.is_authenticated():5 if not len(qs) or not user.is_authenticated:
6 return qs6 return qs
7 if isinstance(qs[0], Topic):7 if isinstance(qs[0], Topic):
8 reads = Read.objects.filter(topic__pk__in=set(x.id for x in qs),8 reads = Read.objects.filter(topic__pk__in=set(x.id for x in qs),
99
=== modified file 'pybb/urls.py'
--- pybb/urls.py 2016-12-13 18:28:51 +0000
+++ pybb/urls.py 2018-04-19 17:49:06 +0000
@@ -48,7 +48,7 @@
48 url('^api/post_ajax_preview/$', views.post_ajax_preview,48 url('^api/post_ajax_preview/$', views.post_ajax_preview,
49 name='pybb_post_ajax_preview'),49 name='pybb_post_ajax_preview'),
5050
51 # Subsciption51 # Subscription
52 url('^topic/(?P<topic_id>\d+)/subscribe/$',52 url('^topic/(?P<topic_id>\d+)/subscribe/$',
53 views.add_subscription, name='pybb_add_subscription'),53 views.add_subscription, name='pybb_add_subscription'),
54 url('^topic/(?P<topic_id>\d+)/unsubscribe/$',54 url('^topic/(?P<topic_id>\d+)/unsubscribe/$',
5555
=== removed file 'pybb/urls.py.orig'
--- pybb/urls.py.orig 2009-02-25 16:55:36 +0000
+++ pybb/urls.py.orig 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1from django.conf.urls.defaults import *
2from django.views.generic.simple import redirect_to
3from django.conf import settings
4import django.views.static
5
6from django.contrib import admin
7admin.autodiscover()
8
9urlpatterns = patterns('',
10 (r'^$', redirect_to, {'url': '/forum/'}),
11 (r'^admin/(.*)', admin.site.root),
12 (r'', include('account.urls')),
13 # (r'^forum/', include('pybb.urls')),
14 # (r'^forum/', include('forum.urls')),
15)
16
17if (settings.DEBUG):
18 urlpatterns += patterns('',
19 (r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
20 django.views.static.serve, {'document_root': settings.MEDIA_ROOT}),
21 )
220
=== modified file 'pybb/util.py'
--- pybb/util.py 2017-08-21 19:13:19 +0000
+++ pybb/util.py 2018-04-19 17:49:06 +0000
@@ -5,8 +5,7 @@
55
6from BeautifulSoup import BeautifulSoup6from BeautifulSoup import BeautifulSoup
7from datetime import datetime7from datetime import datetime
8from django.shortcuts import render_to_response8from django.shortcuts import render
9from django.template import RequestContext
10from django.http import HttpResponse9from django.http import HttpResponse
11from django.utils.functional import Promise10from django.utils.functional import Promise
12from django.utils.translation import check_for_language11from django.utils.translation import check_for_language
@@ -32,7 +31,7 @@
32 output = func(request, *args, **kwargs)31 output = func(request, *args, **kwargs)
33 if not isinstance(output, dict):32 if not isinstance(output, dict):
34 return output33 return output
35 kwargs = {'context_instance': RequestContext(request)}34
3635
37 # TODO(Franku): 'MIME_TYPE' is never in output as i can see for now.36 # TODO(Franku): 'MIME_TYPE' is never in output as i can see for now.
38 # But if, this should maybe 'content_type' instead37 # But if, this should maybe 'content_type' instead
@@ -43,7 +42,7 @@
43 else:42 else:
44 template = template_path43 template = template_path
4544
46 return render_to_response(template, output, **kwargs)45 return render(request, template, output)
47 return wrapper46 return wrapper
4847
49 return decorator48 return decorator
5049
=== modified file 'pybb/views.py'
--- pybb/views.py 2017-12-10 13:18:47 +0000
+++ pybb/views.py 2018-04-19 17:49:06 +0000
@@ -7,7 +7,7 @@
7from django.contrib.auth.models import User7from django.contrib.auth.models import User
8from django.contrib.auth.decorators import login_required8from django.contrib.auth.decorators import login_required
9from django.conf import settings9from django.conf import settings
10from django.core.urlresolvers import reverse10from django.urls import reverse
11from django.db import connection11from django.db import connection
12from django.utils import translation12from django.utils import translation
13from django.shortcuts import render13from django.shortcuts import render
@@ -88,7 +88,7 @@
88 topic.views += 188 topic.views += 1
89 topic.save()89 topic.save()
9090
91 if request.user.is_authenticated():91 if request.user.is_authenticated:
92 topic.update_read(request.user)92 topic.update_read(request.user)
9393
94 if pybb_settings.FREEZE_FIRST_POST:94 if pybb_settings.FREEZE_FIRST_POST:
@@ -98,13 +98,13 @@
98 last_post = topic.posts.order_by('-created')[0]98 last_post = topic.posts.order_by('-created')[0]
9999
100 initial = {}100 initial = {}
101 if request.user.is_authenticated():101 if request.user.is_authenticated:
102 initial = {'markup': 'markdown'}102 initial = {'markup': 'markdown'}
103 form = AddPostForm(topic=topic, initial=initial)103 form = AddPostForm(topic=topic, initial=initial)
104104
105 moderator = (request.user.is_superuser or105 moderator = (request.user.is_superuser or
106 request.user in topic.forum.moderators.all())106 request.user in topic.forum.moderators.all())
107 subscribed = (request.user.is_authenticated() and107 subscribed = (request.user.is_authenticated and
108 request.user in topic.subscribers.all())108 request.user in topic.subscribers.all())
109109
110 posts = topic.posts.exclude(hidden=True).select_related()110 posts = topic.posts.exclude(hidden=True).select_related()
111111
=== modified file 'settings.py'
--- settings.py 2017-12-23 09:15:02 +0000
+++ settings.py 2018-04-19 17:49:06 +0000
@@ -67,7 +67,7 @@
67 'django.contrib.staticfiles.finders.AppDirectoriesFinder',67 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
68]68]
6969
70INSTALLED_APPS = (70INSTALLED_APPS = [
71 'django.contrib.admin',71 'django.contrib.admin',
72 'django.contrib.auth',72 'django.contrib.auth',
73 'django.contrib.contenttypes',73 'django.contrib.contenttypes',
@@ -78,8 +78,6 @@
78 'django.contrib.humanize',78 'django.contrib.humanize',
79 'django.contrib.sitemaps',79 'django.contrib.sitemaps',
80 'nocaptcha_recaptcha',80 'nocaptcha_recaptcha',
81 # Thirdparty apps, but need preload
82 'tracking', # included as wlapp
8381
84 # Our own apps82 # Our own apps
85 'wiki.templatetags.restructuredtext',83 'wiki.templatetags.restructuredtext',
@@ -91,43 +89,42 @@
91 'wlsearch',89 'wlsearch',
92 'wlpoll',90 'wlpoll',
93 'wlevents',91 'wlevents',
94 'wlmaps',92 'wlmaps.apps.WlMapsConfig',
95 'wlscreens',93 'wlscreens',
96 'wlggz',94 'wlggz',
97 'wlscheduling',95 'wlscheduling',
98 'check_input',96 'check_input.apps.CheckInput',
99 'haystack', # search engine; see option HAYSTACK_CONNECTIONS97 'haystack', # search engine; see option HAYSTACK_CONNECTIONS
10098
101 # Modified 3rd party apps99 # Modified 3rd party apps
102 'wiki', # This is based on wikiapp, but has some local modifications100 'wiki.apps.WikiConfig', # This is based on wikiapp, but has some local modifications
103 'news', # This is based on simple-blog, but has some local modifications101 'news', # This is based on simple-blog, but has some local modifications
104 'news.managers',102 'news.managers',
105 'pybb', # Feature enriched version of pybb103 'pybb.apps.PybbConfig', # Feature enriched version of pybb
106104
107 # Thirdparty apps105 # Thirdparty apps
108 'threadedcomments', # included as wlapp106 'threadedcomments', # included as wlapp
109 'notification', # included as wlapp107 'notification', # included as wlapp
110 'django_messages',108 'django_messages_wl.apps.WLDjangoMessagesConfig',
111 'linaro_django_pagination',109 'dj_pagination',
112 'tagging',110 'tagging',
113 'djangoratings', # included as wlapp111 'djangoratings', # included as wlapp
114 'sphinxdoc', # included as wlapp112 'sphinxdoc', # included as wlapp
115)113]
116114
117MIDDLEWARE_CLASSES = (115MIDDLEWARE = [
116 'django.middleware.security.SecurityMiddleware',
117 'django.contrib.sessions.middleware.SessionMiddleware',
118 'django.middleware.common.CommonMiddleware',118 'django.middleware.common.CommonMiddleware',
119 'django.contrib.sessions.middleware.SessionMiddleware',
120 'django.contrib.messages.middleware.MessageMiddleware',
121 'django.middleware.csrf.CsrfViewMiddleware',119 'django.middleware.csrf.CsrfViewMiddleware',
122 'django.contrib.auth.middleware.AuthenticationMiddleware',120 'django.contrib.auth.middleware.AuthenticationMiddleware',
123 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',121 'django.contrib.messages.middleware.MessageMiddleware',
124 'django.middleware.clickjacking.XFrameOptionsMiddleware',122 'django.middleware.clickjacking.XFrameOptionsMiddleware',
125 'django.middleware.security.SecurityMiddleware',123
126124 # Foreign middleware
127 'linaro_django_pagination.middleware.PaginationMiddleware',125 'dj_pagination.middleware.PaginationMiddleware',
128 'tracking.middleware.VisitorTrackingMiddleware',126 'online_users_middleware.OnlineNowMiddleware',
129 'tracking.middleware.VisitorCleanUpMiddleware',127]
130)
131128
132TEMPLATES = [129TEMPLATES = [
133 {130 {
@@ -161,6 +158,13 @@
161DEFAULT_FROM_EMAIL = 'noreply@widelands.org'158DEFAULT_FROM_EMAIL = 'noreply@widelands.org'
162ACCOUNT_ACTIVATION_DAYS = 2 # Days an activation token keeps active159ACCOUNT_ACTIVATION_DAYS = 2 # Days an activation token keeps active
163160
161# Franku: SHA1 Needed as compatibility for old passwords
162# https://docs.djangoproject.com/en/1.11/releases/1.10/#removed-weak-password-hashers-from-the-default-password-hashers-setting
163PASSWORD_HASHERS = [
164 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
165 'django.contrib.auth.hashers.SHA1PasswordHasher'
166]
167
164######################168######################
165# Wiki configuration #169# Wiki configuration #
166######################170######################
@@ -253,11 +257,6 @@
253 },257 },
254}258}
255259
256############
257# Tracking #
258############
259TRACKING_CLEANUP_TIMEOUT = 48
260
261###########################260###########################
262# Widelands SVN directory #261# Widelands SVN directory #
263###########################262###########################
@@ -311,12 +310,21 @@
311BLEACH_ALLOWED_ATTRIBUTES = {'img': ['src', 'alt'], 'a': [310BLEACH_ALLOWED_ATTRIBUTES = {'img': ['src', 'alt'], 'a': [
312 'href'], 'td': ['align'], '*': ['class', 'id', 'title']}311 'href'], 'td': ['align'], '*': ['class', 'id', 'title']}
313312
314################################313##########################
315# Pagination settings #314# Pagination settings #
316# for linaro-django-pagination #315# for dj-pagination #
317################################316##########################
318PAGINATION_DEFAULT_WINDOW = 2317PAGINATION_DEFAULT_WINDOW = 2
319318
319###########################
320# Settings for displaying #
321# online users #
322###########################
323
324# Time in seconds how long a user will be shown online
325ONLINE_THRESHOLD = 60 * 30
326# Number of stored users
327ONLINE_MAX = 25
320328
321try:329try:
322 from local_settings import *330 from local_settings import *
323331
=== modified file 'sphinxdoc/models.py'
--- sphinxdoc/models.py 2016-12-13 18:28:51 +0000
+++ sphinxdoc/models.py 2018-04-19 17:49:06 +0000
@@ -4,6 +4,7 @@
4"""4"""
55
6from django.db import models6from django.db import models
7from django.urls import reverse
78
89
9class App(models.Model):10class App(models.Model):
@@ -15,9 +16,8 @@
15 def __unicode__(self):16 def __unicode__(self):
16 return self.name17 return self.name
1718
18 @models.permalink
19 def get_absolute_url(self):19 def get_absolute_url(self):
20 return ('doc-index', (), {'slug': self.slug})20 return reverse('doc-index', kwargs={'slug': self.slug})
2121
22 class Meta:22 class Meta:
23 app_label = 'sphinxdoc'23 app_label = 'sphinxdoc'
2424
=== modified file 'sphinxdoc/views.py'
--- sphinxdoc/views.py 2016-12-13 18:28:51 +0000
+++ sphinxdoc/views.py 2018-04-19 17:49:06 +0000
@@ -4,8 +4,8 @@
4import os.path4import os.path
55
6from django.http import Http4046from django.http import Http404
7from django.shortcuts import get_object_or_404, render_to_response7from django.shortcuts import get_object_or_404, render
8from django.template import RequestContext8#from django.template import RequestContext
9import json9import json
10from django.views import static10from django.views import static
1111
@@ -52,8 +52,7 @@
52 if 'title' not in data['doc']:52 if 'title' not in data['doc']:
53 data['doc']['title'] = SPECIAL_TITLES[page_name]53 data['doc']['title'] = SPECIAL_TITLES[page_name]
5454
55 return render_to_response(templates, data,55 return render(request, templates, data)
56 context_instance=RequestContext(request))
5756
5857
59def search(request, slug):58def search(request, slug):
6059
=== modified file 'static_sitemap.py'
--- static_sitemap.py 2016-11-22 19:21:00 +0000
+++ static_sitemap.py 2018-04-19 17:49:06 +0000
@@ -1,5 +1,5 @@
1from django.contrib.sitemaps import Sitemap1from django.contrib.sitemaps import Sitemap
2from django.core.urlresolvers import reverse2from django.urls import reverse
33
44
5class StaticViewSitemap(Sitemap):5class StaticViewSitemap(Sitemap):
66
=== modified file 'templates/django_messages/compose.html'
--- templates/django_messages/compose.html 2018-02-11 14:48:26 +0000
+++ templates/django_messages/compose.html 2018-04-19 17:49:06 +0000
@@ -10,7 +10,7 @@
10<script>10<script>
11 $(function() {11 $(function() {
12 $( "#id_recipient" ).autocomplete({12 $( "#id_recipient" ).autocomplete({
13 source: {% all_users %},13 source: '/messages/django_messages_wl/get_usernames/',
14 minLength: 3,14 minLength: 3,
15 });15 });
16});16});
1717
=== modified file 'templates/django_messages/inbox.html'
--- templates/django_messages/inbox.html 2017-11-07 18:04:38 +0000
+++ templates/django_messages/inbox.html 2018-04-19 17:49:06 +0000
@@ -31,7 +31,7 @@
31 </td>31 </td>
32 <td>{{ message.sent_at|custom_date:user }}</td>32 <td>{{ message.sent_at|custom_date:user }}</td>
33 <td>33 <td>
34 <a href="{% url 'django_messages.views.delete' message.id %}?next={{ request.path|iriencode }}">34 <a href="{% url 'messages_delete' message.id %}?next={{ request.path|iriencode }}">
35 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />35 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />
36 </a>36 </a>
37 </td>37 </td>
3838
=== modified file 'templates/django_messages/outbox.html'
--- templates/django_messages/outbox.html 2017-11-07 18:04:38 +0000
+++ templates/django_messages/outbox.html 2018-04-19 17:49:06 +0000
@@ -27,7 +27,7 @@
27 </td>27 </td>
28 <td>{{ message.sent_at|custom_date:user }}</td>28 <td>{{ message.sent_at|custom_date:user }}</td>
29 <td>29 <td>
30 <a href="{% url 'django_messages.views.delete' message.id %}?next={{ request.path|iriencode }}">30 <a href="{% url 'messages_delete' message.id %}?next={{ request.path|iriencode }}">
31 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />31 <img src="{{ MEDIA_URL }}img/delete.png" alt="delete" title="delete" />
32 </a>32 </a>
33 </td>33 </td>
3434
=== modified file 'templates/django_messages/trash.html'
--- templates/django_messages/trash.html 2017-11-07 18:04:38 +0000
+++ templates/django_messages/trash.html 2018-04-19 17:49:06 +0000
@@ -34,11 +34,11 @@
34 <td>{{ message.sent_at|custom_date:user }}</td>34 <td>{{ message.sent_at|custom_date:user }}</td>
35 <td>35 <td>
36 {% if message.sender == request.user %}36 {% if message.sender == request.user %}
37 <a href="{% url 'django_messages.views.undelete' message.id %}?next=/messages/outbox/">37 <a href="{% url 'messages_undelete' message.id %}?next=/messages/outbox/">
38 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />38 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />
39 </a>39 </a>
40 {% else %}40 {% else %}
41 <a href="{% url 'django_messages.views.undelete' message.id %}?next=/messages/inbox/">41 <a href="{% url 'messages_undelete' message.id %}?next=/messages/inbox/">
42 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />42 <img src="{{ MEDIA_URL }}img/undelete.png" alt="undelete" title="undelete" />
43 </a>43 </a>
44 {% endif %}44 {% endif %}
4545
=== removed file 'templates/mainpage/online_users.html'
--- templates/mainpage/online_users.html 2016-06-05 20:58:46 +0000
+++ templates/mainpage/online_users.html 1970-01-01 00:00:00 +0000
@@ -1,18 +0,0 @@
1{% load wlprofile_extras %}
2
3{% if users %}
4<div class="columnModule">
5 <h3>Currently Online</h3>
6 <div class="columnModuleBox">
7 {% if users %}
8 <ul class="player">
9 {% for user in users %}
10 <li><a href="{% url 'profile_view' user %}">{{user.username}}</a></li>
11 {% endfor %}
12 </ul>
13 {% else %}
14 <p>Currently nobody is online.</p>
15 {% endif %}
16 </div>
17</div>
18{% endif %}
190
=== modified file 'templates/notification/email_subject.txt'
--- templates/notification/email_subject.txt 2017-12-07 07:28:38 +0000
+++ templates/notification/email_subject.txt 2018-04-19 17:49:06 +0000
@@ -1,6 +1,2 @@
1{% load i18n %}1{% load i18n %}
2{% if message|length > 1 %}
3{% blocktrans %}{{ message }}{% endblocktrans %}2{% blocktrans %}{{ message }}{% endblocktrans %}
4{% else %}
5{% blocktrans %}{{ subject }}{% endblocktrans %}
6{% endif %}
73
=== modified file 'templates/notification/forum_new_post/full.txt'
--- templates/notification/forum_new_post/full.txt 2018-02-04 09:31:30 +0000
+++ templates/notification/forum_new_post/full.txt 2018-04-19 17:49:06 +0000
@@ -1,3 +1,4 @@
1{% autoescape off %}
1{% load i18n %}A new forum post was added to the topic "{{ topic }}":2{% load i18n %}A new forum post was added to the topic "{{ topic }}":
23
3"{{ user }}" wrote:4"{{ user }}" wrote:
@@ -7,4 +8,4 @@
7-------------------------8-------------------------
8Link to post: http://{{ current_site }}{{ post_url }}9Link to post: http://{{ current_site }}{{ post_url }}
9Link to topic: http://{{ current_site }}{{ topic_url }}10Link to topic: http://{{ current_site }}{{ topic_url }}
10{% endblocktrans %}
11\ No newline at end of file11\ No newline at end of file
12{% endblocktrans %}{% endautoescape %}
12\ No newline at end of file13\ No newline at end of file
1314
=== modified file 'templates/notification/forum_new_topic/full.txt'
--- templates/notification/forum_new_topic/full.txt 2018-03-18 10:48:37 +0000
+++ templates/notification/forum_new_topic/full.txt 2018-04-19 17:49:06 +0000
@@ -1,9 +1,9 @@
1{% autoescape off %}
1{% 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 }}.2{% 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 }}.
2
3{{ user }} wrote:3{{ user }} wrote:
44
5{{ txt }}5{{ txt }}
66
7-------------------------7-------------------------
8Link to topic: http://{{ current_site }}{{ topic_url }}8Link to topic: http://{{ current_site }}{{ topic_url }}
9{% endblocktrans %}9{% endblocktrans %}{% endautoescape %}
10\ No newline at end of file10\ No newline at end of file
1111
=== modified file 'templates/notification/maps_new_map/full.txt'
--- templates/notification/maps_new_map/full.txt 2018-02-04 09:31:30 +0000
+++ templates/notification/maps_new_map/full.txt 2018-04-19 17:49:06 +0000
@@ -1,3 +1,4 @@
1{% autoescape off %}
1{% load i18n %}A new map has been uploaded to the Website by {{ user }}:2{% load i18n %}A new map has been uploaded to the Website by {{ user }}:
2{% blocktrans %}3{% blocktrans %}
3Mapname: {{ mapname }}4Mapname: {{ mapname }}
@@ -5,4 +6,4 @@
56
6-------------------------7-------------------------
7Link to map: http://{{ current_site }}{{ url }}8Link to map: http://{{ current_site }}{{ url }}
8{% endblocktrans %}9{% endblocktrans %}{% endautoescape %}
9\ No newline at end of file10\ No newline at end of file
1011
=== modified file 'templates/notification/messages_received/full.txt'
--- templates/notification/messages_received/full.txt 2018-02-04 09:31:30 +0000
+++ templates/notification/messages_received/full.txt 2018-04-19 17:49:06 +0000
@@ -1,3 +1,4 @@
1{% autoescape off %}
1{% 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:2{% 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:
23
3{{ message }}4{{ message }}
@@ -6,4 +7,4 @@
67
7-------------------------8-------------------------
8Link to Message: http://{{ current_site }}{{ message_url }}9Link to Message: http://{{ current_site }}{{ message_url }}
9Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/{% endblocktrans %}10Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/{% endblocktrans %}{% endautoescape %}
10\ No newline at end of file11\ No newline at end of file
1112
=== modified file 'templates/notification/messages_reply_received/full.txt'
--- templates/notification/messages_reply_received/full.txt 2018-02-04 09:31:30 +0000
+++ templates/notification/messages_reply_received/full.txt 2018-04-19 17:49:06 +0000
@@ -1,3 +1,4 @@
1{% autoescape off %}
1{% 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 }}':2{% 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 }}':
23
3{{ message }}4{{ message }}
@@ -7,4 +8,4 @@
7-------------------------8-------------------------
8Link to message: http://{{ current_site }}{{ message_url }}9Link to message: http://{{ current_site }}{{ message_url }}
9Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/10Reply directly: http://{{ current_site }}/messages/reply/{{ message_id }}/
10{% endblocktrans %}11{% endblocktrans %}{% endautoescape %}
1112
=== modified file 'templates/notification/short.txt'
--- templates/notification/short.txt 2017-12-05 19:18:28 +0000
+++ templates/notification/short.txt 2018-04-19 17:49:06 +0000
@@ -1,1 +1,1 @@
1{% load i18n %}{% blocktrans %}{{ notice }}{% endblocktrans %}1{% load i18n %}{% blocktrans %}{{ subject }}{% endblocktrans %}
22
=== modified file 'templates/notification/wiki_observed_article_changed/full.txt'
--- templates/notification/wiki_observed_article_changed/full.txt 2018-02-04 12:31:56 +0000
+++ templates/notification/wiki_observed_article_changed/full.txt 2018-04-19 17:49:06 +0000
@@ -1,3 +1,4 @@
1{% autoescape off %}
1{% load i18n %}{% url 'wiki_changeset' article rev as diff_url %}{% url 'wiki_article' article as article_url %}{% blocktrans %}2{% load i18n %}{% url 'wiki_changeset' article rev as diff_url %}{% url 'wiki_article' article as article_url %}{% blocktrans %}
2The article "{{ article }}" that you observe has been edited by {{ editor }}.3The article "{{ article }}" that you observe has been edited by {{ editor }}.
3Comment for this revision: "{{ rev_comment }}"4Comment for this revision: "{{ rev_comment }}"
@@ -5,4 +6,4 @@
5-------------------------6-------------------------
6A diff is available at: http://{{ current_site }}{{ diff_url }}7A diff is available at: http://{{ current_site }}{{ diff_url }}
7Link to article: http://{{ current_site }}{{ article_url }}8Link to article: http://{{ current_site }}{{ article_url }}
8{% endblocktrans %}
9\ No newline at end of file9\ No newline at end of file
10{% endblocktrans %}{% endautoescape %}
10\ No newline at end of file11\ No newline at end of file
1112
=== modified file 'templates/right_boxes.html'
--- templates/right_boxes.html 2016-06-05 20:58:46 +0000
+++ templates/right_boxes.html 2018-04-19 17:49:06 +0000
@@ -10,7 +10,6 @@
10{% load i18n %}10{% load i18n %}
11{% load wlprofile_extras wlpoll_extras wlevents_extras %}11{% load wlprofile_extras wlpoll_extras wlevents_extras %}
12{% load pybb_extras %}12{% load pybb_extras %}
13{% load online_users %}
1413
15<!-- Donation Box -->14<!-- Donation Box -->
16<div class="columnModule">15<div class="columnModule">
@@ -109,7 +108,18 @@
109108
110109
111<!-- Logged in users -->110<!-- Logged in users -->
112{% online_users 10 %}111{% if request.online_now %}
112<div class="columnModule">
113 <h3>Currently Online</h3>
114 <div class="columnModuleBox">
115 <ul class="player">
116 {% for user in request.online_now|slice:":10" %}
117 <li><a href="{% url 'profile_view' user %}">{{ user.username }}</a></li>
118 {% endfor %}
119 </ul>
120 </div>
121</div>
122{% endif %}
113123
114<!-- Latest Post -->124<!-- Latest Post -->
115{% pybb_last_posts %}125{% pybb_last_posts %}
116126
=== removed file 'templates/wiki/article_teaser.html'
--- templates/wiki/article_teaser.html 2016-03-07 20:38:39 +0000
+++ templates/wiki/article_teaser.html 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
1
2{% load wiki_extras %}
3{% load avatar_tags %}
4{% load custom_date %}
5
6<tr class="{% cycle odd,even %}">
7 <td class="meta">
8 <div class="avatar">{% avatar article.latest_changeset.editor 40 %}</div>
9 <div class="details">
10 <a href="{% url 'profiles.views.profile' article.latest_changeset.editor.username %}">
11 {{ article.latest_changeset.editor }}
12 </a>
13 </div>
14 {{ article.last_update|custom_date:user }}
15 </td>
16 <td>
17 <h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
18 <div class="body">{% render_content article 'summary' %}</div>
19 </td>
20</tr>
210
=== modified file 'templates/wlmaps/index.html'
--- templates/wlmaps/index.html 2017-11-12 16:15:08 +0000
+++ templates/wlmaps/index.html 2018-04-19 17:49:06 +0000
@@ -22,7 +22,7 @@
22 <table class="maps">22 <table class="maps">
23 {% for map in object_list %}23 {% for map in object_list %}
24 <tr class="{% cycle "odd" "even" %}">24 <tr class="{% cycle "odd" "even" %}">
25 <td class="first-column"><a href="{{ map.get_absolute_url }}"><img class="minimap" src="{{ MEDIA_URL }}{{ map.minimap.url }}" alt="{{ map.name }}" /></a></td>25 <td class="first-column"><a href="{{ map.get_absolute_url }}"><img class="minimap" src="{{ MEDIA_URL }}{{ map.minimap }}" alt="{{ map.name }}" /></a></td>
26 <td>26 <td>
27 <h3><a class="invertedColor" href="{{ map.get_absolute_url }}">{{ map.name }}</a></h3>27 <h3><a class="invertedColor" href="{{ map.get_absolute_url }}">{{ map.name }}</a></h3>
28 <table>28 <table>
2929
=== modified file 'templates/wlmaps/map_detail.html'
--- templates/wlmaps/map_detail.html 2017-11-12 16:15:08 +0000
+++ templates/wlmaps/map_detail.html 2018-04-19 17:49:06 +0000
@@ -38,7 +38,7 @@
38 <div>38 <div>
39 <a href="{% url 'wlmaps_index' %}">Maps</a> &#187; {{ map.name }}39 <a href="{% url 'wlmaps_index' %}">Maps</a> &#187; {{ map.name }}
40 </div>40 </div>
41 <img class="posLeft map" style="float: left" src="{{ MEDIA_URL }}{{ map.minimap.url }}" alt="{{ map.name }}" />41 <img class="posLeft map" style="float: left" src="{{ MEDIA_URL }}{{ map.minimap }}" alt="{{ map.name }}" />
42 <div>42 <div>
43 <h3>Description:</h3>43 <h3>Description:</h3>
44 <p>{{ map.descr|wl_markdown:"bleachit" }}</p>44 <p>{{ map.descr|wl_markdown:"bleachit" }}</p>
4545
=== modified file 'threadedcomments/templatetags/gravatar.py'
--- threadedcomments/templatetags/gravatar.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/templatetags/gravatar.py 2018-04-19 17:49:06 +0000
@@ -3,7 +3,7 @@
3from django.template.defaultfilters import stringfilter3from django.template.defaultfilters import stringfilter
4from django.utils.encoding import smart_str4from django.utils.encoding import smart_str
5from django.utils.safestring import mark_safe5from django.utils.safestring import mark_safe
6from django.utils.hashcompat import md5_constructor6from hashlib import md5 as md5_constructor
7import urllib7import urllib
88
9GRAVATAR_MAX_RATING = getattr(settings, 'GRAVATAR_MAX_RATING', 'R')9GRAVATAR_MAX_RATING = getattr(settings, 'GRAVATAR_MAX_RATING', 'R')
1010
=== modified file 'threadedcomments/templatetags/threadedcommentstags.py'
--- threadedcomments/templatetags/threadedcommentstags.py 2017-01-24 11:53:11 +0000
+++ threadedcomments/templatetags/threadedcommentstags.py 2018-04-19 17:49:06 +0000
@@ -1,7 +1,7 @@
1import re1import re
2from django import template2from django import template
3from django.contrib.contenttypes.models import ContentType3from django.contrib.contenttypes.models import ContentType
4from django.core.urlresolvers import reverse4from django.urls import reverse
5from django.utils.encoding import force_unicode5from django.utils.encoding import force_unicode
6from django.utils.safestring import mark_safe6from django.utils.safestring import mark_safe
7from threadedcomments.models import ThreadedComment, FreeThreadedComment7from threadedcomments.models import ThreadedComment, FreeThreadedComment
88
=== removed directory 'threadedcomments/tests'
=== removed file 'threadedcomments/tests/__init__.py'
--- threadedcomments/tests/__init__.py 2016-05-15 14:41:54 +0000
+++ threadedcomments/tests/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1from views_tests import *
2from templatetags_tests import *
3try:
4 import comment_utils
5except ImportError:
6 pass
7else:
8 from moderator_tests import *
90
=== removed file 'threadedcomments/tests/moderator_tests.py'
--- threadedcomments/tests/moderator_tests.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/tests/moderator_tests.py 1970-01-01 00:00:00 +0000
@@ -1,403 +0,0 @@
1from django.core import mail
2from django.test import TestCase
3
4from django.contrib.auth.models import User
5
6from threadedcomments.moderation import moderator, CommentModerator
7from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
8from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
9
10
11__all__ = ('ModeratorTestCase',)
12
13
14class ModeratorTestCase(TestCase):
15
16 def test_threadedcomment(self):
17 topic = TestModel.objects.create(name='Test')
18 user = User.objects.create_user(
19 'user', 'floguy@gmail.com', password='password')
20 user2 = User.objects.create_user(
21 'user2', 'floguy@gmail.com', password='password')
22
23 comment1 = ThreadedComment.objects.create_for_object(
24 topic, user=user, ip_address='127.0.0.1',
25 comment='This is fun! This is very fun!',
26 )
27 comment2 = ThreadedComment.objects.create_for_object(
28 topic, user=user, ip_address='127.0.0.1',
29 comment='This is stupid! I hate it!',
30 )
31 comment3 = ThreadedComment.objects.create_for_object(
32 topic, user=user, ip_address='127.0.0.1', parent=comment2,
33 comment='I agree, the first comment was wrong and you are right!',
34 )
35 comment4 = ThreadedComment.objects.create_for_object(
36 topic, user=user, ip_address='127.0.0.1',
37 comment='What are we talking about?',
38 )
39 comment5 = ThreadedComment.objects.create_for_object(
40 topic, user=user, ip_address='127.0.0.1', parent=comment3,
41 comment="I'm a fanboy!",
42 )
43 comment6 = ThreadedComment.objects.create_for_object(
44 topic, user=user, ip_address='127.0.0.1', parent=comment1,
45 comment='What are you talking about?',
46 )
47
48 class Moderator1(CommentModerator):
49 enable_field = 'is_public'
50 auto_close_field = 'date'
51 close_after = 15
52 moderator.register(TestModel, Moderator1)
53
54 comment7 = ThreadedComment.objects.create_for_object(
55 topic, user=user, ip_address='127.0.0.1',
56 comment='Post moderator addition. Does it still work?',
57 )
58
59 topic.is_public = False
60 topic.save()
61
62 comment8 = ThreadedComment.objects.create_for_object(
63 topic, user=user, ip_address='127.0.0.1', parent=comment7,
64 comment='This should not appear, due to enable_field',
65 )
66
67 moderator.unregister(TestModel)
68
69 comment9 = ThreadedComment.objects.create_for_object(
70 topic, user=user, ip_address='127.0.0.1',
71 comment='This should appear again, due to unregistration',
72 )
73
74 self.assertEquals(len(mail.outbox), 0)
75
76 ##################
77
78 class Moderator2(CommentModerator):
79 enable_field = 'is_public'
80 auto_close_field = 'date'
81 close_after = 15
82 akismet = False
83 email_notification = True
84 moderator.register(TestModel, Moderator2)
85
86 comment10 = ThreadedComment.objects.create_for_object(
87 topic, user=user, ip_address='127.0.0.1',
88 comment='This should not appear again, due to registration with a new manager.',
89 )
90
91 topic.is_public = True
92 topic.save()
93
94 comment11 = ThreadedComment.objects.create_for_object(
95 topic, user=user, ip_address='127.0.0.1', parent=comment1,
96 comment='This should appear again.',
97 )
98
99 self.assertEquals(len(mail.outbox), 1)
100 mail.outbox = []
101
102 topic.date = topic.date - datetime.timedelta(days=20)
103 topic.save()
104
105 comment12 = ThreadedComment.objects.create_for_object(
106 topic, user=user, ip_address='127.0.0.1', parent=comment7,
107 comment="This shouldn't appear, due to close_after=15.",
108 )
109
110 topic.date = topic.date + datetime.timedelta(days=20)
111 topic.save()
112
113 moderator.unregister(TestModel)
114
115 class Moderator3(CommentModerator):
116 max_comment_length = 10
117 moderator.register(TestModel, Moderator3)
118
119 comment13 = ThreadedComment.objects.create_for_object(
120 topic, user=user, ip_address='127.0.0.1', parent=comment7,
121 comment="This shouldn't appear because it has more than 10 chars.",
122 )
123
124 comment14 = ThreadedComment.objects.create_for_object(
125 topic, user=user, ip_address='127.0.0.1', parent=comment7,
126 comment='<10chars',
127 )
128
129 moderator.unregister(TestModel)
130
131 class Moderator4(CommentModerator):
132 allowed_markup = [REST, ]
133 moderator.register(TestModel, Moderator4)
134
135 comment15 = ThreadedComment.objects.create_for_object(
136 topic, user=user, ip_address='127.0.0.1', parent=comment7,
137 comment='INVALID Markup. Should not show up.', markup=TEXTILE
138 )
139
140 comment16 = ThreadedComment.objects.create_for_object(
141 topic, user=user, ip_address='127.0.0.1', parent=comment7,
142 comment='VALID Markup. Should show up.', markup=REST
143 )
144
145 moderator.unregister(TestModel)
146
147 tree = ThreadedComment.public.get_tree(topic)
148 output = []
149 for comment in tree:
150 output.append('%s %s' % (' ' * comment.depth, comment.comment))
151 self.assertEquals('\n'.join(output),
152 """
153This is fun! This is very fun!
154 What are you talking about?
155 This should appear again.
156This is stupid! I hate it!
157 I agree, the first comment was wrong and you are right!
158 I'm a fanboy!
159What are we talking about?
160Post moderator addition. Does it still work?
161 <10chars
162 VALID Markup. Should show up.
163This should appear again, due to unregistration
164""".lstrip())
165
166 tree = ThreadedComment.objects.get_tree(topic)
167 output = []
168 for comment in tree:
169 output.append('%s %s' % (' ' * comment.depth, comment.comment))
170 self.assertEquals('\n'.join(output),
171 """
172This is fun! This is very fun!
173 What are you talking about?
174 This should appear again.
175This is stupid! I hate it!
176 I agree, the first comment was wrong and you are right!
177 I'm a fanboy!
178What are we talking about?
179Post moderator addition. Does it still work?
180 This shouldn't appear because it has more than 10 chars.
181 <10chars
182 VALID Markup. Should show up.
183This should appear again, due to unregistration
184""".lstrip())
185
186 tree = ThreadedComment.objects.get_tree(topic, root=comment2)
187 output = []
188 for comment in tree:
189 output.append('%s %s' % (' ' * comment.depth, comment.comment))
190 self.assertEquals('\n'.join(output),
191 """
192This is stupid! I hate it!
193 I agree, the first comment was wrong and you are right!
194 I'm a fanboy!
195""".lstrip())
196
197 tree = ThreadedComment.objects.get_tree(topic, root=comment2.id)
198 for comment in tree:
199 output.append('%s %s' % (' ' * comment.depth, comment.comment))
200 self.assertEquals('\n'.join(output),
201 """
202This is stupid! I hate it!
203 I agree, the first comment was wrong and you are right!
204 I'm a fanboy!
205""".lstrip())
206
207 def test_freethreadedcomment(self):
208
209 ###########################
210 ### FreeThreadedComment ###
211 ###########################
212
213 fcomment1 = FreeThreadedComment.objects.create_for_object(
214 topic, name='Eric', ip_address='127.0.0.1',
215 comment='This is fun! This is very fun!',
216 )
217 fcomment2 = FreeThreadedComment.objects.create_for_object(
218 topic, name='Eric', ip_address='127.0.0.1',
219 comment='This is stupid! I hate it!',
220 )
221 fcomment3 = FreeThreadedComment.objects.create_for_object(
222 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment2,
223 comment='I agree, the first comment was wrong and you are right!',
224 )
225 fcomment4 = FreeThreadedComment.objects.create_for_object(
226 topic, name='Eric', ip_address='127.0.0.1',
227 website='http://www.eflorenzano.com/', email='floguy@gmail.com',
228 comment='What are we talking about?',
229 )
230 fcomment5 = FreeThreadedComment.objects.create_for_object(
231 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment3,
232 comment="I'm a fanboy!",
233 )
234 fcomment6 = FreeThreadedComment.objects.create_for_object(
235 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment1,
236 comment='What are you talking about?',
237 )
238
239 moderator.register(TestModel, Moderator1)
240
241 fcomment7 = FreeThreadedComment.objects.create_for_object(
242 topic, name='Eric', ip_address='127.0.0.1',
243 comment='Post moderator addition. Does it still work?',
244 )
245
246 topic.is_public = False
247 topic.save()
248
249 fcomment8 = FreeThreadedComment.objects.create_for_object(
250 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
251 comment='This should not appear, due to enable_field',
252 )
253
254 moderator.unregister(TestModel)
255
256 fcomment9 = FreeThreadedComment.objects.create_for_object(
257 topic, name='Eric', ip_address='127.0.0.1',
258 comment='This should appear again, due to unregistration',
259 )
260
261 self.assertEquals(len(mail.outbox), 0)
262
263 moderator.register(TestModel, Moderator2)
264
265 fcomment10 = FreeThreadedComment.objects.create_for_object(
266 topic, name='Eric', ip_address='127.0.0.1',
267 comment='This should not appear again, due to registration with a new manager.',
268 )
269
270 topic.is_public = True
271 topic.save()
272
273 fcomment11 = FreeThreadedComment.objects.create_for_object(
274 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment1,
275 comment='This should appear again.',
276 )
277
278 self.assertEquals(len(mail.outbox), 1)
279
280 mail.outbox = []
281
282 topic.date = topic.date - datetime.timedelta(days=20)
283 topic.save()
284
285 fcomment12 = FreeThreadedComment.objects.create_for_object(
286 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
287 comment="This shouldn't appear, due to close_after=15.",
288 )
289
290 topic.date = topic.date + datetime.timedelta(days=20)
291 topic.save()
292
293 moderator.unregister(TestModel)
294 moderator.register(TestModel, Moderator3)
295
296 fcomment13 = FreeThreadedComment.objects.create_for_object(
297 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
298 comment="This shouldn't appear because it has more than 10 chars.",
299 )
300
301 fcomment14 = FreeThreadedComment.objects.create_for_object(
302 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
303 comment='<10chars',
304 )
305
306 moderator.unregister(TestModel)
307
308 class Moderator5(CommentModerator):
309 allowed_markup = [REST, ]
310 max_depth = 3
311 moderator.register(TestModel, Moderator5)
312
313 fcomment15 = FreeThreadedComment.objects.create_for_object(
314 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment7,
315 comment='INVALID Markup. Should not show up.', markup=TEXTILE
316 )
317
318 fcomment16 = FreeThreadedComment.objects.create_for_object(
319 topic, name='Eric', ip_address='127.0.0.1', parent=None,
320 comment='VALID Markup. Should show up.', markup=REST
321 )
322
323 fcomment17 = FreeThreadedComment.objects.create_for_object(
324 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment16,
325 comment='Building Depth...Should Show Up.', markup=REST
326 )
327
328 fcomment18 = FreeThreadedComment.objects.create_for_object(
329 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment17,
330 comment='More Depth...Should Show Up.', markup=REST
331 )
332
333 fcomment19 = FreeThreadedComment.objects.create_for_object(
334 topic, name='Eric', ip_address='127.0.0.1', parent=fcomment18,
335 comment='Too Deep..Should NOT Show UP', markup=REST
336 )
337
338 moderator.unregister(TestModel)
339
340 tree = FreeThreadedComment.public.get_tree(topic)
341 output = []
342 for comment in tree:
343 output.append('%s %s' % (' ' * comment.depth, comment.comment))
344 self.assertEquals('\n'.join(output),
345 """
346This is fun! This is very fun!
347 What are you talking about?
348 This should appear again.
349This is stupid! I hate it!
350 I agree, the first comment was wrong and you are right!
351 I'm a fanboy!
352What are we talking about?
353Post moderator addition. Does it still work?
354 <10chars
355This should appear again, due to unregistration
356VALID Markup. Should show up.
357 Building Depth...Should Show Up.
358 More Depth...Should Show Up.
359""".lstrip())
360
361 tree = FreeThreadedComment.objects.get_tree(topic)
362 output = []
363 for comment in tree:
364 output.append('%s %s' % (' ' * comment.depth, comment.comment))
365 self.assertEquals('\n'.join(output),
366 """
367This is fun! This is very fun!
368 What are you talking about?
369 This should appear again.
370This is stupid! I hate it!
371 I agree, the first comment was wrong and you are right!
372 I'm a fanboy!
373What are we talking about?
374Post moderator addition. Does it still work?
375 This shouldn't appear because it has more than 10 chars.
376 <10chars
377This should appear again, due to unregistration
378VALID Markup. Should show up.
379 Building Depth...Should Show Up.
380 More Depth...Should Show Up.
381""".lstrip())
382
383 tree = FreeThreadedComment.objects.get_tree(topic, root=comment2)
384 output = []
385 for comment in tree:
386 output.append('%s %s' % (' ' * comment.depth, comment.comment))
387 self.assertEquals('\n'.join(output),
388 """
389This is stupid! I hate it!
390 I agree, the first comment was wrong and you are right!
391 I'm a fanboy!
392""".lstrip())
393
394 tree = FreeThreadedComment.objects.get_tree(topic, root=comment2.id)
395 output = []
396 for comment in tree:
397 output.append('%s %s' % (' ' * comment.depth, comment.comment))
398 self.assertEquals('\n'.join(output),
399 """
400This is stupid! I hate it!
401 I agree, the first comment was wrong and you are right!
402 I'm a fanboy!
403""".lstrip())
4040
=== removed file 'threadedcomments/tests/templatetags_tests.py'
--- threadedcomments/tests/templatetags_tests.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/tests/templatetags_tests.py 1970-01-01 00:00:00 +0000
@@ -1,561 +0,0 @@
1import datetime
2
3from xml.dom.minidom import parseString
4
5from django.core import mail
6from django.core.urlresolvers import reverse
7from django.template import Context, Template
8from django.test import TestCase
9from django.utils.simplejson import loads
10
11from django.contrib.auth.models import User
12from django.contrib.contenttypes.models import ContentType
13
14from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
15from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
16from threadedcomments.templatetags import threadedcommentstags as tags
17
18
19__all__ = ('TemplateTagTestCase',)
20
21
22class TemplateTagTestCase(TestCase):
23 urls = 'threadedcomments.tests.threadedcomments_urls'
24
25 def test_get_comment_url(self):
26
27 user = User.objects.create_user(
28 'user', 'floguy@gmail.com', password='password')
29
30 topic = TestModel.objects.create(name='Test2')
31 content_type = ContentType.objects.get_for_model(topic)
32
33 comment = ThreadedComment.objects.create_for_object(topic,
34 user=user,
35 ip_address='127.0.0.1',
36 comment='My test comment!',
37 )
38
39 c = Context({
40 'topic': topic,
41 'parent': comment
42 })
43 sc = {
44 'ct': content_type.pk,
45 'id': topic.pk,
46 'pid': comment.pk,
47 }
48
49 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url topic %}').render(
50 c), u'/comment/%(ct)s/%(id)s/' % sc)
51 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url topic parent %}').render(
52 c), u'/comment/%(ct)s/%(id)s/%(pid)s/' % sc)
53 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_json topic %}').render(
54 c), u'/comment/%(ct)s/%(id)s/json/' % sc)
55 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_xml topic %}').render(
56 c), u'/comment/%(ct)s/%(id)s/xml/' % sc)
57 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_json topic parent %}').render(
58 c), u'/comment/%(ct)s/%(id)s/%(pid)s/json/' % sc)
59 self.assertEquals(Template('{% load threadedcommentstags %}{% get_comment_url_xml topic parent %}').render(
60 c), u'/comment/%(ct)s/%(id)s/%(pid)s/xml/' % sc)
61
62 def test_get_free_comment_url(self):
63
64 topic = TestModel.objects.create(name='Test2')
65 content_type = ContentType.objects.get_for_model(topic)
66
67 comment = FreeThreadedComment.objects.create_for_object(topic,
68 ip_address='127.0.0.1',
69 comment='My test free comment!',
70 )
71
72 c = Context({
73 'topic': topic,
74 'parent': comment,
75 })
76 sc = {
77 'ct': content_type.pk,
78 'id': topic.pk,
79 'pid': comment.pk,
80 }
81
82 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url topic %}').render(
83 c), u'/freecomment/%(ct)s/%(id)s/' % sc)
84 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url topic parent %}').render(
85 c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/' % sc)
86 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_json topic %}').render(
87 c), u'/freecomment/%(ct)s/%(id)s/json/' % sc)
88 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_xml topic %}').render(
89 c), u'/freecomment/%(ct)s/%(id)s/xml/' % sc)
90 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_json topic parent %}').render(
91 c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/json/' % sc)
92 self.assertEquals(Template('{% load threadedcommentstags %}{% get_free_comment_url_xml topic parent %}').render(
93 c), u'/freecomment/%(ct)s/%(id)s/%(pid)s/xml/' % sc)
94
95 def test_get_comment_count(self):
96
97 user = User.objects.create_user(
98 'user', 'floguy@gmail.com', password='password')
99
100 topic = TestModel.objects.create(name='Test2')
101
102 comment = ThreadedComment.objects.create_for_object(topic,
103 user=user,
104 ip_address='127.0.0.1',
105 comment='My test comment!',
106 )
107
108 c = Context({
109 'topic': topic,
110 })
111
112 self.assertEquals(
113 Template(
114 '{% load threadedcommentstags %}{% get_comment_count for topic as count %}{{ count }}').render(c),
115 u'1'
116 )
117
118 def test_get_free_comment_count(self):
119
120 topic = TestModel.objects.create(name='Test2')
121
122 comment = FreeThreadedComment.objects.create_for_object(topic,
123 ip_address='127.0.0.1',
124 comment='My test free comment!',
125 )
126
127 c = Context({
128 'topic': topic,
129 })
130
131 self.assertEquals(
132 Template(
133 '{% load threadedcommentstags %}{% get_free_comment_count for topic as count %}{{ count }}').render(c),
134 u'1'
135 )
136
137 def test_get_threaded_comment_form(self):
138 self.assertEquals(
139 Template('{% load threadedcommentstags %}{% get_threaded_comment_form as form %}{{ form }}').render(
140 Context({})),
141 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>'
142 )
143
144 def test_get_latest_comments(self):
145
146 user = User.objects.create_user(
147 'user', 'floguy@gmail.com', password='password')
148
149 topic = TestModel.objects.create(name='Test2')
150 old_topic = topic
151 content_type = ContentType.objects.get_for_model(topic)
152
153 ThreadedComment.objects.create_for_object(topic,
154 user=user,
155 ip_address='127.0.0.1',
156 comment='Test 1',
157 )
158 ThreadedComment.objects.create_for_object(topic,
159 user=user,
160 ip_address='127.0.0.1',
161 comment='Test 2',
162 )
163 ThreadedComment.objects.create_for_object(topic,
164 user=user,
165 ip_address='127.0.0.1',
166 comment='Test 3',
167 )
168
169 self.assertEquals(
170 Template('{% load threadedcommentstags %}{% get_latest_comments 2 as comments %}{{ comments }}').render(
171 Context({})),
172 u'[&lt;ThreadedComment: Test 3&gt;, &lt;ThreadedComment: Test 2&gt;]'
173 )
174
175 def test_get_latest_free_comments(self):
176
177 topic = TestModel.objects.create(name='Test2')
178
179 FreeThreadedComment.objects.create_for_object(topic,
180 ip_address='127.0.0.1',
181 comment='Test 1',
182 )
183 FreeThreadedComment.objects.create_for_object(topic,
184 ip_address='127.0.0.1',
185 comment='Test 2',
186 )
187 FreeThreadedComment.objects.create_for_object(topic,
188 ip_address='127.0.0.1',
189 comment='Test 3',
190 )
191
192 self.assertEquals(
193 Template('{% load threadedcommentstags %}{% get_latest_free_comments 2 as comments %}{{ comments }}').render(
194 Context({})),
195 u'[&lt;FreeThreadedComment: Test 3&gt;, &lt;FreeThreadedComment: Test 2&gt;]'
196 )
197
198 def test_get_threaded_comment_tree(self):
199
200 user = User.objects.create_user(
201 'user', 'floguy@gmail.com', password='password')
202
203 topic = TestModel.objects.create(name='Test2')
204
205 parent1 = ThreadedComment.objects.create_for_object(topic,
206 user=user,
207 ip_address='127.0.0.1',
208 comment='test1',
209 )
210 ThreadedComment.objects.create_for_object(topic,
211 user=user,
212 ip_address='127.0.0.1',
213 comment='test2',
214 parent=parent1,
215 )
216 parent2 = ThreadedComment.objects.create_for_object(topic,
217 user=user,
218 ip_address='127.0.0.1',
219 comment='test3',
220 )
221 ThreadedComment.objects.create_for_object(topic,
222 user=user,
223 ip_address='127.0.0.1',
224 comment='test4',
225 parent=parent2,
226 )
227
228 c = Context({
229 'topic': topic,
230 })
231
232 self.assertEquals(
233 Template(
234 '{% load threadedcommentstags %}{% get_threaded_comment_tree for topic as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
235 u'[(0)test1,(1)test2,(0)test3,(1)test4,]'
236 )
237 self.assertEquals(
238 Template(
239 '{% load threadedcommentstags %}{% get_threaded_comment_tree for topic 3 as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
240 u'[(0)test3,(1)test4,]'
241 )
242
243 def test_get_free_threaded_comment_tree(self):
244
245 topic = TestModel.objects.create(name='Test2')
246
247 parent1 = FreeThreadedComment.objects.create_for_object(topic,
248 ip_address='127.0.0.1',
249 comment='test1',
250 )
251 FreeThreadedComment.objects.create_for_object(topic,
252 ip_address='127.0.0.1',
253 comment='test2',
254 parent=parent1,
255 )
256 parent2 = FreeThreadedComment.objects.create_for_object(topic,
257 ip_address='127.0.0.1',
258 comment='test3',
259 )
260 FreeThreadedComment.objects.create_for_object(topic,
261 ip_address='127.0.0.1',
262 comment='test4',
263 parent=parent2,
264 )
265
266 c = Context({
267 'topic': topic,
268 })
269
270 self.assertEquals(
271 Template(
272 '{% load threadedcommentstags %}{% get_free_threaded_comment_tree for topic as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
273 u'[(0)test1,(1)test2,(0)test3,(1)test4,]'
274 )
275 self.assertEquals(
276 Template(
277 '{% load threadedcommentstags %}{% get_free_threaded_comment_tree for topic 3 as tree %}[{% for item in tree %}({{ item.depth }}){{ item.comment }},{% endfor %}]').render(c),
278 u'[(0)test3,(1)test4,]'
279 )
280
281 def test_user_comment_tags(self):
282
283 user1 = User.objects.create_user(
284 'eric', 'floguy@gmail.com', password='password')
285 user2 = User.objects.create_user(
286 'brian', 'brosner@gmail.com', password='password')
287
288 topic = TestModel.objects.create(name='Test2')
289
290 ThreadedComment.objects.create_for_object(topic,
291 user=user1,
292 ip_address='127.0.0.1',
293 comment='Eric comment',
294 )
295 ThreadedComment.objects.create_for_object(topic,
296 user=user2,
297 ip_address='127.0.0.1',
298 comment='Brian comment',
299 )
300
301 c = Context({
302 'user': user1,
303 })
304
305 self.assertEquals(
306 Template(
307 '{% load threadedcommentstags %}{% get_user_comments for user as comments %}{{ comments }}').render(c),
308 u'[&lt;ThreadedComment: Eric comment&gt;]'
309 )
310 self.assertEquals(
311 Template(
312 '{% load threadedcommentstags %}{% get_user_comment_count for user as comment_count %}{{ comment_count }}').render(c),
313 u'1',
314 )
315
316 def test_markdown_comment(self):
317
318 user = User.objects.create_user(
319 'user', 'floguy@gmail.com', password='password')
320 topic = TestModel.objects.create(name='Test2')
321
322 markdown_txt = '''
323A First Level Header
324====================
325
326A Second Level Header
327---------------------
328
329Now is the time for all good men to come to
330the aid of their country. This is just a
331regular paragraph.
332
333The quick brown fox jumped over the lazy
334dog's back.
335
336### Header 3
337
338> This is a blockquote.
339>
340> This is the second paragraph in the blockquote.
341>
342> ## This is an H2 in a blockquote
343'''
344
345 comment_markdown = ThreadedComment.objects.create_for_object(
346 topic, user=user, ip_address='127.0.0.1', markup=MARKDOWN,
347 comment=markdown_txt,
348 )
349
350 c = Context({
351 'comment': comment_markdown,
352 })
353 s = Template('{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(
354 c).replace('\\n', '')
355 self.assertEquals(s.startswith(u"<h1>"), True)
356
357 def test_textile_comment(self):
358
359 user = User.objects.create_user(
360 'user', 'floguy@gmail.com', password='password')
361 topic = TestModel.objects.create(name='Test2')
362
363 textile_txt = '''
364h2{color:green}. This is a title
365
366h3. This is a subhead
367
368p{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.
369
370bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised.
371
372Simple list:
373
374#{color:blue} one
375# two
376# three
377
378Multi-level list:
379
380# one
381## aye
382## bee
383## see
384# two
385## x
386## y
387# three
388
389Mixed list:
390
391* Point one
392* Point two
393## Step 1
394## Step 2
395## Step 3
396* Point three
397** Sub point 1
398** Sub point 2
399
400
401Well, 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!
402
403"This is a link (optional title)":http://www.textism.com
404
405table{border:1px solid black}.
406|_. this|_. is|_. a|_. header|
407<{background:gray}. |\2. this is|{background:red;width:200px}. a|^<>{height:200px}. row|
408|this|<>{padding:10px}. is|^. another|(bob#bob). row|
409
410An image:
411
412!/common/textist.gif(optional alt text)!
413
414# Librarians rule
415# Yes they do
416# But you knew that
417
418Some more text of dubious character. Here is a noisome string of CAPITAL letters. Here is ... something we want to _emphasize_.
419That 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.
420
421h3. Coding
422
423This <code>is some code, "isn't it"</code>. Watch those quote marks! Now for some preformatted text:
424
425<pre>
426<code>
427 $text = str_replace("<p>%::%</p>","",$text);
428 $text = str_replace("%::%</p>","",$text);
429 $text = str_replace("%::%","",$text);
430
431</code>
432</pre>
433
434This isn't code.
435
436
437So you see, my friends:
438
439* The time is now
440* The time is not later
441* The time is not yesterday
442* We must act
443'''
444
445 comment_textile = ThreadedComment.objects.create_for_object(
446 topic, user=user, ip_address='127.0.0.1', markup=TEXTILE,
447 comment=textile_txt,
448 )
449 c = Context({
450 'comment': comment_textile
451 })
452 s = Template(
453 '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c)
454 self.assertEquals('<h3>' in s, True)
455
456 def test_rest_comment(self):
457
458 user = User.objects.create_user(
459 'user', 'floguy@gmail.com', password='password')
460 topic = TestModel.objects.create(name='Test2')
461
462 rest_txt = '''
463FooBar Header
464=============
465reStructuredText is **nice**. It has its own webpage_.
466
467A table:
468
469===== ===== ======
470 Inputs Output
471------------ ------
472 A B A or B
473===== ===== ======
474False False False
475True False True
476False True True
477True True True
478===== ===== ======
479
480RST TracLinks
481-------------
482
483See also ticket `#42`::.
484
485.. _webpage: http://docutils.sourceforge.net/rst.html
486'''
487
488 comment_rest = ThreadedComment.objects.create_for_object(
489 topic, user=user, ip_address='127.0.0.1', markup=REST,
490 comment=rest_txt,
491 )
492 c = Context({
493 'comment': comment_rest
494 })
495 s = Template(
496 '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c)
497 self.assertEquals(s.startswith('<p>reStructuredText is'), True)
498
499 def test_plaintext_comment(self):
500
501 user = User.objects.create_user(
502 'user', 'floguy@gmail.com', password='password')
503 topic = TestModel.objects.create(name='Test2')
504
505 comment_plaintext = ThreadedComment.objects.create_for_object(
506 topic, user=user, ip_address='127.0.0.1', markup=PLAINTEXT,
507 comment='<b>This is Funny</b>',
508 )
509 c = Context({
510 'comment': comment_plaintext
511 })
512 self.assertEquals(
513 Template(
514 '{% load threadedcommentstags %}{% auto_transform_markup comment %}').render(c),
515 u'&lt;b&gt;This is Funny&lt;/b&gt;'
516 )
517
518 comment_plaintext = ThreadedComment.objects.create_for_object(
519 topic, user=user, ip_address='127.0.0.1', markup=PLAINTEXT,
520 comment='<b>This is Funny</b>',
521 )
522 c = Context({
523 'comment': comment_plaintext
524 })
525 self.assertEquals(
526 Template(
527 '{% load threadedcommentstags %}{% auto_transform_markup comment as abc %}{{ abc }}').render(c),
528 u'&lt;b&gt;This is Funny&lt;/b&gt;'
529 )
530
531 def test_gravatar_tags(self):
532 c = Context({
533 'email': 'floguy@gmail.com',
534 'rating': 'G',
535 'size': 30,
536 'default': 'overridectx',
537 })
538 self.assertEquals(
539 Template(
540 '{% load gravatar %}{% get_gravatar_url for email %}').render(c),
541 u'http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
542 )
543 self.assertEquals(
544 Template(
545 '{% load gravatar %}{% get_gravatar_url for email as var %}Var: {{ var }}').render(c),
546 u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
547 )
548 self.assertEquals(
549 Template(
550 '{% load gravatar %}{% get_gravatar_url for email size 30 rating "G" default override as var %}Var: {{ var }}').render(c),
551 u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=G&size=30&default=override'
552 )
553 self.assertEquals(
554 Template(
555 '{% load gravatar %}{% get_gravatar_url for email size size rating rating default default as var %}Var: {{ var }}').render(c),
556 u'Var: http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=G&size=30&default=overridectx'
557 )
558 self.assertEquals(
559 Template('{% load gravatar %}{{ email|gravatar }}').render(c),
560 u'http://www.gravatar.com/avatar.php?gravatar_id=04d6b8e8d3c68899ac88eb8623392150&rating=R&size=80&default=img%3Ablank'
561 )
5620
=== removed file 'threadedcomments/tests/threadedcomments_urls.py'
--- threadedcomments/tests/threadedcomments_urls.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/tests/threadedcomments_urls.py 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1from django.conf.urls.defaults import *
2
3
4urlpatterns = patterns('',
5 url(r"", include('threadedcomments.urls')),
6 )
70
=== removed file 'threadedcomments/tests/views_tests.py'
--- threadedcomments/tests/views_tests.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/tests/views_tests.py 1970-01-01 00:00:00 +0000
@@ -1,856 +0,0 @@
1import datetime
2
3from xml.dom.minidom import parseString
4
5from django.core import mail
6from django.core.urlresolvers import reverse
7from django.template import Context, Template
8from django.test import TestCase
9from django.utils.simplejson import loads
10
11from django.contrib.auth.models import User
12from django.contrib.contenttypes.models import ContentType
13
14from threadedcomments.models import FreeThreadedComment, ThreadedComment, TestModel
15from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
16from threadedcomments.templatetags import threadedcommentstags as tags
17
18
19__all__ = ('ViewsTestCase',)
20
21
22class ViewsTestCase(TestCase):
23 urls = 'threadedcomments.tests.threadedcomments_urls'
24
25 def test_freecomment_create(self):
26
27 topic = TestModel.objects.create(name='Test2')
28 content_type = ContentType.objects.get_for_model(topic)
29
30 url = reverse('tc_free_comment', kwargs={
31 'content_type': content_type.id,
32 'object_id': topic.id
33 })
34 response = self.client.post(url, {
35 'comment': 'test1',
36 'name': 'eric',
37 'website': 'http://www.eflorenzano.com/',
38 'email': 'floguy@gmail.com',
39 'next': '/'
40 })
41 o = FreeThreadedComment.objects.latest(
42 'date_submitted').get_base_data(show_dates=False)
43 self.assertEquals(o, {
44 'website': u'http://www.eflorenzano.com/',
45 'comment': u'test1',
46 'name': u'eric',
47 'parent': None,
48 'markup': u'plaintext',
49 'content_object': topic,
50 'is_public': True,
51 'ip_address': u'127.0.0.1',
52 'email': u'floguy@gmail.com',
53 'is_approved': False
54 })
55
56 def test_freecomment_preview(self):
57
58 topic = TestModel.objects.create(name='Test2')
59 content_type = ContentType.objects.get_for_model(topic)
60
61 url = reverse('tc_free_comment', kwargs={
62 'content_type': content_type.id,
63 'object_id': topic.id
64 })
65
66 response = self.client.post(url, {
67 'comment': 'test1',
68 'name': 'eric',
69 'website': 'http://www.eflorenzano.com/',
70 'email': 'floguy@gmail.com',
71 'next': '/',
72 'preview': 'True'
73 })
74 self.assertEquals(len(response.content) > 0, True)
75
76 def test_freecomment_edit(self):
77
78 topic = TestModel.objects.create(name='Test2')
79
80 comment = FreeThreadedComment.objects.create_for_object(topic,
81 ip_address='127.0.0.1',
82 comment='My test free comment!',
83 )
84
85 url = reverse('tc_free_comment_edit', kwargs={
86 'edit_id': comment.pk
87 })
88
89 response = self.client.post(url, {
90 'comment': 'test1_edited',
91 'name': 'eric',
92 'website': 'http://www.eflorenzano.com/',
93 'email': 'floguy@gmail.com',
94 'next': '/'
95 })
96 o = FreeThreadedComment.objects.latest(
97 'date_submitted').get_base_data(show_dates=False)
98 self.assertEquals(o, {
99 'website': u'http://www.eflorenzano.com/',
100 'comment': u'test1_edited',
101 'name': u'eric',
102 'parent': None,
103 'markup': u'plaintext',
104 'content_object': topic,
105 'is_public': True,
106 'ip_address': u'127.0.0.1',
107 'email': u'floguy@gmail.com',
108 'is_approved': False
109 })
110
111 def test_freecomment_edit_with_preview(self):
112
113 topic = TestModel.objects.create(name='Test2')
114
115 comment = FreeThreadedComment.objects.create_for_object(topic,
116 website='http://oebfare.com/',
117 comment='My test free comment!',
118 ip_address='127.0.0.1',
119 )
120
121 url = reverse('tc_free_comment_edit', kwargs={
122 'edit_id': comment.pk
123 })
124
125 response = self.client.post(url, {
126 'comment': 'test1_edited',
127 'name': 'eric',
128 'website': 'http://www.eflorenzano.com/',
129 'email': 'floguy@gmail.com',
130 'next': '/',
131 'preview': 'True'
132 })
133 o = FreeThreadedComment.objects.latest(
134 'date_submitted').get_base_data(show_dates=False)
135 self.assertEquals(o, {
136 'website': u'http://oebfare.com/',
137 'comment': u'My test free comment!',
138 'name': u'',
139 'parent': None,
140 'markup': u'plaintext',
141 'content_object': topic,
142 'is_public': True,
143 'ip_address': u'127.0.0.1',
144 'email': u'',
145 'is_approved': False
146 })
147 self.assertEquals(len(response.content) > 0, True)
148
149 def test_freecomment_json_create(self):
150
151 topic = TestModel.objects.create(name='Test2')
152 content_type = ContentType.objects.get_for_model(topic)
153
154 url = reverse('tc_free_comment_ajax', kwargs={
155 'content_type': content_type.id,
156 'object_id': topic.id,
157 'ajax': 'json'
158 })
159
160 response = self.client.post(url, {
161 'comment': 'test2',
162 'name': 'eric',
163 'website': 'http://www.eflorenzano.com/',
164 'email': 'floguy@gmail.com'
165 })
166 tmp = loads(response.content)
167 o = FreeThreadedComment.objects.latest(
168 'date_submitted').get_base_data(show_dates=False)
169 self.assertEquals(o, {
170 'website': u'http://www.eflorenzano.com/',
171 'comment': u'test2',
172 'name': u'eric',
173 'parent': None,
174 'markup': u'plaintext',
175 'content_object': topic,
176 'is_public': True,
177 'ip_address': u'127.0.0.1',
178 'email': u'floguy@gmail.com',
179 'is_approved': False
180 })
181
182 def test_freecomment_json_edit(self):
183
184 topic = TestModel.objects.create(name='Test2')
185
186 comment = FreeThreadedComment.objects.create_for_object(topic,
187 ip_address='127.0.0.1',
188 comment='My test free comment!',
189 )
190
191 url = reverse('tc_free_comment_edit_ajax', kwargs={
192 'edit_id': comment.pk,
193 'ajax': 'json'
194 })
195
196 response = self.client.post(url, {
197 'comment': 'test2_edited',
198 'name': 'eric',
199 'website': 'http://www.eflorenzano.com/',
200 'email': 'floguy@gmail.com'
201 })
202 tmp = loads(response.content)
203 o = FreeThreadedComment.objects.latest(
204 'date_submitted').get_base_data(show_dates=False)
205 self.assertEquals(o, {
206 'website': u'http://www.eflorenzano.com/',
207 'comment': u'test2_edited',
208 'name': u'eric',
209 'parent': None,
210 'markup': u'plaintext',
211 'content_object': topic,
212 'is_public': True,
213 'ip_address': u'127.0.0.1',
214 'email': u'floguy@gmail.com',
215 'is_approved': False
216 })
217
218 def test_freecomment_xml_create(self):
219
220 topic = TestModel.objects.create(name='Test2')
221 content_type = ContentType.objects.get_for_model(topic)
222
223 url = reverse('tc_free_comment_ajax', kwargs={
224 'content_type': content_type.id,
225 'object_id': topic.id,
226 'ajax': 'xml'
227 })
228
229 response = self.client.post(url, {'comment': 'test3', 'name': 'eric',
230 'website': 'http://www.eflorenzano.com/', 'email': 'floguy@gmail.com', 'next': '/'})
231 tmp = parseString(response.content)
232 o = FreeThreadedComment.objects.latest(
233 'date_submitted').get_base_data(show_dates=False)
234 self.assertEquals(o, {
235 'website': u'http://www.eflorenzano.com/',
236 'comment': u'test3',
237 'name': u'eric',
238 'parent': None,
239 'markup': u'plaintext',
240 'content_object': topic,
241 'is_public': True,
242 'ip_address': u'127.0.0.1',
243 'email': u'floguy@gmail.com',
244 'is_approved': False
245 })
246
247 def test_freecomment_xml_edit(self):
248
249 topic = TestModel.objects.create(name='Test2')
250
251 comment = FreeThreadedComment.objects.create_for_object(topic,
252 ip_address='127.0.0.1',
253 comment='My test free comment!',
254 )
255
256 url = reverse('tc_free_comment_edit_ajax', kwargs={
257 'edit_id': comment.pk,
258 'ajax': 'xml'
259 })
260
261 response = self.client.post(url, {
262 'comment': 'test2_edited',
263 'name': 'eric',
264 'website': 'http://www.eflorenzano.com/',
265 'email': 'floguy@gmail.com'
266 })
267 tmp = parseString(response.content)
268 o = FreeThreadedComment.objects.latest(
269 'date_submitted').get_base_data(show_dates=False)
270 self.assertEquals(o, {
271 'website': u'http://www.eflorenzano.com/',
272 'comment': u'test2_edited',
273 'name': u'eric',
274 'parent': None,
275 'markup': u'plaintext',
276 'content_object': topic,
277 'is_public': True,
278 'ip_address': u'127.0.0.1',
279 'email': u'floguy@gmail.com',
280 'is_approved': False
281 })
282
283 def test_freecomment_child_create(self):
284
285 topic = TestModel.objects.create(name='Test2')
286 content_type = ContentType.objects.get_for_model(topic)
287
288 parent = FreeThreadedComment.objects.create_for_object(topic,
289 ip_address='127.0.0.1',
290 comment='My test free comment!',
291 )
292
293 url = reverse('tc_free_comment_parent', kwargs={
294 'content_type': content_type.id,
295 'object_id': topic.id,
296 'parent_id': parent.id
297 })
298 response = self.client.post(url, {
299 'comment': 'test4',
300 'name': 'eric',
301 'website': 'http://www.eflorenzano.com/',
302 'email': 'floguy@gmail.com',
303 'next': '/'
304 })
305 o = FreeThreadedComment.objects.latest(
306 'date_submitted').get_base_data(show_dates=False)
307 self.assertEquals(o, {
308 'website': u'http://www.eflorenzano.com/',
309 'comment': u'test4',
310 'name': u'eric',
311 'parent': parent,
312 'markup': u'plaintext',
313 'content_object': topic,
314 'is_public': True,
315 'ip_address': u'127.0.0.1',
316 'email': u'floguy@gmail.com',
317 'is_approved': False
318 })
319
320 def test_freecomment_child_json_create(self):
321
322 topic = TestModel.objects.create(name='Test2')
323 content_type = ContentType.objects.get_for_model(topic)
324
325 parent = FreeThreadedComment.objects.create_for_object(topic,
326 ip_address='127.0.0.1',
327 comment='My test free comment!',
328 )
329
330 url = reverse('tc_free_comment_parent_ajax', kwargs={
331 'content_type': content_type.id,
332 'object_id': topic.id,
333 'parent_id': parent.id,
334 'ajax': 'json'
335 })
336
337 response = self.client.post(url, {
338 'comment': 'test5',
339 'name': 'eric',
340 'website': 'http://www.eflorenzano.com/',
341 'email': 'floguy@gmail.com'
342 })
343 tmp = loads(response.content)
344 o = FreeThreadedComment.objects.latest(
345 'date_submitted').get_base_data(show_dates=False)
346 self.assertEquals(o, {
347 'website': u'http://www.eflorenzano.com/',
348 'comment': u'test5',
349 'name': u'eric',
350 'parent': parent,
351 'markup': u'plaintext',
352 'content_object': topic,
353 'is_public': True,
354 'ip_address': u'127.0.0.1',
355 'email': u'floguy@gmail.com',
356 'is_approved': False
357 })
358
359 def test_freecomment_child_xml_create(self):
360
361 topic = TestModel.objects.create(name='Test2')
362 content_type = ContentType.objects.get_for_model(topic)
363
364 parent = FreeThreadedComment.objects.create_for_object(topic,
365 ip_address='127.0.0.1',
366 comment='My test free comment!',
367 )
368
369 url = reverse('tc_free_comment_parent_ajax', kwargs={
370 'content_type': content_type.id,
371 'object_id': topic.id,
372 'parent_id': parent.id,
373 'ajax': 'xml'
374 })
375
376 response = self.client.post(url, {
377 'comment': 'test6', 'name': 'eric',
378 'website': 'http://www.eflorenzano.com/',
379 'email': 'floguy@gmail.com'
380 })
381 tmp = parseString(response.content)
382 o = FreeThreadedComment.objects.latest(
383 'date_submitted').get_base_data(show_dates=False)
384 self.assertEquals(o, {
385 'website': u'http://www.eflorenzano.com/',
386 'comment': u'test6',
387 'name': u'eric',
388 'parent': parent,
389 'markup': u'plaintext',
390 'content_object': topic,
391 'is_public': True,
392 'ip_address': u'127.0.0.1',
393 'email': u'floguy@gmail.com',
394 'is_approved': False
395 })
396
397 def create_user_and_login(self):
398 user = User.objects.create_user(
399 'testuser',
400 'testuser@gmail.com',
401 'password',
402 )
403 user.is_active = True
404 user.save()
405 self.client.login(username='testuser', password='password')
406 return user
407
408 def test_comment_create(self):
409
410 user = self.create_user_and_login()
411
412 topic = TestModel.objects.create(name='Test2')
413 content_type = ContentType.objects.get_for_model(topic)
414
415 url = reverse('tc_comment', kwargs={
416 'content_type': content_type.id,
417 'object_id': topic.id
418 })
419
420 response = self.client.post(url, {
421 'comment': 'test7',
422 'next': '/'
423 })
424 o = ThreadedComment.objects.latest(
425 'date_submitted').get_base_data(show_dates=False)
426 self.assertEquals(o, {
427 'comment': u'test7',
428 'is_approved': False,
429 'parent': None,
430 'markup': u'plaintext',
431 'content_object': topic,
432 'user': user,
433 'is_public': True,
434 'ip_address': u'127.0.0.1',
435 })
436
437 def test_comment_preview(self):
438
439 user = self.create_user_and_login()
440
441 topic = TestModel.objects.create(name='Test2')
442 content_type = ContentType.objects.get_for_model(topic)
443
444 url = reverse('tc_comment', kwargs={
445 'content_type': content_type.id,
446 'object_id': topic.id
447 })
448
449 response = self.client.post(url, {
450 'comment': 'test7',
451 'next': '/',
452 'preview': 'True'
453 })
454 self.assertEquals(len(response.content) > 0, True)
455
456 def test_comment_edit(self):
457
458 user = self.create_user_and_login()
459
460 topic = TestModel.objects.create(name='Test2')
461 comment = ThreadedComment.objects.create_for_object(topic,
462 user=user,
463 ip_address=u'127.0.0.1',
464 comment='My test comment!',
465 )
466
467 url = reverse('tc_comment_edit', kwargs={
468 'edit_id': comment.pk,
469 })
470
471 response = self.client.post(url, {
472 'comment': 'test7_edited',
473 'next': '/',
474 })
475 o = ThreadedComment.objects.latest(
476 'date_submitted').get_base_data(show_dates=False)
477 self.assertEquals(o, {
478 'comment': u'test7_edited',
479 'is_approved': False,
480 'parent': None,
481 'markup': u'plaintext',
482 'content_object': topic,
483 'user': user,
484 'is_public': True,
485 'ip_address': u'127.0.0.1',
486 })
487
488 def test_comment_edit_with_preview(self):
489
490 user = self.create_user_and_login()
491
492 topic = TestModel.objects.create(name='Test2')
493 comment = ThreadedComment.objects.create_for_object(topic,
494 user=user,
495 ip_address=u'127.0.0.1',
496 comment='My test comment!',
497 )
498
499 url = reverse('tc_comment_edit', kwargs={
500 'edit_id': comment.pk,
501 })
502
503 response = self.client.post(url, {
504 'comment': 'test7_edited',
505 'next': '/',
506 'preview': 'True'
507 })
508
509 self.assertEquals(len(response.content) > 0, True)
510 o = ThreadedComment.objects.latest(
511 'date_submitted').get_base_data(show_dates=False)
512 self.assertEquals(o, {
513 'comment': u'My test comment!',
514 'is_approved': False,
515 'parent': None,
516 'markup': u'plaintext',
517 'content_object': topic,
518 'user': user,
519 'is_public': True,
520 'ip_address': u'127.0.0.1',
521 })
522
523 def test_comment_json_create(self):
524
525 user = self.create_user_and_login()
526
527 topic = TestModel.objects.create(name='Test2')
528 content_type = ContentType.objects.get_for_model(topic)
529
530 url = reverse('tc_comment_ajax', kwargs={
531 'content_type': content_type.id,
532 'object_id': topic.id,
533 'ajax': 'json'
534 })
535
536 response = self.client.post(url, {
537 'comment': 'test8'
538 })
539 tmp = loads(response.content)
540 o = ThreadedComment.objects.latest(
541 'date_submitted').get_base_data(show_dates=False)
542 self.assertEquals(o, {
543 'comment': u'test8',
544 'is_approved': False,
545 'parent': None,
546 'markup': u'plaintext',
547 'content_object': topic,
548 'user': user,
549 'is_public': True,
550 'ip_address': u'127.0.0.1',
551 })
552
553 def test_comment_json_edit(self):
554
555 user = self.create_user_and_login()
556
557 topic = TestModel.objects.create(name='Test2')
558 comment = ThreadedComment.objects.create_for_object(topic,
559 user=user,
560 ip_address=u'127.0.0.1',
561 comment='My test comment!',
562 )
563
564 url = reverse('tc_comment_edit_ajax', kwargs={
565 'edit_id': comment.pk,
566 'ajax': 'json',
567 })
568
569 response = self.client.post(url, {
570 'comment': 'test8_edited'
571 })
572 tmp = loads(response.content)
573 o = ThreadedComment.objects.latest(
574 'date_submitted').get_base_data(show_dates=False)
575 self.assertEquals(o, {
576 'comment': u'test8_edited',
577 'is_approved': False,
578 'parent': None,
579 'markup': u'plaintext',
580 'content_object': topic,
581 'user': user,
582 'is_public': True,
583 'ip_address': u'127.0.0.1',
584 })
585
586 def test_comment_xml_create(self):
587
588 user = self.create_user_and_login()
589
590 topic = TestModel.objects.create(name='Test2')
591 content_type = ContentType.objects.get_for_model(topic)
592
593 url = reverse('tc_comment_ajax', kwargs={
594 'content_type': content_type.id,
595 'object_id': topic.id,
596 'ajax': 'xml'
597 })
598
599 response = self.client.post(url, {
600 'comment': 'test9'
601 })
602 tmp = parseString(response.content)
603 o = ThreadedComment.objects.latest(
604 'date_submitted').get_base_data(show_dates=False)
605 self.assertEquals(o, {
606 'comment': u'test9',
607 'is_approved': False,
608 'parent': None,
609 'markup': u'plaintext',
610 'content_object': topic,
611 'user': user,
612 'is_public': True,
613 'ip_address': u'127.0.0.1',
614 })
615
616 def test_comment_xml_edit(self):
617
618 user = self.create_user_and_login()
619
620 topic = TestModel.objects.create(name='Test2')
621 comment = ThreadedComment.objects.create_for_object(topic,
622 user=user,
623 ip_address=u'127.0.0.1',
624 comment='My test comment!',
625 )
626
627 url = reverse('tc_comment_edit_ajax', kwargs={
628 'edit_id': comment.pk,
629 'ajax': 'xml',
630 })
631
632 response = self.client.post(url, {
633 'comment': 'test8_edited'
634 })
635 tmp = parseString(response.content)
636 o = ThreadedComment.objects.latest(
637 'date_submitted').get_base_data(show_dates=False)
638 self.assertEquals(o, {
639 'comment': u'test8_edited',
640 'is_approved': False,
641 'parent': None,
642 'markup': u'plaintext',
643 'content_object': topic,
644 'user': user,
645 'is_public': True,
646 'ip_address': u'127.0.0.1',
647 })
648
649 def test_comment_child_create(self):
650
651 user = self.create_user_and_login()
652
653 topic = TestModel.objects.create(name='Test2')
654 content_type = ContentType.objects.get_for_model(topic)
655
656 parent = ThreadedComment.objects.create_for_object(topic,
657 user=user,
658 ip_address=u'127.0.0.1',
659 comment='My test comment!',
660 )
661
662 url = reverse('tc_comment_parent', kwargs={
663 'content_type': content_type.id,
664 'object_id': topic.id,
665 'parent_id': parent.id
666 })
667
668 response = self.client.post(url, {
669 'comment': 'test10',
670 'next': '/'
671 })
672 o = ThreadedComment.objects.latest(
673 'date_submitted').get_base_data(show_dates=False)
674 self.assertEquals(o, {
675 'comment': u'test10',
676 'is_approved': False,
677 'parent': parent,
678 'markup': u'plaintext',
679 'content_object': topic,
680 'user': user,
681 'is_public': True,
682 'ip_address': u'127.0.0.1',
683 })
684
685 def test_comment_child_json_create(self):
686
687 user = self.create_user_and_login()
688
689 topic = TestModel.objects.create(name='Test2')
690 content_type = ContentType.objects.get_for_model(topic)
691
692 parent = ThreadedComment.objects.create_for_object(topic,
693 user=user,
694 ip_address=u'127.0.0.1',
695 comment='My test comment!',
696 )
697
698 url = reverse('tc_comment_parent_ajax', kwargs={
699 'content_type': content_type.id,
700 'object_id': topic.id,
701 'parent_id': parent.id,
702 'ajax': 'json'
703 })
704
705 response = self.client.post(url, {
706 'comment': 'test11'
707 })
708 tmp = loads(response.content)
709 o = ThreadedComment.objects.latest(
710 'date_submitted').get_base_data(show_dates=False)
711 self.assertEquals(o, {
712 'comment': u'test11',
713 'is_approved': False,
714 'parent': parent,
715 'markup': u'plaintext',
716 'content_object': topic,
717 'user': user,
718 'is_public': True,
719 'ip_address': u'127.0.0.1',
720 })
721
722 def test_comment_child_xml_create(self):
723
724 user = self.create_user_and_login()
725
726 topic = TestModel.objects.create(name='Test2')
727 content_type = ContentType.objects.get_for_model(topic)
728
729 parent = ThreadedComment.objects.create_for_object(topic,
730 user=user,
731 ip_address=u'127.0.0.1',
732 comment='My test comment!',
733 )
734
735 url = reverse('tc_comment_parent_ajax', kwargs={
736 'content_type': content_type.id,
737 'object_id': topic.id,
738 'parent_id': parent.id,
739 'ajax': 'xml'
740 })
741
742 response = self.client.post(url, {
743 'comment': 'test12'
744 })
745 tmp = parseString(response.content)
746 o = ThreadedComment.objects.latest(
747 'date_submitted').get_base_data(show_dates=False)
748 self.assertEquals(o, {
749 'comment': u'test12',
750 'is_approved': False,
751 'parent': parent,
752 'markup': u'plaintext',
753 'content_object': topic,
754 'user': user,
755 'is_public': True,
756 'ip_address': u'127.0.0.1',
757 })
758
759 def test_freecomment_delete(self):
760
761 user = User.objects.create_user(
762 'testuser',
763 'testuser@gmail.com',
764 'password',
765 )
766 user.is_active = True
767 user.save()
768 self.client.login(username='testuser', password='password')
769
770 topic = TestModel.objects.create(name='Test2')
771
772 comment = FreeThreadedComment.objects.create_for_object(topic,
773 ip_address=u'127.0.0.1',
774 comment='My test comment!',
775 )
776 deleted_id = comment.id
777
778 url = reverse('tc_free_comment_delete', kwargs={
779 'object_id': comment.id,
780 })
781
782 response = self.client.post(url, {'next': '/'})
783 o = response['Location'].split(
784 '?')[-1] == 'next=/freecomment/%d/delete/' % deleted_id
785 self.assertEquals(o, True)
786
787 # become super user and try deleting comment
788 user.is_superuser = True
789 user.save()
790
791 response = self.client.post(url, {'next': '/'})
792 self.assertEquals(response['Location'], 'http://testserver/')
793 self.assertRaises(
794 FreeThreadedComment.DoesNotExist,
795 lambda: FreeThreadedComment.objects.get(id=deleted_id)
796 )
797
798 # re-create comment
799 comment.save()
800
801 response = self.client.get(url, {'next': '/'})
802 self.assertEquals(len(response.content) > 0, True)
803
804 o = FreeThreadedComment.objects.get(id=deleted_id) != None
805 self.assertEquals(o, True)
806
807 def test_comment_delete(self):
808
809 some_other_guy = User.objects.create_user(
810 'some_other_guy',
811 'somewhere@overthemoon.com',
812 'password1',
813 )
814 user = User.objects.create_user(
815 'testuser',
816 'testuser@gmail.com',
817 'password',
818 )
819 user.is_active = True
820 user.save()
821 self.client.login(username='testuser', password='password')
822
823 topic = TestModel.objects.create(name='Test2')
824
825 comment = ThreadedComment.objects.create_for_object(topic,
826 user=some_other_guy,
827 ip_address=u'127.0.0.1',
828 comment='My test comment!',
829 )
830 deleted_id = comment.id
831
832 url = reverse('tc_comment_delete', kwargs={
833 'object_id': comment.id,
834 })
835 response = self.client.post(url, {'next': '/'})
836 self.assertEquals(response['Location'].split(
837 '?')[-1], 'next=/comment/%s/delete/' % deleted_id)
838
839 user.is_superuser = True
840 user.save()
841
842 response = self.client.post(url, {'next': '/'})
843 self.assertEquals(response['Location'], 'http://testserver/')
844 self.assertRaises(
845 ThreadedComment.DoesNotExist,
846 lambda: ThreadedComment.objects.get(id=deleted_id)
847 )
848
849 # re-create comment
850 comment.save()
851
852 response = self.client.get(url, {'next': '/'})
853 self.assertEquals(len(response.content) > 0, True)
854
855 o = ThreadedComment.objects.get(id=deleted_id) != None
856 self.assertEquals(o, True)
8570
=== modified file 'threadedcomments/views.py'
--- threadedcomments/views.py 2016-12-13 18:28:51 +0000
+++ threadedcomments/views.py 2018-04-19 17:49:06 +0000
@@ -1,7 +1,7 @@
1from django.http import HttpResponseRedirect, Http4041from django.http import HttpResponseRedirect, Http404
2from django.contrib.auth.decorators import login_required2from django.contrib.auth.decorators import login_required
3from django.contrib.contenttypes.models import ContentType3from django.contrib.contenttypes.models import ContentType
4from django.shortcuts import get_object_or_404, render_to_response4from django.shortcuts import get_object_or_404, render
5from django.template import RequestContext, Context, Template5from django.template import RequestContext, Context, Template
6from django.utils.http import urlquote6from django.utils.http import urlquote
7from django.conf import settings7from django.conf import settings
@@ -53,11 +53,10 @@
53 context['comment'] = new_comment53 context['comment'] = new_comment
54 else:54 else:
55 context['comment'] = None55 context['comment'] = None
56 return render_to_response(56 return render(request,
57 'threadedcomments/preview_comment.html',57 'threadedcomments/preview_comment.html',
58 extra_context,58 extra_context,
59 context_instance=RequestContext(request, context, context_processors)59 )
60 )
6160
6261
63def 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={}):62def 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={}):
@@ -151,7 +150,7 @@
151 return True150 return True
152 return False151 return False
153152
154153# Todo: Next one is not used so far and may need adjustments to the render()
155def comment_delete(request, object_id, model=ThreadedComment, extra_context={}, context_processors=[], permission_callback=can_delete_comment):154def comment_delete(request, object_id, model=ThreadedComment, extra_context={}, context_processors=[], permission_callback=can_delete_comment):
156 """Deletes the specified comment, which can be either a155 """Deletes the specified comment, which can be either a
157 ``FreeThreadedComment`` or a ``ThreadedComment``.156 ``FreeThreadedComment`` or a ``ThreadedComment``.
@@ -175,17 +174,15 @@
175 else:174 else:
176 is_free_threaded_comment = True175 is_free_threaded_comment = True
177 is_threaded_comment = False176 is_threaded_comment = False
178 return render_to_response(177
179 'threadedcomments/confirm_delete.html',178 extra_context.update(
180 extra_context,179 {'comment': tc,
181 context_instance=RequestContext(180 'is_free_threaded_comment': is_free_threaded_comment,
182 request,181 'is_threaded_comment': is_threaded_comment,
183 {182 'next': _get_next(request),
184 'comment': tc,183 }
185 'is_free_threaded_comment': is_free_threaded_comment,
186 'is_threaded_comment': is_threaded_comment,
187 'next': _get_next(request),
188 },
189 context_processors
190 )184 )
191 )185 return render(request,
186 'threadedcomments/confirm_delete.html',
187 extra_context,
188 )
192189
=== removed directory 'tracking'
=== removed file 'tracking/AUTHORS'
--- tracking/AUTHORS 2016-06-05 20:58:46 +0000
+++ tracking/AUTHORS 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1django-tracking was written by Josh VanderLinden
2
3There have been many contributions, including:
4
5ramusus (https://github.com/ramusus/django-tracking)
6jezdez (https://bitbucket.org/jezdez/django-tracking)
70
=== removed file 'tracking/CHANGES'
--- tracking/CHANGES 2016-06-05 20:58:46 +0000
+++ tracking/CHANGES 1970-01-01 00:00:00 +0000
@@ -1,44 +0,0 @@
1=======================
2django-tracking changes
3=======================
4
50.3.5
6-----
7
8- Using Django's GIS utilities a little better
9- Added caching in the middleware to reduce hits to the database
10- Added some logging
11
120.3.3
13-----
14
15- Improved the setup.py script
16
170.3.2
18-----
19
20- Merged changes from ramusus to better deal with the unicode problems
21
220.3.1
23-----
24
25- Trying to handle some unicode problems
26- Added a sample base.html template
27- Code cleaning
28
290.3.0
30-----
31
32- Fixed several bugs dealing with performance
33- Improved stability
34- Added some German translations
35- Removed dependency on GeoIP C API and Python API in favor of Django's
36 built-in GIS utilities
37- Tweaked the active users map
38
390.2.5
40-----
41
42- Did some code optimization
43- Added the ability to have a dynamically updating Google Map to show where your
44 visitors are coming from
450
=== removed file 'tracking/LICENSE'
--- tracking/LICENSE 2016-06-05 20:58:46 +0000
+++ tracking/LICENSE 1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
1Copyright (c) 2008-2009 Josh VanderLinden
2
3Permission is hereby granted, free of charge, to any person obtaining a copy
4of this software and associated documentation files (the "Software"), to deal
5in the Software without restriction, including without limitation the rights
6to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7copies of the Software, and to permit persons to whom the Software is
8furnished to do so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in
11all copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19THE SOFTWARE.
20\ No newline at end of file0\ No newline at end of file
211
=== removed file 'tracking/__init__.py'
--- tracking/__init__.py 2016-12-13 18:28:51 +0000
+++ tracking/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1import listeners
2
3VERSION = (0, 4, 0)
4
5
6def get_version():
7 'Returns the version as a human-format string.'
8 return '.'.join([str(i) for i in VERSION])
90
=== removed file 'tracking/admin.py'
--- tracking/admin.py 2016-12-13 18:28:51 +0000
+++ tracking/admin.py 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1from django.contrib import admin
2from tracking.models import BannedIP, UntrackedUserAgent
3
4admin.site.register(BannedIP)
5admin.site.register(UntrackedUserAgent)
60
=== removed file 'tracking/listeners.py'
--- tracking/listeners.py 2016-12-13 18:28:51 +0000
+++ tracking/listeners.py 1970-01-01 00:00:00 +0000
@@ -1,35 +0,0 @@
1import logging
2
3log = logging.getLogger('tracking.listeners')
4
5try:
6 from django.core.cache import cache
7 from django.db.models.signals import post_save, post_delete
8
9 from tracking.models import UntrackedUserAgent, BannedIP
10except ImportError:
11 pass
12else:
13
14 def refresh_untracked_user_agents(sender, instance, created=False, **kwargs):
15 """Updates the cache of user agents that we don't track."""
16
17 log.debug('Updating untracked user agents cache')
18 cache.set('_tracking_untracked_uas',
19 UntrackedUserAgent.objects.all(),
20 3600)
21
22 def refresh_banned_ips(sender, instance, created=False, **kwargs):
23 """Updates the cache of banned IP addresses."""
24
25 log.debug('Updating banned IP cache')
26 cache.set('_tracking_banned_ips',
27 [b.ip_address for b in BannedIP.objects.all()],
28 3600)
29
30 post_save.connect(refresh_untracked_user_agents, sender=UntrackedUserAgent)
31 post_delete.connect(refresh_untracked_user_agents,
32 sender=UntrackedUserAgent)
33
34 post_save.connect(refresh_banned_ips, sender=BannedIP)
35 post_delete.connect(refresh_banned_ips, sender=BannedIP)
360
=== removed directory 'tracking/locale'
=== removed directory 'tracking/locale/de'
=== removed directory 'tracking/locale/de/LC_MESSAGES'
=== removed file 'tracking/locale/de/LC_MESSAGES/django.mo'
37Binary 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 differ1Binary 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
=== removed file 'tracking/locale/de/LC_MESSAGES/django.po'
--- tracking/locale/de/LC_MESSAGES/django.po 2016-06-05 20:58:46 +0000
+++ tracking/locale/de/LC_MESSAGES/django.po 1970-01-01 00:00:00 +0000
@@ -1,110 +0,0 @@
1# SOME DESCRIPTIVE TITLE.
2# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3# This file is distributed under the same license as the PACKAGE package.
4# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5#
6#, fuzzy
7msgid ""
8msgstr ""
9"Project-Id-Version: PACKAGE VERSION\n"
10"Report-Msgid-Bugs-To: \n"
11"POT-Creation-Date: 2010-03-24 12:45+0100\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"
18
19#: models.py:77
20msgid "unknown"
21msgstr "unbekannt"
22
23#: models.py:121
24msgid "keyword"
25msgstr "Schlagwort"
26
27#: models.py:121
28msgid ""
29"Part or all of a user-agent string. For example, \"Googlebot\" here will be "
30"found in \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/"
31"bot.html)\" and that visitor will not be tracked."
32msgstr ""
33"Ein Teil oder der ganze Name eines User Agent. \"Googlebot\" zum Beispiel "
34"wird auch in \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google."
35"com/bot.html)\" gefunden werden und verhindern, dass dieser \"User\" "
36"getrackt wird."
37
38#: models.py:128
39msgid "Untracked User-Agent"
40msgstr "Nicht-getrackter User-Agent"
41
42#: models.py:129
43msgid "Untracked User-Agents"
44msgstr "Nicht-getrackte User-Agents"
45
46#: models.py:132
47msgid "The IP address that should be banned"
48msgstr "Die IP-Adresse ist schon verboten"
49
50#: models.py:139
51msgid "Banned IP"
52msgstr "Verbotene IP"
53
54#: models.py:140
55msgid "Banned IPs"
56msgstr "Verbotene IPs"
57
58#: views.py:79
59#, python-format
60msgid "%(minutes)i minute"
61msgid_plural "%(minutes)i minutes"
62msgstr[0] "%(minutes)i Minute"
63msgstr[1] "%(minutes)i Minuten"
64
65#: views.py:85
66#, python-format
67msgid "%(seconds)i second"
68msgid_plural "%(seconds)i seconds"
69msgstr[0] "%(seconds)i Sekunde"
70msgstr[1] "%(seconds)i Sekunden"
71
72#: templates/tracking/_active_users.html:2
73#, python-format
74msgid "1 active user"
75msgid_plural "%(counter)s active users"
76msgstr[0] "1 aktiver Benutzer"
77msgstr[1] "%(counter)s aktive Benutzer"
78
79#: templates/tracking/_active_users.html:5
80#, python-format
81msgid "1 guest user"
82msgid_plural "%(counter)s guest users"
83msgstr[0] "1 Gastbenutzer"
84msgstr[1] "%(counter)s Gastbenutzer"
85
86#: templates/tracking/_active_users.html:15
87#, python-format
88msgid "1 registered user"
89msgid_plural "%(counter)s registered users"
90msgstr[0] "1 registrierter Benutzer"
91msgstr[1] "%(counter)s registrierte Benutzer"
92
93#: templates/tracking/visitor_map.html:4
94msgid "Active Visitors Map"
95msgstr "Aktive Benutzer Karte"
96
97#: templates/tracking/visitor_map.html:15
98msgid "Active Users"
99msgstr "Aktive Benutzer"
100
101#: templates/tracking/visitor_map.html:18
102msgid ""
103"Below is a map and a list of recently active users on this site. It updates "
104"itself every 5 seconds or so, adding and removing pins in the map as "
105"necessary. Stick around for a few minutes to see it in action!"
106msgstr ""
107"Unten sehen Sie eine Karte und eine Liste der Benutzer, die gerade auf "
108"dieser Seite aktiv sind. Ungefähr alle 5 Sekunden werden die Daten "
109"aktualisert und rendern die Karte neu. Bleiben Sie ein paar Minuten, um es "
110"live in Aktion zu erleben!"
1110
=== removed directory 'tracking/media'
=== removed directory 'tracking/media/tracking'
=== removed directory 'tracking/media/tracking/img'
=== removed file 'tracking/media/tracking/img/Flags.zip'
112Binary 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 differ1Binary 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
=== removed file 'tracking/media/tracking/img/famfamfam_flag_icons.zip'
113Binary 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 differ2Binary 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
=== removed directory 'tracking/media/tracking/js'
=== removed file 'tracking/media/tracking/js/jquery-1.4.4.min.js'
--- tracking/media/tracking/js/jquery-1.4.4.min.js 2016-06-05 20:58:46 +0000
+++ tracking/media/tracking/js/jquery-1.4.4.min.js 1970-01-01 00:00:00 +0000
@@ -1,167 +0,0 @@
1/*!
2 * jQuery JavaScript Library v1.4.4
3 * http://jquery.com/
4 *
5 * Copyright 2010, John Resig
6 * Dual licensed under the MIT or GPL Version 2 licenses.
7 * http://jquery.org/license
8 *
9 * Includes Sizzle.js
10 * http://sizzlejs.com/
11 * Copyright 2010, The Dojo Foundation
12 * Released under the MIT, BSD, and GPL Licenses.
13 *
14 * Date: Thu Nov 11 19:04:53 2010 -0500
15 */
16(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=
17h.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"||
18h.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,
19"`").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,
20e);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,
21"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("<"+
22a+">").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>)?$/,
23C=/^[\],:{}\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,
24s){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,
25j)}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},
26toArray: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===
27-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;
28if(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--;
29if(!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",
30b.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&&
31!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&&
32l.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],
33z,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,
34s){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=
35s;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)||
36[];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,
37false);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"),
38k=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,
39scriptEval: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=
40false;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=
411;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=
42"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=
43c.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);
44else 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,
45a)});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=
46c.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,
47a)})},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",
48colspan:"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===
491)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," "),
50l=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,
51"__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";
52if(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=
53a.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},
54attr: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"))&&
55b.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};
56c.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,
57arguments):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=
58d.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("(^|\\.)"+
59c.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=
60w.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===
618)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===
62"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("(^|\\.)"+
63d.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(" "),
64fix: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||
65d&&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,
66Y(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=
67c.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};
68var 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()!==
69"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,
70xa=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=
71B;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===
72"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]===
730&&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,
74a[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=
751;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===
76"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}});
77c.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){}});
78(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,
79[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];
80break}}}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,
81q.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=
82l;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("*"));
83return{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!==
84B){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+\-]*)\))?/,
85POS:/:(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()===
86i?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=
87i=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,
88"")+" ";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,
89m,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===
90true},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"===
91g.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]-
920===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===
93"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()===
94i},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]];
95if(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,
96g);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;
97for(;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"),
98i="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);
99n=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&&
100function(){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||
101p.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=
102t.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?
103function(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;
104c.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})},
105not: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=
106h.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):
107c.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,
1082,"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,
109b){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&&
110e.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,
111"<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=
112c(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},
113wrapInner: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)})},
114prepend: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,
115this.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);
116return 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;
117else 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=
118c(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,
119b,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")):
120this[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",
121prependTo:"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||
122b[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-
1231;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"))));
124d.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,
125jb=/^-?\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,
126zoom: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),
127h=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);
128if(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=
129d.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;
130e=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,
131ob=/^(?: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===
132"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&&
133!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})},
134getScript: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",
135script:"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||
136!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=
137false;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;
138A.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",
139b.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&&
140c.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||
141c.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]=
142encodeURIComponent(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",
143[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"),
144e=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}});
145if(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",
1463),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",
147d)}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,
148d,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)===
149"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||
1501)/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,
151d,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*
152Math.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)}
153var 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;
154this.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||
155this.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=
156c.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===
157b.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&&
158h.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;
159for(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+=
160parseFloat(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",
161height:"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=
162f.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,
163"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,
164e):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)&&
165c.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();
166c.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"+
167b],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);
1680
=== removed file 'tracking/middleware.py'
--- tracking/middleware.py 2016-12-13 18:28:51 +0000
+++ tracking/middleware.py 1970-01-01 00:00:00 +0000
@@ -1,195 +0,0 @@
1from datetime import datetime, timedelta
2import logging
3import re
4import traceback
5
6from django.conf import settings
7from django.contrib.auth.models import AnonymousUser
8from django.core.cache import cache
9from django.core.urlresolvers import reverse, NoReverseMatch
10from django.db.utils import DatabaseError
11from django.http import Http404
12
13from tracking import utils
14from tracking.models import Visitor, UntrackedUserAgent, BannedIP
15
16title_re = re.compile('<title>(.*?)</title>')
17log = logging.getLogger('tracking.middleware')
18
19
20class VisitorTrackingMiddleware(object):
21 """Keeps track of your active users. Anytime a visitor accesses a valid
22 URL, their unique record will be updated with the page they're on and the
23 last time they requested a page.
24
25 Records are considered to be unique when the session key and IP
26 address are unique together. Sometimes the same user used to have
27 two different records, so I added a check to see if the session key
28 had changed for the same IP and user agent in the last 5 minutes
29
30 """
31
32 @property
33 def prefixes(self):
34 """Returns a list of URL prefixes that we should not track."""
35
36 if not hasattr(self, '_prefixes'):
37 self._prefixes = getattr(settings, 'NO_TRACKING_PREFIXES', [])
38
39 if not getattr(settings, '_FREEZE_TRACKING_PREFIXES', False):
40 for name in ('MEDIA_URL', 'STATIC_URL'):
41 url = getattr(settings, name)
42 if url and url != '/':
43 self._prefixes.append(url)
44
45 try:
46 # finally, don't track requests to the tracker update pages
47 self._prefixes.append(
48 reverse('tracking-refresh-active-users'))
49 except NoReverseMatch:
50 # django-tracking hasn't been included in the URLconf if we
51 # get here, which is not a bad thing
52 pass
53
54 settings.NO_TRACKING_PREFIXES = self._prefixes
55 settings._FREEZE_TRACKING_PREFIXES = True
56
57 return self._prefixes
58
59 def process_request(self, request):
60 # don't process AJAX requests
61 if request.is_ajax():
62 return
63
64 # create some useful variables
65 ip_address = utils.get_ip(request)
66 user_agent = unicode(request.META.get(
67 'HTTP_USER_AGENT', '')[:255], errors='ignore')
68
69 # retrieve untracked user agents from cache
70 ua_key = '_tracking_untracked_uas'
71 untracked = cache.get(ua_key)
72 if untracked is None:
73 log.info('Updating untracked user agent cache')
74 untracked = UntrackedUserAgent.objects.all()
75 cache.set(ua_key, untracked, 3600)
76
77 # see if the user agent is not supposed to be tracked
78 for ua in untracked:
79 # if the keyword is found in the user agent, stop tracking
80 if user_agent.find(ua.keyword) != -1:
81 log.debug('Not tracking UA "%s" because of keyword: %s' %
82 (user_agent, ua.keyword))
83 return
84
85 if hasattr(request, 'session') and request.session.session_key:
86 # use the current session key if we can
87 session_key = request.session.session_key
88 else:
89 # otherwise just fake a session key
90 session_key = '%s:%s' % (ip_address, user_agent)
91 session_key = session_key[:40]
92
93 # ensure that the request.path does not begin with any of the prefixes
94 for prefix in self.prefixes:
95 if request.path.startswith(prefix):
96 log.debug('Not tracking request to: %s' % request.path)
97 return
98
99 # if we get here, the URL needs to be tracked
100 # determine what time it is
101 now = datetime.now()
102
103 attrs = {
104 'session_key': session_key,
105 'ip_address': ip_address
106 }
107
108 # for some reason, Visitor.objects.get_or_create was not working here
109 try:
110 visitor = Visitor.objects.get(**attrs)
111 except Visitor.DoesNotExist:
112 # see if there's a visitor with the same IP and user agent
113 # within the last 5 minutes
114 cutoff = now - timedelta(minutes=5)
115 visitors = Visitor.objects.filter(
116 ip_address=ip_address,
117 user_agent=user_agent,
118 last_update__gte=cutoff
119 )
120
121 if len(visitors):
122 visitor = visitors[0]
123 visitor.session_key = session_key
124 log.debug('Using existing visitor for IP %s / UA %s: %s' %
125 (ip_address, user_agent, visitor.id))
126 else:
127 # it's probably safe to assume that the visitor is brand new
128 visitor = Visitor(**attrs)
129 log.debug('Created a new visitor: %s' % attrs)
130 except:
131 return
132
133 # determine whether or not the user is logged in
134 user = request.user
135 if isinstance(user, AnonymousUser):
136 user = None
137
138 # update the tracking information
139 visitor.user = user
140 visitor.user_agent = user_agent
141
142 # if the visitor record is new, or the visitor hasn't been here for
143 # at least an hour, update their referrer URL
144 one_hour_ago = now - timedelta(hours=1)
145 if not visitor.last_update or visitor.last_update <= one_hour_ago:
146 visitor.referrer = utils.u_clean(
147 request.META.get('HTTP_REFERER', 'unknown')[:255])
148
149 # reset the number of pages they've been to
150 visitor.page_views = 0
151 visitor.session_start = now
152
153 visitor.url = request.path
154 visitor.page_views += 1
155 visitor.last_update = now
156 try:
157 visitor.save()
158 except DatabaseError:
159 log.error('There was a problem saving visitor information:\n%s\n\n%s' % (
160 traceback.format_exc(), locals()))
161
162
163class VisitorCleanUpMiddleware:
164 """Clean up old visitor tracking records in the database."""
165
166 def process_request(self, request):
167 timeout = utils.get_cleanup_timeout()
168
169 if str(timeout).isdigit():
170 log.debug('Cleaning up visitors older than %s hours' % timeout)
171 timeout = datetime.now() - timedelta(hours=int(timeout))
172 Visitor.objects.filter(last_update__lte=timeout).delete()
173
174
175class BannedIPMiddleware:
176 """Raises an Http404 error for any page request from a banned IP. IP
177 addresses may be added to the list of banned IPs via the Django admin.
178
179 The banned users do not actually receive the 404 error--instead they get
180 an "Internal Server Error", effectively eliminating any access to the site.
181
182 """
183
184 def process_request(self, request):
185 key = '_tracking_banned_ips'
186 ips = cache.get(key)
187 if ips is None:
188 # compile a list of all banned IP addresses
189 log.info('Updating banned IPs cache')
190 ips = [b.ip_address for b in BannedIP.objects.all()]
191 cache.set(key, ips, 3600)
192
193 # check to see if the current user's IP address is in that list
194 if utils.get_ip(request) in ips:
195 raise Http404
1960
=== removed directory 'tracking/migrations'
=== removed file 'tracking/migrations/0001_initial.py'
--- tracking/migrations/0001_initial.py 2016-12-13 18:28:51 +0000
+++ tracking/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3
4from django.db import models, migrations
5from django.conf import settings
6
7
8class Migration(migrations.Migration):
9
10 dependencies = [
11 migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 ]
13
14 operations = [
15 migrations.CreateModel(
16 name='BannedIP',
17 fields=[
18 ('id', models.AutoField(verbose_name='ID',
19 serialize=False, auto_created=True, primary_key=True)),
20 ('ip_address', models.GenericIPAddressField(
21 help_text='The IP address that should be banned', verbose_name=b'IP Address')),
22 ],
23 options={
24 'ordering': ('ip_address',),
25 'verbose_name': 'Banned IP',
26 'verbose_name_plural': 'Banned IPs',
27 },
28 ),
29 migrations.CreateModel(
30 name='UntrackedUserAgent',
31 fields=[
32 ('id', models.AutoField(verbose_name='ID',
33 serialize=False, auto_created=True, primary_key=True)),
34 ('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')),
35 ],
36 options={
37 'ordering': ('keyword',),
38 'verbose_name': 'Untracked User-Agent',
39 'verbose_name_plural': 'Untracked User-Agents',
40 },
41 ),
42 migrations.CreateModel(
43 name='Visitor',
44 fields=[
45 ('id', models.AutoField(verbose_name='ID',
46 serialize=False, auto_created=True, primary_key=True)),
47 ('session_key', models.CharField(max_length=40)),
48 ('ip_address', models.CharField(max_length=20)),
49 ('user_agent', models.CharField(max_length=255)),
50 ('referrer', models.CharField(max_length=255)),
51 ('url', models.CharField(max_length=255)),
52 ('page_views', models.PositiveIntegerField(default=0)),
53 ('session_start', models.DateTimeField()),
54 ('last_update', models.DateTimeField()),
55 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True)),
56 ],
57 options={
58 'ordering': ('-last_update',),
59 },
60 ),
61 migrations.AlterUniqueTogether(
62 name='visitor',
63 unique_together=set([('session_key', 'ip_address')]),
64 ),
65 ]
660
=== removed file 'tracking/migrations/__init__.py'
=== removed file 'tracking/models.py'
--- tracking/models.py 2016-12-13 18:28:51 +0000
+++ tracking/models.py 1970-01-01 00:00:00 +0000
@@ -1,133 +0,0 @@
1from datetime import datetime, timedelta
2import logging
3import traceback
4
5from django.contrib.gis.geoip import HAS_GEOIP
6
7if HAS_GEOIP:
8 from django.contrib.gis.geoip import GeoIP, GeoIPException
9
10from django.conf import settings
11from django.contrib.auth.models import User
12from django.db import models
13from django.utils.translation import ugettext, ugettext_lazy as _
14from tracking import utils
15
16USE_GEOIP = getattr(settings, 'TRACKING_USE_GEOIP', False)
17CACHE_TYPE = getattr(settings, 'GEOIP_CACHE_TYPE', 4)
18
19log = logging.getLogger('tracking.models')
20
21
22class VisitorManager(models.Manager):
23
24 def active(self, timeout=None):
25 """Retrieves only visitors who have been active within the timeout
26 period."""
27 if not timeout:
28 timeout = utils.get_timeout()
29
30 now = datetime.now()
31 cutoff = now - timedelta(minutes=timeout)
32
33 return self.get_queryset().filter(last_update__gte=cutoff)
34
35
36class Visitor(models.Model):
37 session_key = models.CharField(max_length=40)
38 ip_address = models.CharField(max_length=20)
39 user = models.ForeignKey(User, null=True)
40 user_agent = models.CharField(max_length=255)
41 referrer = models.CharField(max_length=255)
42 url = models.CharField(max_length=255)
43 page_views = models.PositiveIntegerField(default=0)
44 session_start = models.DateTimeField()
45 last_update = models.DateTimeField()
46
47 objects = VisitorManager()
48
49 def _time_on_site(self):
50 """Attempts to determine the amount of time a visitor has spent on the
51 site based upon their information that's in the database."""
52 if self.session_start:
53 seconds = (self.last_update - self.session_start).seconds
54
55 hours = seconds / 3600
56 seconds -= hours * 3600
57 minutes = seconds / 60
58 seconds -= minutes * 60
59
60 return u'%i:%02i:%02i' % (hours, minutes, seconds)
61 else:
62 return ugettext(u'unknown')
63 time_on_site = property(_time_on_site)
64
65 def _get_geoip_data(self):
66 """Attempts to retrieve MaxMind GeoIP data based upon the visitor's
67 IP."""
68
69 if not HAS_GEOIP or not USE_GEOIP:
70 # go no further when we don't need to
71 log.debug('Bailing out. HAS_GEOIP: %s; TRACKING_USE_GEOIP: %s' % (
72 HAS_GEOIP, USE_GEOIP))
73 return None
74
75 if not hasattr(self, '_geoip_data'):
76 self._geoip_data = None
77 try:
78 gip = GeoIP(cache=CACHE_TYPE)
79 self._geoip_data = gip.city(self.ip_address)
80 except GeoIPException:
81 # don't even bother...
82 log.error('Error getting GeoIP data for IP "%s": %s' %
83 (self.ip_address, traceback.format_exc()))
84
85 return self._geoip_data
86
87 geoip_data = property(_get_geoip_data)
88
89 def _get_geoip_data_json(self):
90 """Cleans out any dirty unicode characters to make the geoip data safe
91 for JSON encoding."""
92 clean = {}
93 if not self.geoip_data:
94 return {}
95
96 for key, value in self.geoip_data.items():
97 clean[key] = utils.u_clean(value)
98 return clean
99
100 geoip_data_json = property(_get_geoip_data_json)
101
102 class Meta:
103 ordering = ('-last_update',)
104 unique_together = ('session_key', 'ip_address',)
105 app_label = 'tracking'
106
107
108class UntrackedUserAgent(models.Model):
109 keyword = models.CharField(_('keyword'), max_length=100, help_text=_(
110 '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.'))
111
112 def __unicode__(self):
113 return self.keyword
114
115 class Meta:
116 ordering = ('keyword',)
117 verbose_name = _('Untracked User-Agent')
118 verbose_name_plural = _('Untracked User-Agents')
119 app_label = 'tracking'
120
121
122class BannedIP(models.Model):
123 ip_address = models.GenericIPAddressField(
124 'IP Address', help_text=_('The IP address that should be banned'))
125
126 def __unicode__(self):
127 return self.ip_address
128
129 class Meta:
130 ordering = ('ip_address',)
131 verbose_name = _('Banned IP')
132 verbose_name_plural = _('Banned IPs')
133 app_label = 'tracking'
1340
=== removed directory 'tracking/templates'
=== removed file 'tracking/templates/base.html'
--- tracking/templates/base.html 2016-06-05 20:58:46 +0000
+++ tracking/templates/base.html 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1<html>
2<head>
3<title>My Site - {% block title %}Welcome{% endblock %}</title>
4<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery-1.4.4.min.js"></script>
5<style type="text/css">
6#active-users-map {
7 height: 400px;
8 width: 600px;
9}
10</style>
11{% block extra-head %}{% endblock %}
12</head>
13<body>
14<h1><a href="/">My Site</a></h1>
15{% block content %}{% endblock %}
16
17{% block footer %}
18<p>Copyright &copy; {% now "Y" %} Me. All rights reserved.</p>
19{% endblock %}
20</body>
21</html>
220
=== removed directory 'tracking/templates/tracking'
=== removed file 'tracking/templates/tracking/_active_users.html'
--- tracking/templates/tracking/_active_users.html 2016-06-05 20:58:46 +0000
+++ tracking/templates/tracking/_active_users.html 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1{% load i18n %}<div class="total">
2 {% blocktrans count active.count as counter %}1 active user{% plural %}{{ counter }} active users{% endblocktrans %}
3</div>
4<div class="guests">
5 {% blocktrans count guests.count as counter %}1 guest user{% plural %}{{ counter }} guest users{% endblocktrans %}
6 {% if user.is_superuser %}
7 {% for visitor in guests %}
8 {% if forloop.first %}<ul class="active-guests">{% endif %}
9 <li>{{ visitor.ip_address }}</li>
10 {% if forloop.last %}</ul>{% endif %}
11 {% endfor %}
12 {% endif %}
13</div>
14<div class="registered">
15 {% blocktrans count registered.count as counter %}1 registered user{% plural %}{{ counter }} registered users{% endblocktrans %}
16 {% for visitor in registered %}
17 {% if forloop.first %}<ul class="active-users">{% endif %}
18 <li>{{ visitor.user }}</li>
19 {% if forloop.last %}</ul>{% endif %}
20 {% endfor %}
21</div>
22\ No newline at end of file0\ No newline at end of file
231
=== removed file 'tracking/templates/tracking/_active_users.js'
--- tracking/templates/tracking/_active_users.js 2016-06-06 18:26:47 +0000
+++ tracking/templates/tracking/_active_users.js 1970-01-01 00:00:00 +0000
@@ -1,155 +0,0 @@
1//<![CDATA[
2var AUmap;
3var markerList = new Array();
4var blurbs = new Array();
5
6$(document).ready(function () {
7 if (GBrowserIsCompatible()) {
8 // set up Google Maps
9 origin = new GLatLng(35.232253,-95.273437);
10 AUmap = new GMap2(document.getElementById("active-users-map"));
11 AUmap.setCenter(origin, 2);
12 AUmap.addControl(new GSmallMapControl());
13 AUmap.addControl(new GMapTypeControl());
14 AUmap.enableScrollWheelZoom();
15
16 // now fetch the active users
17 createMarkers();
18
19 // refresh the markers every 5 seconds or so
20 setInterval('createMarkers()', 5000);
21
22 $('.active-user h3').live('click', function () {
23 var p = $(this).parent().get(0);
24 var num = parseInt(p.id.replace('au-', ''));
25 var marker = markerList[num];
26
27 AUmap.openInfoWindowHtml(marker.getLatLng(), blurbs[num]);
28 AUmap.panTo(marker.getLatLng());
29 });
30 } else {
31 alert('This page requires a modern browser which supports Google Maps!');
32 }
33});
34$(document).unload(GUnload);
35
36$('html').ajaxSend(function(event, xhr, settings) {
37 function getCookie(name) {
38 var cookieValue = null;
39 if (document.cookie && document.cookie != '') {
40 var cookies = document.cookie.split(';');
41 for (var i = 0; i < cookies.length; i++) {
42 var cookie = jQuery.trim(cookies[i]);
43 // Does this cookie string begin with the name we want?
44 if (cookie.substring(0, name.length + 1) == (name + '=')) {
45 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
46 break;
47 }
48 }
49 }
50 return cookieValue;
51 }
52 if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
53 // Only send the token to relative URLs i.e. locally.
54 xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
55 }
56});
57
58function createMarkers() {
59 // Pull back all active users and create markers for them
60 $.ajax({
61 url : "{% url 'tracking-get-active-users' %}",
62 type : 'POST',
63 dataType : 'json',
64 data: {},
65 success : function (json) {
66 var userList = $('#active-users-list');
67
68 $.each(json.users, function (i, user) {
69 var url = '<a href="' + user.url + '">' + user.url + '</a>';
70 if (!markerList[user.id] && user.geoip && user.geoip.city && user.geoip.city != 'None') {
71 // determine which flag to use
72 var img = '<img src="/static/images/flags/' +
73 user.geoip.country_code.toLowerCase() +
74 '.png" class="flag" />';
75
76 var ref = '';
77 if (user.referrer != 'unknown') {
78 ref = '<div><strong>From</strong> <a href="' + user.referrer +
79 '">' + user.referrer + '</a></div>';
80 }
81
82 // come up with some HTML to put in the list
83 var listHtml = '<div id="au-' + user.id + '" ' +
84 'class="active-user location-info"><h3>' +
85 user.geoip.city + '</h3><div>' + img +
86 user.geoip.region + ', ' +
87 user.geoip.country_name + '</div>' +
88 '<div><strong>Viewing</strong> <span id="auu-' + user.id +'">' +
89 url + '</span></div>' +
90 '<div><strong>Using</strong> ' + user.user_agent + '</div>' + ref +
91 '<div><strong>Has viewed</strong> <span id="pv-' + user.id +
92 '">' + user.page_views + '</span> page(s)</div>' +
93 '<div><strong>Updated</strong> <span id="lu-' + user.id + '">' +
94 user.friendly_time + '</span> ago</div></div>';
95 userList.prepend(listHtml);
96
97 // add a marker to the map
98 var point = new GLatLng(user.geoip.latitude,
99 user.geoip.longitude);
100
101 AUmap.addOverlay(createMarker(point, user, img));
102 } else {
103 $('#auu-' + user.id).html(url);
104 $('#lu-' + user.id).html(user.friendly_time);
105 $('#pv-' + user.id).html(user.page_views);
106
107 // Send recently-active users to the top of the list
108 if (user.last_update <= 10) {
109 $('#au-' + user.id).prependTo(userList);
110 }
111 }
112 });
113
114 // Clean up old markers
115 $.each(markerList, function (i, marker) {
116 if (marker) {
117 var inList = false;
118 $.each(json.users, function (j, juser) {
119 if (i == juser.id) {
120 inList = true;
121 return;
122 }
123 });
124 if (!inList) {
125 AUmap.removeOverlay(marker);
126 $('#au-' + i).remove();
127 markerList[i] = null;
128 }
129 }
130 });
131 }
132 });
133}
134
135function createMarker(point, user, img) {
136 // Add a marker overlay to the map and store various bits of info about it
137 var marker = new GMarker(point);
138 marker.value = user.id;
139
140 var myHtml = '<div class="mapOverlay"><h3>' + user.geoip.city + '</h3>';
141 myHtml += '<div>' + img + user.geoip.region;
142 myHtml += ', ' + user.geoip.country_name + '</div></div>';
143
144 // Add a listener to pop up an info box when the mouse goes over a marker
145 GEvent.addListener(marker, "mouseover", function() {
146 AUmap.openInfoWindowHtml(point, myHtml);
147 });
148
149 // Keep track of the marker's data
150 markerList[user.id] = marker;
151 blurbs[user.id] = myHtml;
152
153 return marker;
154}
155//]]>
1560
=== removed file 'tracking/templates/tracking/refresh_active_users.js'
--- tracking/templates/tracking/refresh_active_users.js 2016-06-05 20:58:46 +0000
+++ tracking/templates/tracking/refresh_active_users.js 1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
1var trackingUsers;
2var refreshTimeout = 5000;
3
4$(document).ready(function () {
5 trackingUsers = $('#tracking-active-users');
6 if (trackingUsers) {
7 refreshActiveUsers();
8 }
9});
10
11function refreshActiveUsers() {
12 $.ajax({
13 'url': updateActiveURL,
14 'type': 'GET',
15 'data': {},
16 'dataType': 'json',
17 'error': function (xhr, status, msg) {
18 // do nothing
19 },
20 'success': function (json) {
21 trackingUsers.html(json.users)
22 }
23 });
24
25 setTimeout(refreshActiveUsers, refreshTimeout);
26}
27\ No newline at end of file0\ No newline at end of file
281
=== removed file 'tracking/templates/tracking/visitor_map.html'
--- tracking/templates/tracking/visitor_map.html 2016-06-05 20:58:46 +0000
+++ tracking/templates/tracking/visitor_map.html 1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
1{% extends template %}
2{% load i18n %}
3
4{% block title %}{% trans "Active Visitors Map" %}{% endblock %}
5
6{% block extra-head %}
7{{ block.super }}
8<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={{ GOOGLE_MAPS_KEY }}" type="text/javascript"></script>
9<script type="text/javascript">
10{% include 'tracking/_active_users.js' %}
11</script>
12{% endblock %}
13
14{% block content %}
15<h2>{% trans "Active Users" %}</h2>
16
17<p>
18 {% 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!" %}
19</p>
20
21<div id="active-users-map"></div>
22
23<div id="active-users-list"></div>
24{% endblock %}
250
=== removed directory 'tracking/templatetags'
=== removed file 'tracking/templatetags/__init__.py'
=== removed file 'tracking/templatetags/tracking_tags.py'
--- tracking/templatetags/tracking_tags.py 2016-12-13 18:28:51 +0000
+++ tracking/templatetags/tracking_tags.py 1970-01-01 00:00:00 +0000
@@ -1,53 +0,0 @@
1from django import template
2from tracking.models import Visitor
3
4register = template.Library()
5
6
7class VisitorsOnSite(template.Node):
8 """Injects the number of active users on your site as an integer into the
9 context."""
10
11 def __init__(self, varname, same_page=False):
12 self.varname = varname
13 self.same_page = same_page
14
15 def render(self, context):
16 if self.same_page:
17 try:
18 request = context['request']
19 count = Visitor.objects.active().filter(url=request.path).count()
20 except KeyError:
21 raise template.TemplateSyntaxError(
22 "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.")
23 else:
24 count = Visitor.objects.active().count()
25
26 context[self.varname] = count
27 return ''
28
29
30def visitors_on_site(parser, token):
31 """Determines the number of active users on your site and puts it into the
32 context."""
33 try:
34 tag, a, varname = token.split_contents()
35 except ValueError:
36 raise template.TemplateSyntaxError(
37 'visitors_on_site usage: {% visitors_on_site as visitors %}')
38
39 return VisitorsOnSite(varname)
40register.tag(visitors_on_site)
41
42
43def visitors_on_page(parser, token):
44 """Determines the number of active users on the same page and puts it into
45 the context."""
46 try:
47 tag, a, varname = token.split_contents()
48 except ValueError:
49 raise template.TemplateSyntaxError(
50 'visitors_on_page usage: {% visitors_on_page as visitors %}')
51
52 return VisitorsOnSite(varname, same_page=True)
53register.tag(visitors_on_page)
540
=== removed file 'tracking/urls.py'
--- tracking/urls.py 2016-12-13 18:28:51 +0000
+++ tracking/urls.py 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1from django.conf.urls.defaults import *
2from django.conf import settings
3from tracking import views
4
5urlpatterns = patterns('',
6 url(r'^refresh/$', views.update_active_users,
7 name='tracking-refresh-active-users'),
8 url(r'^refresh/json/$', views.get_active_users,
9 name='tracking-get-active-users'),
10 )
11
12if getattr(settings, 'TRACKING_USE_GEOIP', False):
13 urlpatterns += patterns('',
14 url(r'^map/$', views.display_map,
15 name='tracking-visitor-map'),
16 )
170
=== removed file 'tracking/utils.py'
--- tracking/utils.py 2016-12-13 18:28:51 +0000
+++ tracking/utils.py 1970-01-01 00:00:00 +0000
@@ -1,74 +0,0 @@
1from django.conf import settings
2import re
3import unicodedata
4
5# this is not intended to be an all-knowing IP address regex
6IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
7
8
9def get_ip(request):
10 """Retrieves the remote IP address from the request data.
11
12 If the user is
13 behind a proxy, they may have a comma-separated list of IP addresses, so
14 we need to account for that. In such a case, only the first IP in the
15 list will be retrieved. Also, some hosts that use a proxy will put the
16 REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
17 IP from the proper place.
18
19 """
20
21 # if neither header contain a value, just use local loopback
22 ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
23 request.META.get('REMOTE_ADDR', '127.0.0.1'))
24 if ip_address:
25 # make sure we have one and only one IP
26 try:
27 ip_address = IP_RE.match(ip_address)
28 if ip_address:
29 ip_address = ip_address.group(0)
30 else:
31 # no IP, probably from some dirty proxy or other device
32 # throw in some bogus IP
33 ip_address = '10.0.0.1'
34 except IndexError:
35 pass
36
37 return ip_address
38
39
40def get_timeout():
41 """Gets any specified timeout from the settings file, or use 10 minutes by
42 default."""
43 return getattr(settings, 'TRACKING_TIMEOUT', 10)
44
45
46def get_cleanup_timeout():
47 """
48 Gets any specified visitor clean-up timeout from the settings file, or
49 use 24 hours by default
50 """
51 return getattr(settings, 'TRACKING_CLEANUP_TIMEOUT', 24)
52
53
54def u_clean(s):
55 """A strange attempt at cleaning up unicode."""
56
57 uni = ''
58 try:
59 # try this first
60 uni = str(s).decode('iso-8859-1')
61 except UnicodeDecodeError:
62 try:
63 # try utf-8 next
64 uni = str(s).decode('utf-8')
65 except UnicodeDecodeError:
66 # last resort method... one character at a time (ugh)
67 if s and type(s) in (str, unicode):
68 for c in s:
69 try:
70 uni += unicodedata.normalize('NFKC', unicode(c))
71 except UnicodeDecodeError:
72 uni += '-'
73
74 return uni.encode('ascii', 'xmlcharrefreplace')
750
=== removed file 'tracking/views.py'
--- tracking/views.py 2016-12-13 18:28:51 +0000
+++ tracking/views.py 1970-01-01 00:00:00 +0000
@@ -1,115 +0,0 @@
1from datetime import datetime
2import logging
3import traceback
4
5from django.conf import settings
6from django.http import Http404, HttpResponse
7from django.shortcuts import render_to_response
8from django.template import RequestContext, Context, loader
9from django.utils.simplejson import JSONEncoder
10from django.utils.translation import ungettext
11from django.views.decorators.cache import never_cache
12from tracking.models import Visitor
13from tracking.utils import u_clean as uc
14
15DEFAULT_TRACKING_TEMPLATE = getattr(settings, 'DEFAULT_TRACKING_TEMPLATE',
16 'tracking/visitor_map.html')
17log = logging.getLogger('tracking.views')
18
19
20def update_active_users(request):
21 """Returns a list of all active users."""
22 if request.is_ajax():
23 active = Visitor.objects.active()
24 user = getattr(request, 'user', None)
25
26 info = {
27 'active': active,
28 'registered': active.filter(user__isnull=False),
29 'guests': active.filter(user__isnull=True),
30 'user': user
31 }
32
33 # render the list of active users
34 t = loader.get_template('tracking/_active_users.html')
35 c = Context(info)
36 users = {'users': t.render(c)}
37
38 return HttpResponse(content=JSONEncoder().encode(users))
39
40 # if the request was not made via AJAX, raise a 404
41 raise Http404
42
43
44@never_cache
45def get_active_users(request):
46 """Retrieves a list of active users which is returned as plain JSON for
47 easier manipulation with JavaScript."""
48 if request.is_ajax():
49 active = Visitor.objects.active().reverse()
50 now = datetime.now()
51
52 # we don't put the session key or IP address here for security reasons
53 try:
54 data = {'users': [{
55 'id': v.id,
56 #'user': uc(v.user),
57 'user_agent': uc(v.user_agent),
58 'referrer': uc(v.referrer),
59 'url': uc(v.url),
60 'page_views': v.page_views,
61 'geoip': v.geoip_data_json,
62 'last_update': (now - v.last_update).seconds,
63 'friendly_time': ', '.join(friendly_time((now - v.last_update).seconds)),
64 } for v in active]}
65 except:
66 log.error('There was a problem putting all of the visitor data together:\n%s\n\n%s' % (
67 traceback.format_exc(), locals()))
68 return HttpResponse(content='{}', mimetype='text/javascript')
69
70 response = HttpResponse(content=JSONEncoder().encode(data),
71 mimetype='text/javascript')
72 response['Content-Length'] = len(response.content)
73
74 return response
75
76 # if the request was not made via AJAX, raise a 404
77 raise Http404
78
79
80def friendly_time(last_update):
81 minutes = last_update / 60
82 seconds = last_update % 60
83
84 friendly_time = []
85 if minutes > 0:
86 friendly_time.append(ungettext(
87 '%(minutes)i minute',
88 '%(minutes)i minutes',
89 minutes
90 ) % {'minutes': minutes})
91 if seconds > 0:
92 friendly_time.append(ungettext(
93 '%(seconds)i second',
94 '%(seconds)i seconds',
95 seconds
96 ) % {'seconds': seconds})
97
98 return friendly_time or 0
99
100
101def display_map(request, template_name=DEFAULT_TRACKING_TEMPLATE,
102 extends_template='base.html'):
103 """Displays a map of recently active users.
104
105 Requires a Google Maps API key and GeoIP in order to be most
106 effective.
107
108 """
109
110 GOOGLE_MAPS_KEY = getattr(settings, 'GOOGLE_MAPS_KEY', None)
111
112 return render_to_response(template_name,
113 {'GOOGLE_MAPS_KEY': GOOGLE_MAPS_KEY,
114 'template': extends_template},
115 context_instance=RequestContext(request))
1160
=== modified file 'urls.py'
--- urls.py 2017-12-23 09:15:02 +0000
+++ urls.py 2018-04-19 17:49:06 +0000
@@ -4,7 +4,6 @@
4from django.contrib import admin4from django.contrib import admin
5admin.autodiscover()5admin.autodiscover()
66
7from mainpage.views import mainpage
8from news.feeds import NewsPostsFeed7from news.feeds import NewsPostsFeed
9from django.views.generic.base import RedirectView8from django.views.generic.base import RedirectView
10from django.views.generic import TemplateView9from django.views.generic import TemplateView
@@ -12,6 +11,7 @@
12from registration.backends.hmac.views import RegistrationView11from registration.backends.hmac.views import RegistrationView
13from mainpage.forms import RegistrationWithCaptchaForm12from mainpage.forms import RegistrationWithCaptchaForm
1413
14
15urlpatterns = [15urlpatterns = [
16 # Creating a sitemap.xml16 # Creating a sitemap.xml
17 url(r'^sitemap\.xml/', include('sitemap_urls')),17 url(r'^sitemap\.xml/', include('sitemap_urls')),
@@ -28,13 +28,14 @@
28 url(r'^accounts/', include('registration.backends.hmac.urls')),28 url(r'^accounts/', include('registration.backends.hmac.urls')),
29 url('^', include('django.contrib.auth.urls')),29 url('^', include('django.contrib.auth.urls')),
3030
31 # Feed for Mainpage31 # Feed for news
32 url(r'^feeds/news/$', NewsPostsFeed()),32 url(r'^feeds/news/$', NewsPostsFeed()),
3333
34 # Formerly 3rd party34 # Formerly 3rd party
35 url(r'^notification/', include('notification.urls')),35 url(r'^notification/', include('notification.urls')),
36 36
37 url(r'^messages/', include('django_messages.urls')),37 url(r'^messages/', include('django_messages_wl.urls')),
38
38 url(r'^threadedcomments/', include('threadedcomments.urls')),39 url(r'^threadedcomments/', include('threadedcomments.urls')),
3940
40 # Redirect old urls to docs to docs/wl41 # Redirect old urls to docs to docs/wl
@@ -47,15 +48,9 @@
47 url(r'^forum/', include('pybb.urls')),48 url(r'^forum/', include('pybb.urls')),
4849
49 # WL specific:50 # WL specific:
50 url(r'^$', mainpage, name='mainpage'),51 url(r'^', include('mainpage.urls')),
51 url(r'^locale/$', 'mainpage.views.view_locale'),
52 url(r'^changelog/$', 'mainpage.views.changelog', name='changelog'),
53 url(r'^developers/$', 'mainpage.views.developers', name='developers'),
54 url(r'^legal_notice/$', 'mainpage.views.legal_notice', name='legal_notice'),
55 url(r'^legal_notice_thanks/$', 'mainpage.views.legal_notice_thanks',
56 name='legal_notice_thanks'),
57 url(r'^help/(?P<path>.*)', RedirectView.as_view(url='/encyclopedia/%(path)s',52 url(r'^help/(?P<path>.*)', RedirectView.as_view(url='/encyclopedia/%(path)s',
58 permanent=True)), # to not break old links53 permanent=True)), # to not break old links
59 url(r'^encyclopedia/', include('wlhelp.urls')),54 url(r'^encyclopedia/', include('wlhelp.urls')),
60 url(r'^webchat/', include('wlwebchat.urls')),55 url(r'^webchat/', include('wlwebchat.urls')),
61 url(r'^images/', include('wlimages.urls')),56 url(r'^images/', include('wlimages.urls')),
6257
=== modified file 'wiki/__init__.py'
--- wiki/__init__.py 2016-02-26 19:00:22 +0000
+++ wiki/__init__.py 2018-04-19 17:49:06 +0000
@@ -1,3 +0,0 @@
1#from wiki.templatetags.restructuredtext import restructuredtext
2
3#restructuredtext('`Available as 1.0 since September, 2007 <http://www.modwsgi.org/>`')
40
=== added file 'wiki/apps.py'
--- wiki/apps.py 1970-01-01 00:00:00 +0000
+++ wiki/apps.py 2018-04-19 17:49:06 +0000
@@ -0,0 +1,12 @@
1from django.apps import AppConfig
2from django.db.models import signals
3
4
5class WikiConfig(AppConfig):
6
7 name = 'wiki'
8 verbose_name = 'Wiki'
9
10 def ready(self):
11 from wiki.management import create_notice_types
12 signals.post_migrate.connect(create_notice_types, sender=self)
013
=== modified file 'wiki/management.py'
--- wiki/management.py 2017-04-26 19:54:03 +0000
+++ wiki/management.py 2018-04-19 17:49:06 +0000
@@ -1,11 +1,11 @@
1from django.db.models import signals
21
3from django.utils.translation import ugettext_noop as _2from django.utils.translation import ugettext_noop as _
43
5try:4try:
6 from notification import models as notification5 from notification import models as notification
76
8 def create_notice_types(app, created_models, verbosity, **kwargs):7 def create_notice_types(sender, **kwargs):
8 print("Creating noticetypes for wiki ...")
9 notification.create_notice_type('wiki_revision_reverted',9 notification.create_notice_type('wiki_revision_reverted',
10 _('Article Revision Reverted'),10 _('Article Revision Reverted'),
11 _('your revision has been reverted'))11 _('your revision has been reverted'))
@@ -13,9 +13,5 @@
13 _('Observed Article Changed'),13 _('Observed Article Changed'),
14 _('an article you observe has changed'))14 _('an article you observe has changed'))
1515
16 # TODO (Franku): post_syncdb is deprecated since Django 1.7
17 # See: https://docs.djangoproject.com/en/1.8/ref/signals/#post-syncdb
18 signals.post_syncdb.connect(create_notice_types,
19 sender=notification)
20except ImportError:16except ImportError:
21 print 'Skipping creation of NoticeTypes as notification app not found'17 print 'Skipping creation of NoticeTypes as notification app not found'
2218
=== modified file 'wiki/models.py'
--- wiki/models.py 2017-12-10 13:18:47 +0000
+++ wiki/models.py 2018-04-19 17:49:06 +0000
@@ -1,5 +1,5 @@
1from datetime import datetime1from datetime import datetime
2from django.core.urlresolvers import reverse2from django.urls import reverse
33
4# Google Diff Match Patch library4# Google Diff Match Patch library
5# http://code.google.com/p/google-diff-match-patch5# http://code.google.com/p/google-diff-match-patch
66
=== removed file 'wiki/static_urls.py'
--- wiki/static_urls.py 2016-12-13 18:28:51 +0000
+++ wiki/static_urls.py 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1from django.conf.urls import *
2from django.conf import settings
3
4
5urlpatterns = patterns('',
6 url(r'^site_media/(?P<path>.*)$',
7 'django.views.static.serve',
8 {'document_root': settings.STATIC_MEDIA_PATH},
9 name='wiki_static_media'),
10 )
110
=== modified file 'wiki/templatetags/switchcase.py'
--- wiki/templatetags/switchcase.py 2016-12-13 18:28:51 +0000
+++ wiki/templatetags/switchcase.py 2018-04-19 17:49:06 +0000
@@ -72,8 +72,8 @@
72 # Resolve the value; if it's a non-existant variable don't even bother72 # Resolve the value; if it's a non-existant variable don't even bother
73 # checking the values of the cases since they'll never match.73 # checking the values of the cases since they'll never match.
74 try:74 try:
75 value = template.resolve_variable(self.value, context)75 value = template.Variable(self.value).resolve(context)
76 except VariableDoesNotExist:76 except template.VariableDoesNotExist:
77 return ''77 return ''
7878
79 # Check each case, and if it matches return the rendered content79 # Check each case, and if it matches return the rendered content
@@ -99,8 +99,8 @@
9999
100 """100 """
101 try:101 try:
102 return template.resolve_variable(self.value, context) == otherval102 return template.Variable(self.value).resolve(context) == otherval
103 except VariableDoesNotExist:103 except template.VariableDoesNotExist:
104 # If the variable doesn't exist, it doesn't equal anything.104 # If the variable doesn't exist, it doesn't equal anything.
105 return False105 return False
106106
107107
=== modified file 'wiki/templatetags/wiki_extras.py'
--- wiki/templatetags/wiki_extras.py 2017-02-24 20:12:28 +0000
+++ wiki/templatetags/wiki_extras.py 2018-04-19 17:49:06 +0000
@@ -52,18 +52,3 @@
52 'content': getattr(article, content_attr),52 'content': getattr(article, content_attr),
53 'markup': getattr(article, markup_attr)53 'markup': getattr(article, markup_attr)
54 }54 }
55
56
57@register.inclusion_tag('wiki/article_teaser.html')
58def show_teaser(article):
59 """Show a teaser box for the summary of the article."""
60 return {'article': article}
61
62
63@register.inclusion_tag('wiki/wiki_title.html')
64def wiki_title(group):
65 """Display a <h1> title for the wiki, with a link to the group main
66 page."""
67 return {'group_name': group.name,
68 'group_type': group._meta.verbose_name.title(),
69 'group_url': group.get_absolute_url()}
7055
=== modified file 'wiki/views.py'
--- wiki/views.py 2017-12-10 19:04:12 +0000
+++ wiki/views.py 2018-04-19 17:49:06 +0000
@@ -5,10 +5,10 @@
5from django.conf import settings5from django.conf import settings
6from django.core.cache import cache6from django.core.cache import cache
7from django.template import RequestContext7from django.template import RequestContext
8from django.core.urlresolvers import reverse8from django.urls import reverse
9from django.http import (Http404, HttpResponseRedirect,9from django.http import (Http404, HttpResponseRedirect,
10 HttpResponseNotAllowed, HttpResponse, HttpResponseForbidden)10 HttpResponseNotAllowed, HttpResponse, HttpResponseForbidden)
11from django.shortcuts import get_object_or_404, render_to_response, redirect11from django.shortcuts import get_object_or_404, render, redirect
12from django.contrib.contenttypes.models import ContentType12from django.contrib.contenttypes.models import ContentType
13from django.contrib import messages13from django.contrib import messages
14from wiki.forms import ArticleForm14from wiki.forms import ArticleForm
@@ -155,9 +155,8 @@
155 if extra_context is not None:155 if extra_context is not None:
156 template_params.update(extra_context)156 template_params.update(extra_context)
157157
158 return render_to_response('/'.join([template_dir, template_name]),158 return render(request, '/'.join([template_dir, template_name]),
159 template_params,159 template_params,)
160 context_instance=RequestContext(request))
161 return HttpResponseNotAllowed(['GET'])160 return HttpResponseNotAllowed(['GET'])
162161
163162
@@ -224,9 +223,8 @@
224 if extra_context is not None:223 if extra_context is not None:
225 template_params.update(extra_context)224 template_params.update(extra_context)
226225
227 return render_to_response('/'.join([template_dir, template_name]),226 return render(request, '/'.join([template_dir, template_name]),
228 template_params,227 template_params,)
229 context_instance=RequestContext(request))
230 return HttpResponseNotAllowed(['GET'])228 return HttpResponseNotAllowed(['GET'])
231229
232230
@@ -279,7 +277,7 @@
279 form.cache_old_content()277 form.cache_old_content()
280 if form.is_valid():278 if form.is_valid():
281279
282 if request.user.is_authenticated():280 if request.user.is_authenticated:
283 form.editor = request.user281 form.editor = request.user
284282
285 if ((article is None) and (group_slug is not None)):283 if ((article is None) and (group_slug is not None)):
@@ -337,9 +335,8 @@
337 if extra_context is not None:335 if extra_context is not None:
338 template_params.update(extra_context)336 template_params.update(extra_context)
339337
340 return render_to_response('/'.join([template_dir, template_name]),338 return render(request, '/'.join([template_dir, template_name]),
341 template_params,339 template_params,)
342 context_instance=RequestContext(request))
343340
344341
345def view_changeset(request, title, revision,342def view_changeset(request, title, revision,
@@ -403,9 +400,8 @@
403 if extra_context is not None:400 if extra_context is not None:
404 template_params.update(extra_context)401 template_params.update(extra_context)
405402
406 return render_to_response('/'.join([template_dir, template_name]),403 return render(request, '/'.join([template_dir, template_name]),
407 template_params,404 template_params,)
408 context_instance=RequestContext(request))
409 return HttpResponseNotAllowed(['GET'])405 return HttpResponseNotAllowed(['GET'])
410406
411407
@@ -449,9 +445,8 @@
449 if extra_context is not None:445 if extra_context is not None:
450 template_params.update(extra_context)446 template_params.update(extra_context)
451447
452 return render_to_response('/'.join([template_dir, template_name]),448 return render(request, '/'.join([template_dir, template_name]),
453 template_params,449 template_params,)
454 context_instance=RequestContext(request))
455450
456 return HttpResponseNotAllowed(['GET'])451 return HttpResponseNotAllowed(['GET'])
457452
@@ -497,7 +492,7 @@
497 art = Article.objects.exclude(pk=article.pk).get(title=old_title)492 art = Article.objects.exclude(pk=article.pk).get(title=old_title)
498 except Article.DoesNotExist:493 except Article.DoesNotExist:
499 # No existing article found -> reverting possible494 # No existing article found -> reverting possible
500 if request.user.is_authenticated():495 if request.user.is_authenticated:
501 article.revert_to(revision, get_real_ip(request), request.user)496 article.revert_to(revision, get_real_ip(request), request.user)
502 else:497 else:
503 article.revert_to(revision, get_real_ip(request))498 article.revert_to(revision, get_real_ip(request))
@@ -541,9 +536,8 @@
541 if extra_context is not None:536 if extra_context is not None:
542 template_params.update(extra_context)537 template_params.update(extra_context)
543538
544 return render_to_response('/'.join([template_dir, template_name]),539 return render(request, '/'.join([template_dir, template_name]),
545 template_params,540 template_params,)
546 context_instance=RequestContext(request))
547 return HttpResponseNotAllowed(['GET'])541 return HttpResponseNotAllowed(['GET'])
548542
549543
@@ -680,6 +674,5 @@
680 context = {'found_links': found_links,674 context = {'found_links': found_links,
681 'found_old_links': found_old_links,675 'found_old_links': found_old_links,
682 'name': title}676 'name': title}
683 return render_to_response('wiki/backlinks.html',677 return render(request, 'wiki/backlinks.html',
684 context,678 context,)
685 context_instance=RequestContext(request))
686679
=== modified file 'wl_utils.py'
--- wl_utils.py 2016-12-13 18:28:51 +0000
+++ wl_utils.py 2018-04-19 17:49:06 +0000
@@ -13,23 +13,15 @@
13# Initial implemenation details about AutoOneToOneField:13# Initial implemenation details about AutoOneToOneField:
14# http://softwaremaniacs.org/blog/2007/03/07/auto-one-to-one-field/14# http://softwaremaniacs.org/blog/2007/03/07/auto-one-to-one-field/
15#15#
16# This doesn't worked anymore with django 1.816
17# changed according to:17
18# https://github.com/skorokithakis/django-annoying/issues/3618from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor
19
20
21# SingleRelatedObjectDescriptor gets renamed with Django 1.9
22try:
23 from django.db.models.fields.related import SingleRelatedObjectDescriptor
24except ImportError:
25 from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor
2619
27from django.db.models import OneToOneField20from django.db.models import OneToOneField
28from django.db.models.fields.related import SingleRelatedObjectDescriptor
29from django.db import models21from django.db import models
3022
3123
32class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):24class AutoReverseOneToOneDescriptor(ReverseOneToOneDescriptor):
33 """The descriptor that handles the object creation for an25 """The descriptor that handles the object creation for an
34 AutoOneToOneField."""26 AutoOneToOneField."""
3527
@@ -37,7 +29,7 @@
37 model = getattr(self.related, 'related_model', self.related.model)29 model = getattr(self.related, 'related_model', self.related.model)
3830
39 try:31 try:
40 return (super(AutoSingleRelatedObjectDescriptor, self)32 return (super(AutoReverseOneToOneDescriptor, self)
41 .__get__(instance, instance_type))33 .__get__(instance, instance_type))
42 except model.DoesNotExist:34 except model.DoesNotExist:
43 obj = model(**{self.related.field.name: instance})35 obj = model(**{self.related.field.name: instance})
@@ -47,7 +39,7 @@
47 # Don't return obj directly, otherwise it won't be added39 # Don't return obj directly, otherwise it won't be added
48 # to Django's cache, and the first 2 calls to obj.relobj40 # to Django's cache, and the first 2 calls to obj.relobj
49 # will return 2 different in-memory objects41 # will return 2 different in-memory objects
50 return (super(AutoSingleRelatedObjectDescriptor, self)42 return (super(AutoReverseOneToOneDescriptor, self)
51 .__get__(instance, instance_type))43 .__get__(instance, instance_type))
5244
5345
@@ -57,4 +49,4 @@
5749
58 def contribute_to_related_class(self, cls, related):50 def contribute_to_related_class(self, cls, related):
59 setattr(cls, related.get_accessor_name(),51 setattr(cls, related.get_accessor_name(),
60 AutoSingleRelatedObjectDescriptor(related))52 AutoReverseOneToOneDescriptor(related))
6153
=== modified file 'wlggz/views.py'
--- wlggz/views.py 2016-12-13 18:28:51 +0000
+++ wlggz/views.py 2018-04-19 17:49:06 +0000
@@ -1,10 +1,9 @@
1# Create your views here.1# Create your views here.
22
33
4from django.core.urlresolvers import reverse4from django.urls import reverse
5from django.contrib.auth.decorators import login_required5from django.contrib.auth.decorators import login_required
6from django.shortcuts import render_to_response
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches