summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rw-r--r--askbot/__init__.py2
-rw-r--r--askbot/conf/email.py38
-rw-r--r--askbot/const/__init__.py5
-rw-r--r--askbot/deps/django_authopenid/views.py6
-rw-r--r--askbot/doc/source/contributors.rst2
-rw-r--r--askbot/doc/source/index.rst2
-rw-r--r--askbot/doc/source/initial-configuration.rst3
-rw-r--r--askbot/doc/source/live-settings.rst21
-rw-r--r--askbot/doc/source/management-commands.rst51
-rw-r--r--askbot/doc/source/optional-modules.rst26
-rw-r--r--askbot/doc/source/sending-email-to-askbot.rst33
-rw-r--r--askbot/management/commands/post_emailed_questions.py17
-rw-r--r--askbot/management/commands/send_email_alerts.py98
-rw-r--r--askbot/management/commands/send_unanswered_question_reminders.py97
-rw-r--r--askbot/models/__init__.py45
-rw-r--r--askbot/models/question.py36
-rw-r--r--askbot/setup_templates/settings.py3
-rw-r--r--askbot/skins/default/media/js/live_search.js62
-rwxr-xr-xaskbot/skins/default/media/style/style.css2
-rw-r--r--askbot/skins/default/templates/answer_edit.html2
-rw-r--r--askbot/skins/default/templates/authopenid/changeemail.html2
-rw-r--r--askbot/skins/default/templates/authopenid/complete.html6
-rw-r--r--askbot/skins/default/templates/authopenid/signin.html4
-rw-r--r--askbot/skins/default/templates/authopenid/signup_with_password.html4
-rw-r--r--askbot/skins/default/templates/avatar/add.html2
-rw-r--r--askbot/skins/default/templates/avatar/change.html4
-rw-r--r--askbot/skins/default/templates/avatar/confirm_delete.html2
-rw-r--r--askbot/skins/default/templates/blocks/ask_form.html2
-rw-r--r--askbot/skins/default/templates/close.html2
-rw-r--r--askbot/skins/default/templates/feedback.html2
-rw-r--r--askbot/skins/default/templates/import_data.html2
-rw-r--r--askbot/skins/default/templates/macros.html4
-rw-r--r--askbot/skins/default/templates/main_page/tab_bar.html2
-rw-r--r--askbot/skins/default/templates/question.html2
-rw-r--r--askbot/skins/default/templates/question_edit.html2
-rw-r--r--askbot/skins/default/templates/question_retag.html2
-rw-r--r--askbot/skins/default/templates/reopen.html2
-rw-r--r--askbot/skins/default/templates/subscribe_for_tags.html2
-rw-r--r--askbot/skins/default/templates/user_profile/user_edit.html2
-rw-r--r--askbot/skins/default/templates/user_profile/user_email_subscriptions.html2
-rw-r--r--askbot/skins/default/templates/user_profile/user_moderate.html6
-rw-r--r--askbot/tests/email_alert_tests.py29
-rw-r--r--askbot/views/avatar_views.py4
-rw-r--r--askbot/views/commands.py4
-rw-r--r--askbot/views/meta.py2
-rw-r--r--askbot/views/readers.py2
-rw-r--r--askbot/views/users.py6
-rw-r--r--askbot/views/writers.py10
-rw-r--r--setup.py2
50 files changed, 496 insertions, 173 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 05201977..709b9437 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,5 +13,6 @@ exclude urls.py
exclude askbot/upfiles/*.*
recursive-exclude avatar *
recursive-exclude adzone *
+recursive-exclude categories *
recursive-exclude env
recursive-exclude .tox
diff --git a/askbot/__init__.py b/askbot/__init__.py
index 8d907571..fce9e7a5 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -11,7 +11,7 @@ import logging
from askbot import patches
from askbot.deployment.assertions import assert_package_compatibility
-VERSION = (0, 6, 76)
+VERSION = (0, 6, 77)
#necessary for interoperability of django and coffin
assert_package_compatibility()
diff --git a/askbot/conf/email.py b/askbot/conf/email.py
index f18a554d..953aac3d 100644
--- a/askbot/conf/email.py
+++ b/askbot/conf/email.py
@@ -57,6 +57,44 @@ settings.register(
settings.register(
livesettings.BooleanValue(
EMAIL,
+ 'ENABLE_UNANSWERED_REMINDERS',
+ default = False,
+ description = _('Send periodic reminders about unanswered questions'),
+ help_text = _(
+ 'NOTE: in order to use this feature, it is necessary to '
+ 'run the management command "send_unanswered_question_reminders" '
+ '(for example, via a cron job - with an appropriate frequency) '
+ 'and an IMAP server with a dedicated inbox must be configured '
+ )
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ EMAIL,
+ 'DAYS_BEFORE_SENDING_UNANSWERED_REMINDER',
+ default = 1,
+ description = _(
+ 'Days before starting to send reminders about unanswered questions'
+ ),
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ EMAIL,
+ 'UNANSWERED_REMINDER_FREQUENCY',
+ default = 1,
+ description = _(
+ 'How often to send unanswered question reminders '
+ '(in days between the reminders sent).'
+ )
+ )
+)
+
+settings.register(
+ livesettings.BooleanValue(
+ EMAIL,
'EMAIL_VALIDATION',
default=False,
hidden=True,
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 5a0781f0..ea9236b6 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -105,6 +105,7 @@ TYPE_ACTIVITY_FAVORITE=16
TYPE_ACTIVITY_USER_FULL_UPDATED = 17
TYPE_ACTIVITY_EMAIL_UPDATE_SENT = 18
TYPE_ACTIVITY_MENTION = 19
+TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT = 20
#TYPE_ACTIVITY_EDIT_QUESTION=17
#TYPE_ACTIVITY_EDIT_ANSWER=18
@@ -128,6 +129,10 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
(TYPE_ACTIVITY_EMAIL_UPDATE_SENT, _('email update sent to user')),
+ (
+ TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT,
+ _('reminder about unanswered questions sent'),
+ ),
(TYPE_ACTIVITY_MENTION, _('mentioned in the post')),
)
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 2ec423c7..14def205 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -40,6 +40,7 @@ from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
+from django.views.decorators import csrf
from django.utils.encoding import smart_unicode
from django.utils.html import escape
from django.utils.translation import ugettext as _
@@ -258,6 +259,7 @@ def complete_oauth_signin(request):
return HttpResponseRedirect(next_url)
#@not_authenticated
+@csrf.csrf_protect
def signin(
request,
newquestion = False,#todo: not needed
@@ -447,6 +449,7 @@ def signin(
view_subtype = view_subtype
)
+@csrf.csrf_protect
def show_signin_view(
request,
login_form = None,
@@ -690,6 +693,7 @@ def finalize_generic_signin(
return HttpResponseRedirect(redirect_url)
@not_authenticated
+@csrf.csrf_protect
def register(request, login_provider_name=None, user_identifier=None):
"""
this function is used via it's own url with request.method=POST
@@ -833,6 +837,7 @@ def signin_failure(request, message):
@not_authenticated
@decorators.valid_password_login_provider_required
+@csrf.csrf_protect
def signup_with_password(request):
"""Create a password-protected account
template: authopenid/signup_with_password.html
@@ -1024,6 +1029,7 @@ def send_new_email_key(user,nomessage=False):
set_email_validation_message(user)
@login_required
+@csrf.csrf_protect
def send_email_key(request):
"""
url = /email/sendkey/
diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst
index 67d8fdeb..c5887f42 100644
--- a/askbot/doc/source/contributors.rst
+++ b/askbot/doc/source/contributors.rst
@@ -11,10 +11,12 @@ Programming
* Mike Chen & Sailing Cai - original authors of CNPROG forum
* Evgeny Fadeev - founder of askbot
* Benoit Lavine (with Windriver Software, Inc.)
+* Alex Robbins (celery support)
* Adolfo Fitoria
* Andrei Mamoutkine
* Ramiro Morales (with Machinalis)
* Andy Knotts
+* Jeff Madynski
Translations
------------
diff --git a/askbot/doc/source/index.rst b/askbot/doc/source/index.rst
index a7fb8122..981af741 100644
--- a/askbot/doc/source/index.rst
+++ b/askbot/doc/source/index.rst
@@ -21,6 +21,8 @@ at the forum_ or by email at admin@askbot.org
Deploy on a webserver <deployment>
Import data (StackExchange) <import-data>
Appendix A: Maintenance procedures <management-commands>
+ Appendix B: Sending email to askbot <sending-email-to-askbot>
+ Apperdix C: Optional modules <optional-modules>
Contributors <contributors>
Some background information: Askbot is written in Python on top of the Django platform.
diff --git a/askbot/doc/source/initial-configuration.rst b/askbot/doc/source/initial-configuration.rst
index 3e15dde4..cb871a7e 100644
--- a/askbot/doc/source/initial-configuration.rst
+++ b/askbot/doc/source/initial-configuration.rst
@@ -31,8 +31,9 @@ Within settings.py, at the very minimum you will need to provide correct values
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
+ CSRF_COOKIE_DOMAIN = ''#e.g. example.com (localhost/IP address for tests)
-within single quotes - login credentials to your mysql database.
+within single quotes - login credentials to your database.
.. _urls.py: http://github.com/ASKBOT/askbot-devel/blob/master/askbot/setup_templates/urls.py
.. _settings.py: http://github.com/ASKBOT/askbot-devel/blob/master/askbot/setup_templates/settings.py
diff --git a/askbot/doc/source/live-settings.rst b/askbot/doc/source/live-settings.rst
new file mode 100644
index 00000000..e154a257
--- /dev/null
+++ b/askbot/doc/source/live-settings.rst
@@ -0,0 +1,21 @@
+.. _live-settings:
+=============
+Live settings
+=============
+
+Many of the configuration settings in askbot are accessible
+to the site administators via link "settings" in the site header.
+
+Any change to the "live settings" will be reflected on the site
+immediately.
+
+No-one but the site administrators can change those settings.
+
+.. note::
+ Any user can be turned into an administrator via running a command.
+
+ python manage.py add_admin <user_id>
+
+ At the moment this command is not available from the web-interface
+ but this will be fixed in the future.
+
diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst
index 188654bf..a91c0286 100644
--- a/askbot/doc/source/management-commands.rst
+++ b/askbot/doc/source/management-commands.rst
@@ -73,22 +73,41 @@ The bulk of the management commands fall into this group and will probably be th
| | marked as deleted. |
+---------------------------------+-------------------------------------------------------------+
-Batch jobs
-==========
-
-Batch jobs are those that should be run periodically. A program called `cron` can run these commands at the specified times (please look up futher information about `cron` elsewhere).
-
-+----------------------+-------------------------------------------------------------+
-| command | purpose |
-+======================+=============================================================+
-| `send_email_alerts` | Dispatches email alerts to the users according to |
-| | their subscription settings. This command does not |
-| | send iinstant" alerts because those are sent automatically |
-| | and do not require a separate command. |
-| | The most frequent alert setting that can be served by this |
-| | command is "daily", therefore running `send_email_alerts` |
-| | more than twice a day is not necessary. |
-+----------------------+-------------------------------------------------------------+
+.. _email-related-commands:
+
+Email-related commands
+======================
+
+These commands deal with the periodic tasks related to sending and receiving email by askbot.
+A UNIX program called `cron` can run these commands at the specified times
+(please look up futher information about `cron` elsewhere).
+
+Any configurable options, related to these commands are accessible via "Email" section of the
+:ref:`live settings <live-settings>`.
+
++-------------------------------------+-------------------------------------------------------------+
+| command | purpose |
++=====================================+=============================================================+
+| `send_email_alerts` | Dispatches email alerts to the users according to |
+| | their subscription settings. This command does not |
+| | send instant" alerts because those are sent automatically |
+| | and do not require a separate command. |
+| | The most frequent alert setting that can be served by this |
+| | command is "daily", therefore running `send_email_alerts` |
+| | more than twice a day is not necessary. |
++-------------------------------------+-------------------------------------------------------------+
+| `post_emailed_questions` | (experimental feature) posts questions sent by email |
+| | to enable this feature - please follow the instructions |
+| | on :doc:`sending email to askbot <sending-email-to-askbot>`.|
+| | This command uses :ref:`live settings <live-settings>` |
+| | "allow posting by email" and "replace spaces in tags |
+| | with dash". |
++-------------------------------------+-------------------------------------------------------------+
+| `send_unanswered_question_reminders`| Sends periodic reminders about unanswered questions. |
+| | This command may be disabled from the "email" section |
+| | of :ref:`live settings <live-settings>`, as well as |
+| | an initial wait period and the recurrence delay may be set. |
++-------------------------------------+-------------------------------------------------------------+
Data repair commands
====================
diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst
new file mode 100644
index 00000000..d7ca2f24
--- /dev/null
+++ b/askbot/doc/source/optional-modules.rst
@@ -0,0 +1,26 @@
+================
+Optional modules
+================
+
+Askbot supports a number of optional modules, enabling certain features, not available
+in askbot by default.
+
+Uploaded avatars
+================
+
+To enable uploadable avatars (in addition to :ref:`gravatars <gravatar>`),
+please install development version of
+application ``django-avatar``, with the following command:
+
+ pip install -e git+git://github.com/ericflo/django-avatar.git#egg=django-avatar
+
+Then add ``avatar`` to the list of ``INSTALLED_APPS`` in your ``settings.py`` file
+and run (to install database table used by the avatar app):
+
+ python manage.py syncdb
+
+.. note::
+
+ Version of the ``avatar`` application available at pypi may not
+ be up to date, so please take the development version from the
+ github repository
diff --git a/askbot/doc/source/sending-email-to-askbot.rst b/askbot/doc/source/sending-email-to-askbot.rst
new file mode 100644
index 00000000..b50b8c8e
--- /dev/null
+++ b/askbot/doc/source/sending-email-to-askbot.rst
@@ -0,0 +1,33 @@
+=======================
+Sending email to askbot
+=======================
+
+Askbot supports asking questions by email via the IMAP protocol,
+answering by email is not yet supported.
+
+.. note::
+ This feature is still experimental and some emails will not
+ be parsed, please report any issues at the askbot forum.
+
+To enable the feature, please:
+
+* set up an IMAP mailbox called "INBOX"
+* in your ``settings.py`` file fill out values
+ ``IMAP_HOST``, ``IMAP_HOST_USER``, ``IMAP_HOST_PASSWORD``,
+ ``IMAP_PORT`` and ``IMAP_USE_TLS``
+* in the site :ref:`live settings <live-settings>`, enable the
+ feature
+* set up a cron job to periodically run command
+ :ref:`post_emailed_questions <email-related-commands>`.
+ This command will connect to the inbox, and post questions,
+ based on the incoming messages.
+
+The email address to send the questions will be
+``<IMAP_HOST_USER>@<IMAP_HOST>``. Also, there is a quite strict
+requirement to the format of incoming messages - described
+in a response to any incorrectly formatted emails.
+
+.. warning::
+ the "INBOX" used to post messages to askbot must be dedicated
+ do not use any other mailbox as all messages
+ are **automatically deleted** after each processing.
diff --git a/askbot/management/commands/post_emailed_questions.py b/askbot/management/commands/post_emailed_questions.py
index b80eb188..73b5ed3e 100644
--- a/askbot/management/commands/post_emailed_questions.py
+++ b/askbot/management/commands/post_emailed_questions.py
@@ -125,7 +125,7 @@ class Command(NoArgsCommand):
all messages are deleted thereafter
"""
- if askbot_settings.ALLOW_ASKING_BY_EMAIL:
+ if not askbot_settings.ALLOW_ASKING_BY_EMAIL:
raise CommandError('Asking by email is not enabled')
#open imap server and select the inbox
@@ -146,6 +146,12 @@ class Command(NoArgsCommand):
#get message ids
status, ids = imap.search(None, 'ALL')
+ if len(ids[0].strip()) == 0:
+ #with empty inbox - close and exit
+ imap.close()
+ imap.logout()
+ return
+
#for each id - read a message, parse it and post a question
for id in ids[0].split(' '):
t, data = imap.fetch(id, '(RFC822)')
@@ -166,8 +172,9 @@ class Command(NoArgsCommand):
if form.is_valid():
email_address = form.cleaned_data['email']
try:
- print 'looking for ' + email_address
- user = models.User.objects.get(email = email_address)
+ user = models.User.objects.get(
+ email__iexact = email_address
+ )
except models.User.DoesNotExist:
bounce_email(email_address, subject, reason = 'unknown_user')
except models.User.MultipleObjectsReturned:
@@ -178,10 +185,6 @@ class Command(NoArgsCommand):
body_text = form.cleaned_data['body_text']
try:
- print 'posting question'
- print title
- print tagnames
- print body_text
user.post_question(
title = title,
tags = tagnames,
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index 56286981..5f14618b 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -5,6 +5,7 @@ from django.db.models import Q, F
from askbot.models import User, Question, Answer, Tag, QuestionRevision
from askbot.models import AnswerRevision, Activity, EmailFeedSetting
from askbot.models import Comment
+from askbot.models.question import get_tag_summary_from_questions
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings as django_settings
@@ -72,52 +73,6 @@ def format_action_count(string, number, output):
if number > 0:
output.append(_(string) % {'num':number})
-def get_update_subject_line(question_dict):
- """forms a subject line based on up to five most
- frequently used tags in the question_dict
-
- question_dict is an instance of SortedDict,
- where questions are keys and values are meta_data
- accumulated during the question filtering
- """
- #todo: in python 2.6 there is collections.Counter() thing
- #which would be very useful here
- tag_counts = dict()
- updated_questions = question_dict.keys()
- for question in updated_questions:
- tag_names = question.get_tag_names()
- for tag_name in tag_names:
- if tag_name in tag_counts:
- tag_counts[tag_name] += 1
- else:
- tag_counts[tag_name] = 1
- tag_list = tag_counts.keys()
- #sort in descending order
- tag_list.sort(lambda x, y: cmp(tag_counts[y], tag_counts[x]))
-
- question_count = len(updated_questions)
- #note that double quote placement is important here
- if len(tag_list) == 1:
- last_topic = '"'
- elif len(tag_list) <= 5:
- last_topic = _('" and "%s"') % tag_list.pop()
- else:
- tag_list = tag_list[:5]
- last_topic = _('" and more')
-
- topics = '"' + '", "'.join(tag_list) + last_topic
-
- subject_line = ungettext(
- '%(question_count)d updated question about %(topics)s',
- '%(question_count)d updated questions about %(topics)s',
- question_count
- ) % {
- 'question_count': question_count,
- 'topics': topics
- }
-
- return mail.prefix_the_subject_line(subject_line)
-
class Command(NoArgsCommand):
def handle_noargs(self, **options):
try:
@@ -244,41 +199,8 @@ class Command(NoArgsCommand):
q_ans_B.cutoff_time = cutoff_time
elif feed.feed_type == 'q_all':
- if user.email_tag_filter_strategy == 'ignored':
-
- ignored_tags = Tag.objects.filter(
- user_selections__reason='bad',
- user_selections__user=user
- )
-
- wk = user.ignored_tags.strip().split()
- ignored_by_wildcards = Tag.objects.get_by_wildcards(wk)
-
- q_all_A = Q_set_A.exclude(
- tags__in = ignored_tags
- ).exclude(
- tags__in = ignored_by_wildcards
- )
-
- q_all_B = Q_set_B.exclude(
- tags__in = ignored_tags
- ).exclude(
- tags__in = ignored_by_wildcards
- )
- else:
- selected_tags = Tag.objects.filter(
- user_selections__reason='good',
- user_selections__user=user
- )
-
- wk = user.interesting_tags.strip().split()
- selected_by_wildcards = Tag.objects.get_by_wildcards(wk)
-
- tag_filter = Q(tags__in = list(selected_tags)) \
- | Q(tags__in = list(selected_by_wildcards))
-
- q_all_A = Q_set_A.filter( tag_filter )
- q_all_B = Q_set_B.filter( tag_filter )
+ q_all_A = user.get_tag_filtered_questions(Q_set_A)
+ q_all_B = user.get_tag_filtered_questions(Q_set_B)
q_all_A = q_all_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
q_all_B = q_all_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
@@ -478,7 +400,19 @@ class Command(NoArgsCommand):
num_q += 1
if num_q > 0:
url_prefix = askbot_settings.APP_URL
- subject_line = get_update_subject_line(q_list)
+
+ tag_summary = get_tag_summary_from_questions(q_list.keys())
+ question_count = len(q_list.keys())
+
+ subject_line = ungettext(
+ '%(question_count)d updated question about %(topics)s',
+ '%(question_count)d updated questions about %(topics)s',
+ question_count
+ ) % {
+ 'question_count': question_count,
+ 'topics': tag_summary
+ }
+
#todo: send this to special log
#print 'have %d updated questions for %s' % (num_q, user.username)
text = ungettext('%(name)s, this is an update message header for %(num)d question',
diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py
new file mode 100644
index 00000000..50ae6451
--- /dev/null
+++ b/askbot/management/commands/send_unanswered_question_reminders.py
@@ -0,0 +1,97 @@
+import datetime
+from django.core.management.base import NoArgsCommand
+from django.conf import settings as django_settings
+from askbot import models
+from askbot import const
+from askbot.conf import settings as askbot_settings
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from askbot.utils import mail
+from askbot.models.question import get_tag_summary_from_questions
+
+DEBUG_THIS_COMMAND = False
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ if askbot_settings.ENABLE_UNANSWERED_REMINDERS == False:
+ return
+ #get questions without answers, excluding closed and deleted
+ #order it by descending added_at date
+ wait_period = datetime.timedelta(
+ askbot_settings.DAYS_BEFORE_SENDING_UNANSWERED_REMINDER
+ )
+ cutoff_date = datetime.datetime.now() + wait_period
+
+ questions = models.Question.objects.exclude(
+ closed = True
+ ).exclude(
+ deleted = True
+ ).filter(
+ added_at__lt = cutoff_date
+ ).filter(
+ answer_count = 0
+ ).order_by('-added_at')
+ #for all users, excluding blocked
+ #for each user, select a tag filtered subset
+ #format the email reminder and send it
+ for user in models.User.objects.exclude(status = 'b'):
+ user_questions = questions.exclude(author = user)
+ user_questions = user.get_tag_filtered_questions(user_questions)
+
+ final_question_list = list()
+ #todo: rewrite using query set filter
+ #may be a lot more efficient
+ for question in user_questions:
+ activity_type = const.TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT
+ try:
+ activity = models.Activity.objects.get(
+ user = user,
+ question = question,
+ activity_type = activity_type
+ )
+ now = datetime.datetime.now()
+ recurrence_delay = datetime.timedelta(
+ askbot_settings.UNANSWERED_REMINDER_FREQUENCY
+ )
+ if now < activity.active_at + recurrence_delay:
+ continue
+ except models.Activity.DoesNotExist:
+ activity = models.Activity(
+ user = user,
+ question = question,
+ activity_type = activity_type,
+ content_object = question,
+ )
+ activity.active_at = datetime.datetime.now()
+ activity.save()
+ final_question_list.append(question)
+
+ question_count = len(final_question_list)
+ if question_count == 0:
+ continue
+
+ tag_summary = get_tag_summary_from_questions(user_questions)
+ subject_line = ungettext(
+ '%(question_count)d unanswered question about %(topics)s',
+ '%(question_count)d unanswered questions about %(topics)s',
+ question_count
+ ) % {
+ 'question_count': question_count,
+ 'topics': tag_summary
+ }
+
+ body_text = '<ul>'
+ for question in final_question_list:
+ body_text += '<li><a href="%s%s?sort=latest">%s</a></li>' \
+ % (
+ askbot_settings.APP_URL,
+ question.get_absolute_url(),
+ question.title
+ )
+ body_text += '</ul>'
+
+ mail.send_mail(
+ subject_line = subject_line,
+ body_text = body_text,
+ recipient_list = (user.email,)
+ )
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index a4d21caa..b11cb976 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -1492,6 +1492,45 @@ def user_get_q_sel_email_feed_frequency(self):
raise e
return feed_setting.frequency
+def user_get_tag_filtered_questions(self, questions = None):
+ """Returns a query set of questions, tag filtered according
+ to the user choices. Parameter ``questions`` can be either ``None``
+ or a starting query set.
+ """
+ if questions == None:
+ questions = Question.objects.all()
+
+ if self.email_tag_filter_strategy == const.EXCLUDE_IGNORED:
+
+ ignored_tags = Tag.objects.filter(
+ user_selections__reason = 'bad',
+ user_selections__user = self
+ )
+
+ wk = self.ignored_tags.strip().split()
+ ignored_by_wildcards = Tag.objects.get_by_wildcards(wk)
+
+ return questions.exclude(
+ tags__in = ignored_tags
+ ).exclude(
+ tags__in = ignored_by_wildcards
+ )
+ elif self.email_tag_filter_strategy == const.INCLUDE_INTERESTING:
+ selected_tags = Tag.objects.filter(
+ user_selections__reason = 'good',
+ user_selections__user = self
+ )
+
+ wk = self.interesting_tags.strip().split()
+ selected_by_wildcards = Tag.objects.get_by_wildcards(wk)
+
+ tag_filter = models.Q(tags__in = list(selected_tags)) \
+ | models.Q(tags__in = list(selected_by_wildcards))
+
+ return questions.filter( tag_filter )
+ else:
+ return questions
+
def get_messages(self):
messages = []
for m in self.message_set.all():
@@ -1831,10 +1870,14 @@ User.add_to_class('downvote', downvote)
User.add_to_class('flag_post', flag_post)
User.add_to_class('receive_reputation', user_receive_reputation)
User.add_to_class('get_flags', user_get_flags)
-User.add_to_class('get_flag_count_posted_today', user_get_flag_count_posted_today)
+User.add_to_class(
+ 'get_flag_count_posted_today',
+ user_get_flag_count_posted_today
+)
User.add_to_class('get_flags_for_post', user_get_flags_for_post)
User.add_to_class('get_profile_url', get_profile_url)
User.add_to_class('get_profile_link', get_profile_link)
+User.add_to_class('get_tag_filtered_questions', user_get_tag_filtered_questions)
User.add_to_class('get_messages', get_messages)
User.add_to_class('delete_messages', delete_messages)
User.add_to_class('toggle_favorite_question', toggle_favorite_question)
diff --git a/askbot/models/question.py b/askbot/models/question.py
index bbfa4cfd..ccb587e7 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -40,6 +40,42 @@ QUESTION_ORDER_BY_MAP = {
'relevance-desc': None#this is a special case for postges only
}
+def get_tag_summary_from_questions(questions):
+ """returns a humanized string containing up to
+ five most frequently used
+ unique tags coming from the ``questions``.
+ Variable ``questions`` is an iterable of
+ :class:`~askbot.models.Question` model objects.
+
+ This is not implemented yet as a query set method,
+ because it is used on a list.
+ """
+ #todo: in python 2.6 there is collections.Counter() thing
+ #which would be very useful here
+ tag_counts = dict()
+ for question in questions:
+ tag_names = question.get_tag_names()
+ for tag_name in tag_names:
+ if tag_name in tag_counts:
+ tag_counts[tag_name] += 1
+ else:
+ tag_counts[tag_name] = 1
+ tag_list = tag_counts.keys()
+ #sort in descending order
+ tag_list.sort(lambda x, y: cmp(tag_counts[y], tag_counts[x]))
+
+ #note that double quote placement is important here
+ if len(tag_list) == 1:
+ last_topic = '"'
+ elif len(tag_list) <= 5:
+ last_topic = _('" and "%s"') % tag_list.pop()
+ else:
+ tag_list = tag_list[:5]
+ last_topic = _('" and more')
+
+ return '"' + '", "'.join(tag_list) + last_topic
+
+
class QuestionQuerySet(models.query.QuerySet):
"""Custom query set subclass for :class:`~askbot.models.Question`
"""
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 5ded3e17..1276d307 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -210,3 +210,6 @@ CELERY_ALWAYS_EAGER = True
import djcelery
djcelery.setup_loader()
+
+CSRF_COOKIE_NAME = 'askbot_scrf'
+CSRF_COOKIE_DOMAIN = ''#enter domain name here - e.g. example.com
diff --git a/askbot/skins/default/media/js/live_search.js b/askbot/skins/default/media/js/live_search.js
index b94ecf07..0f6b79c3 100644
--- a/askbot/skins/default/media/js/live_search.js
+++ b/askbot/skins/default/media/js/live_search.js
@@ -398,40 +398,43 @@ var liveSearch = function(){
var render_ask_page_result = function(data, text_status, xhr){
var container = $('#' + q_list_sel);
- container.children().remove();
- if (data.length > 5){
- container.css('overflow-y', 'scroll');
- container.css('height', '120px');
- } else {
- container.css('height', data.length * 24 + 'px');
- container.css('overflow-y', 'hidden');
- }
- $.each(data, function(idx, question){
- var url = question['url'];
- var title = question['title'];
- var answer_count = question['answer_count'];
- var list_item = $('<h2></h2>');
- var count_element = $('<span class="item-count"></span>');
- count_element.html(answer_count);
- list_item.append(count_element);
- var link = $('<a></a>');
- link.attr('href', url);
- list_item.append(link);
- title_element = $('<span class="title"></span>');
- title_element.html(title);
- link.append(title)
- container.append(list_item);
+ container.fadeOut(200, function() {
+ container.children().remove();
+ if (data.length > 5){
+ container.css('overflow-y', 'scroll');
+ container.css('height', '120px');
+ } else {
+ container.css('height', data.length * 24 + 'px');
+ container.css('overflow-y', 'hidden');
+ }
+ $.each(data, function(idx, question){
+ var url = question['url'];
+ var title = question['title'];
+ var answer_count = question['answer_count'];
+ var list_item = $('<h2></h2>');
+ var count_element = $('<span class="item-count"></span>');
+ count_element.html(answer_count);
+ list_item.append(count_element);
+ var link = $('<a></a>');
+ link.attr('href', url);
+ list_item.append(link);
+ title_element = $('<span class="title"></span>');
+ title_element.html(title);
+ link.append(title)
+ container.append(list_item);
+ });
+ container.fadeIn();
});
};
var render_main_page_result = function(data, text_status, xhr){
var old_list = $('#' + q_list_sel);
- var new_list = $('<div></div>');
+ var new_list = $('<div></div>').hide();
if (data['questions'].length > 0){
new_list.html(render_question_list(data['questions']));
- old_list.hide();
+ //old_list.hide();
old_list.after(new_list);
- old_list.remove();
+ //old_list.remove();
//rename new div to old
new_list.attr('id', q_list_sel);
render_paginator(data['paginator']);
@@ -442,8 +445,13 @@ var liveSearch = function(){
render_relevance_sort_tab();
set_active_sort_tab(sortMethod);
query.focus();
+
+ //show new div with a fadeIn effect
+ old_list.fadeOut(200, function() {
+ old_list.remove();
+ new_list.fadeIn(400);
+ });
}
- //show new div
}
var try_again = function(){
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index 34be550f..9671ec29 100755
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -331,6 +331,7 @@ blockquote {
#question-list {
float: left;
+ position: relative;
background-color: #FFF;
padding: 0;
width: 100%;
@@ -474,6 +475,7 @@ ul#search-tags {
.short-summary {
position: relative;
+ filter: inherit;
padding: 5px 2px 5px 2px;
border-top: 1px dashed #ccccce;
overflow: hidden;
diff --git a/askbot/skins/default/templates/answer_edit.html b/askbot/skins/default/templates/answer_edit.html
index 0dc137ae..0d8b40da 100644
--- a/askbot/skins/default/templates/answer_edit.html
+++ b/askbot/skins/default/templates/answer_edit.html
@@ -11,7 +11,7 @@
</h1>
<div id="main-body" class="ask-body">
<div id="askform">
- <form id="fmedit" action="{% url edit_answer answer.id %}" method="post" >
+ <form id="fmedit" action="{% url edit_answer answer.id %}" method="post" >{% csrf_token %}
<label for="id_revision" ><strong>{% trans %}revision{% endtrans %}:</strong></label> <br/>
{% if revision_form.revision.errors %}{{ revision_form.revision.errors.as_ul() }}{% endif %}
<div style="vertical-align:middle">
diff --git a/askbot/skins/default/templates/authopenid/changeemail.html b/askbot/skins/default/templates/authopenid/changeemail.html
index 52dc6a0c..1316a048 100644
--- a/askbot/skins/default/templates/authopenid/changeemail.html
+++ b/askbot/skins/default/templates/authopenid/changeemail.html
@@ -21,7 +21,7 @@
<p class="error">{{ msg }}</p>
{% endif %}
<div class="aligned">
- <form action="." method="post" accept-charset="utf-8">
+ <form action="." method="post" accept-charset="utf-8">{% csrf_token %}
{% if next %}
<input type="hidden" name="next" value="{{next}}"/>
{% endif %}
diff --git a/askbot/skins/default/templates/authopenid/complete.html b/askbot/skins/default/templates/authopenid/complete.html
index ccaf753a..40ec4ccc 100644
--- a/askbot/skins/default/templates/authopenid/complete.html
+++ b/askbot/skins/default/templates/authopenid/complete.html
@@ -48,11 +48,11 @@ parameters:
{% endif %}
<div class="login">
{% if login_type=='openid' %}
- <form name="fregister" action="{% url user_register %}" method="POST">
+ <form name="fregister" action="{% url user_register %}" method="POST">{% csrf_token %}
{% elif login_type=='facebook' %}
- <form name="fregister" action="" method="POST">
+ <form name="fregister" action="" method="POST">{% csrf_token %}
{% else %}
- <form name="fregister" action="{% url user_signin %}" method="POST">
+ <form name="fregister" action="{% url user_signin %}" method="POST">{% csrf_token %}
{% endif %}
{{ openid_register_form.next }}
<div class="form-row-vertical">
diff --git a/askbot/skins/default/templates/authopenid/signin.html b/askbot/skins/default/templates/authopenid/signin.html
index aa67c95f..9316255a 100644
--- a/askbot/skins/default/templates/authopenid/signin.html
+++ b/askbot/skins/default/templates/authopenid/signin.html
@@ -44,7 +44,7 @@
<p class="warning">{{ openid_error_message }}</p>
{% endif %}
{% if view_subtype != 'email_sent' and view_subtype != 'bad_key' %}
- <form id="signin-form" method="post" action="{% url user_signin %}">
+ <form id="signin-form" method="post" action="{% url user_signin %}">{% csrf_token %}
{# in this branch - the real signin view we display the login icons
here we hide the local login button only if admin
wants to always show the password login form - then
@@ -157,7 +157,7 @@
{% endif %}
{% if view_subtype != 'email_sent' or view_subtype == 'bad_key' %}
{% if user.is_anonymous() %}
- <form id="account-recovery-form" action="{% url user_account_recover %}" method="post">
+ <form id="account-recovery-form" action="{% url user_account_recover %}" method="post">{% csrf_token %}
{% if view_subtype != 'bad_key' %}
<h2 id='account-recovery-heading'>{% trans %}Still have trouble signing in?{% endtrans %}</h2>
{% endif %}
diff --git a/askbot/skins/default/templates/authopenid/signup_with_password.html b/askbot/skins/default/templates/authopenid/signup_with_password.html
index d85f8671..b5680806 100644
--- a/askbot/skins/default/templates/authopenid/signup_with_password.html
+++ b/askbot/skins/default/templates/authopenid/signup_with_password.html
@@ -8,7 +8,7 @@
{% block content %}
{% if settings.PASSWORD_REGISTER_SHOW_PROVIDER_BUTTONS == True %}
<h1>{% trans %}Please register by clicking on any of the icons below{% endtrans %}</h1>
- <form id="signin-form" method="post" action="{% url user_signin %}">
+ <form id="signin-form" method="post" action="{% url user_signin %}">{% csrf_token %}
{# hide_local_login == True because it is password reg form #}
{{
login_macros.provider_buttons(
@@ -25,7 +25,7 @@
<h1>{% trans %}Create login name and password{% endtrans %}</h1>
<p class="message">{% trans %}Traditional signup info{% endtrans %}</p>
{%endif%}
-<form action="{% url user_signup_with_password %}" method="post" accept-charset="utf-8">
+<form action="{% url user_signup_with_password %}" method="post" accept-charset="utf-8">{% csrf_token %}
{{form.login_provider}}
<ul class="form-horizontal-rows">
<li><label for="usename_id">{{form.username.label}}</label>{{form.username}}{{form.username.errors}}</li>
diff --git a/askbot/skins/default/templates/avatar/add.html b/askbot/skins/default/templates/avatar/add.html
index df700d0c..68a188ef 100644
--- a/askbot/skins/default/templates/avatar/add.html
+++ b/askbot/skins/default/templates/avatar/add.html
@@ -8,7 +8,7 @@
{% if not avatars %}
<p>{% trans %}You haven't uploaded an avatar yet. Please upload one now.{% endtrans %}</p>
{% endif %}
- <form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">
+ <form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">{% csrf_token %}
{{ upload_avatar_form.as_p() }}
<p><input type="submit" value="{% trans %}Upload New Image{% endtrans %}" /></p>
</form>
diff --git a/askbot/skins/default/templates/avatar/change.html b/askbot/skins/default/templates/avatar/change.html
index 7a88ddef..7921a662 100644
--- a/askbot/skins/default/templates/avatar/change.html
+++ b/askbot/skins/default/templates/avatar/change.html
@@ -10,14 +10,14 @@
{% if not avatars %}
<p>{% trans %}You haven't uploaded an avatar yet. Please upload one now.{% endtrans %}</p>
{% else %}
- <form method="POST" action="{% url avatar_change %}">
+ <form method="POST" action="{% url avatar_change %}">{% csrf_token %}
<ul>
{{ primary_avatar_form.as_ul() }}
</ul>
<p><input type="submit" value="{% trans %}Choose new Default{% endtrans %}" /></p>
</form>
{% endif %}
- <form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">
+ <form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">{% csrf_token %}
{{ upload_avatar_form.as_p() }}
<p><input type="submit" value="{% trans %}Upload{% endtrans %}" /></p>
</form>
diff --git a/askbot/skins/default/templates/avatar/confirm_delete.html b/askbot/skins/default/templates/avatar/confirm_delete.html
index 042d7c0d..282d72fa 100644
--- a/askbot/skins/default/templates/avatar/confirm_delete.html
+++ b/askbot/skins/default/templates/avatar/confirm_delete.html
@@ -6,7 +6,7 @@
{% if not avatars %}
<p>{% trans avatar_change_url="avatar_change"|url %}You have no avatars to delete. Please <a href="{{ avatar_change_url }}">upload one</a> now.{% endtrans %}</p>
{% else %}
- <form method="POST" action="{% url avatar_delete %}">
+ <form method="POST" action="{% url avatar_delete %}">{% csrf_token %}
<ul>
{{ delete_avatar_form.as_ul() }}
</ul>
diff --git a/askbot/skins/default/templates/blocks/ask_form.html b/askbot/skins/default/templates/blocks/ask_form.html
index 8df6c019..9b61c7ce 100644
--- a/askbot/skins/default/templates/blocks/ask_form.html
+++ b/askbot/skins/default/templates/blocks/ask_form.html
@@ -1,6 +1,6 @@
{% import "macros.html" as macros %}
<div id="askform">
- <form id="fmask" action="" method="post" >
+ <form id="fmask" action="" method="post" >{% csrf_token %}
<div class="form-item">
<div id="askFormBar">
{% if not request.user.is_authenticated() %}
diff --git a/askbot/skins/default/templates/close.html b/askbot/skins/default/templates/close.html
index 57ff5780..d8160865 100644
--- a/askbot/skins/default/templates/close.html
+++ b/askbot/skins/default/templates/close.html
@@ -6,7 +6,7 @@
<p>{% trans %}Close the question{% endtrans %}: <a href="{{ question.get_absolute_url() }}">
<strong>{{ question.get_question_title() }}</strong></a>
</p>
- <form id="fmclose" action="{% url close question.id %}" method="post" >
+ <form id="fmclose" action="{% url close question.id %}" method="post" >{% csrf_token %}
<p>
<strong>{% trans %}Reasons{% endtrans %}:</strong>
{{ form.reason }}
diff --git a/askbot/skins/default/templates/feedback.html b/askbot/skins/default/templates/feedback.html
index 258a85dc..d5e8b3a7 100644
--- a/askbot/skins/default/templates/feedback.html
+++ b/askbot/skins/default/templates/feedback.html
@@ -3,7 +3,7 @@
{% block title %}{% spaceless %}{% trans %}Feedback{% endtrans %}{% endspaceless %}{% endblock %}
{% block content %}
<h1>{% trans %}Give us your feedback!{% endtrans %}</h1>
-<form method="post" action="{% url feedback %}" accept-charset="utf-8">
+<form method="post" action="{% url feedback %}" accept-charset="utf-8">{% csrf_token %}
{% if user.is_authenticated() %}
<p class="message">
{% trans user_name=user.username %}
diff --git a/askbot/skins/default/templates/import_data.html b/askbot/skins/default/templates/import_data.html
index 7bc370ab..affeaa73 100644
--- a/askbot/skins/default/templates/import_data.html
+++ b/askbot/skins/default/templates/import_data.html
@@ -18,7 +18,7 @@
Please note that feedback will be printed in plain text.
{% endtrans %}
</p>
- <form id="load-dump-form" method="post" enctype="multipart/form-data">
+ <form id="load-dump-form" method="post" enctype="multipart/form-data">{% csrf_token %}
<table>
{{dump_upload_form.as_table()}}
</table>
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index d593fec5..0eb0b303 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -528,7 +528,7 @@ poor design of the data or methods on data objects #}
sortButtonData["{{key_name}}"] = {
label: "{{label}}",
asc_tooltip: "{{asc_tooltip}}",
- desc_tooltip: "{{desc_tooltip}}",
+ desc_tooltip: "{{desc_tooltip}}"
};
</script>
{%- endmacro %}
@@ -645,7 +645,7 @@ poor design of the data or methods on data objects #}
useCache: true,
matchInside: true,
maxCacheLength: 100,
- delay: 10,
+ delay: 10
});
tagAc.decorate($("{{ id }}"));
{%- endmacro -%}
diff --git a/askbot/skins/default/templates/main_page/tab_bar.html b/askbot/skins/default/templates/main_page/tab_bar.html
index 9189db13..12096a3b 100644
--- a/askbot/skins/default/templates/main_page/tab_bar.html
+++ b/askbot/skins/default/templates/main_page/tab_bar.html
@@ -45,7 +45,7 @@
sortButtonData['relevance'] = {
asc_tooltip: "{{asc_relevance_tooltip}}",
desc_tooltip: "{{desc_relevance_tooltip}}",
- label: "{{relevance_label}}",
+ label: "{{relevance_label}}"
};
</script>
{% endif %}
diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html
index d95fd6c0..ffab9bd1 100644
--- a/askbot/skins/default/templates/question.html
+++ b/askbot/skins/default/templates/question.html
@@ -304,7 +304,7 @@
{{ macros.paginator(paginator_context) }}
</div><br/>
{% endif %}
-<form id="fmanswer" action="{% url answer question.id %}" method="post">
+<form id="fmanswer" action="{% url answer question.id %}" method="post">{% csrf_token %}
{% if request.user.is_authenticated() %}
<p style="padding-left:3px">
{{ answer.email_notify }}
diff --git a/askbot/skins/default/templates/question_edit.html b/askbot/skins/default/templates/question_edit.html
index c1a84426..6a55e58c 100644
--- a/askbot/skins/default/templates/question_edit.html
+++ b/askbot/skins/default/templates/question_edit.html
@@ -7,7 +7,7 @@
{% endblock %}
{% block content %}
<h1>{% trans %}Edit question{% endtrans %} [<a href="{{ question.get_absolute_url() }}">{% trans %}back{% endtrans %}</a>]</h1>
-<form id="fmedit" action="{% url edit_question question.id %}" method="post" >
+<form id="fmedit" action="{% url edit_question question.id %}" method="post" >{% csrf_token %}
<label for="id_revision" ><strong>{% trans %}revision{% endtrans %}:</strong></label> <br/>
{% if revision_form.revision.errors %}{{ revision_form.revision.errors.as_ul() }}{% endif %}
<div style="vertical-align:middle">
diff --git a/askbot/skins/default/templates/question_retag.html b/askbot/skins/default/templates/question_retag.html
index f521ccb3..79cbbbff 100644
--- a/askbot/skins/default/templates/question_retag.html
+++ b/askbot/skins/default/templates/question_retag.html
@@ -4,7 +4,7 @@
{% block content %}
<h1>{% trans %}Change tags{% endtrans %} [<a href="{{ question.get_absolute_url() }}">{% trans %}back{% endtrans %}</a>]</h1>
<div id="askform">
- <form id="fmretag" action="{% url retag_question question.id %}" method="post" >
+ <form id="fmretag" action="{% url retag_question question.id %}" method="post" >{% csrf_token %}
<h2>
{{ question.get_question_title() }}
</h2>
diff --git a/askbot/skins/default/templates/reopen.html b/askbot/skins/default/templates/reopen.html
index 58d798a3..d68e8bdc 100644
--- a/askbot/skins/default/templates/reopen.html
+++ b/askbot/skins/default/templates/reopen.html
@@ -21,7 +21,7 @@
<p>
{% trans %}Reopen this question?{% endtrans %}
</p>
-<form id="fmclose" action="{% url reopen question.id %}" method="post" >
+<form id="fmclose" action="{% url reopen question.id %}" method="post" >{% csrf_token %}
<div id="" style="padding:20px 0 20px 0">
<input type="submit" value="{% trans %}Reopen this question{% endtrans %}" class="submit" />&nbsp;
<input id="btBack" type="button" value="{% trans %}Cancel{% endtrans %}" class="submit" />
diff --git a/askbot/skins/default/templates/subscribe_for_tags.html b/askbot/skins/default/templates/subscribe_for_tags.html
index 9a58ccbf..b436fb84 100644
--- a/askbot/skins/default/templates/subscribe_for_tags.html
+++ b/askbot/skins/default/templates/subscribe_for_tags.html
@@ -10,7 +10,7 @@
{% endfor %}
</ul>
<div style="clear:both;padding-top: 5px">
- <form method="post" action="{% url subscribe_for_tags %}">
+ <form method="post" action="{% url subscribe_for_tags %}">{% csrf_token %}
<input type="hidden" name="tags" value="{{tags|join(' ')|escape}}" />
<input type="submit" name="ok" value="{% trans %}Subscribe{% endtrans %}" />
<input type="submit" name="nope" value="{% trans %}Cancel{% endtrans %}" />
diff --git a/askbot/skins/default/templates/user_profile/user_edit.html b/askbot/skins/default/templates/user_profile/user_edit.html
index 9308bf90..fe4ea35f 100644
--- a/askbot/skins/default/templates/user_profile/user_edit.html
+++ b/askbot/skins/default/templates/user_profile/user_edit.html
@@ -7,7 +7,7 @@
{{ request.user.username }} - {% trans %}edit profile{% endtrans %}
</h1>
<div id="main-body" style="width:100%;padding-top:10px">
- <form name="" action="{% url edit_user request.user.id %}" method="post">
+ <form name="" action="{% url edit_user request.user.id %}" method="post">{% csrf_token %}
<div id="left" style="float:left;width:180px">
{% if request.user.email %}
{{ macros.gravatar(request.user, 128) }}
diff --git a/askbot/skins/default/templates/user_profile/user_email_subscriptions.html b/askbot/skins/default/templates/user_profile/user_email_subscriptions.html
index 896a77f0..e6a18dd3 100644
--- a/askbot/skins/default/templates/user_profile/user_email_subscriptions.html
+++ b/askbot/skins/default/templates/user_profile/user_email_subscriptions.html
@@ -10,7 +10,7 @@
{% if action_status %}
<p class="action-status"><span>{{action_status}}</span></p>
{% endif %}
- <form method="post" action="">
+ <form method="post" action="">{% csrf_token %}
<table class='form-as-table ab-subscr-form'>
{{email_feeds_form.as_table()}}
</table>
diff --git a/askbot/skins/default/templates/user_profile/user_moderate.html b/askbot/skins/default/templates/user_profile/user_moderate.html
index b8070e50..563026a4 100644
--- a/askbot/skins/default/templates/user_profile/user_moderate.html
+++ b/askbot/skins/default/templates/user_profile/user_moderate.html
@@ -10,7 +10,7 @@
{% if user_status_changed %}
<p class="action-status"><span>{% trans %}User status changed{% endtrans %}</span></p>
{% endif %}
- <form method="post">
+ <form method="post">{% csrf_token %}
<input type="hidden" name="sort" value="moderate"/>
<table class="form-as-table">
{{ change_user_status_form.as_table() }}
@@ -29,7 +29,7 @@
{% if user_rep_changed %}
<p class="action-status"><span>{% trans %}User reputation changed{% endtrans %}</span></p>
{% endif %}
-<form method="post">
+<form method="post">{% csrf_token %}
<input type="hidden" name="sort" value="moderate"/>
<table class="form-as-table">
{{ change_user_reputation_form.as_table() }}
@@ -44,7 +44,7 @@
{% if message_sent %}
<p class="action-status"><span>{% trans %}Message sent{% endtrans %}</span></p>
{% endif %}
-<form method="post">
+<form method="post">{% csrf_token %}
<input type="hidden" name="sort" value="moderate"/>
<div class="form-row-vertical">
<label for="id_subject_line">{{ send_message_form.subject_line.label}}</label>
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index de801967..0c239be1 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -13,6 +13,7 @@ from askbot import models
from askbot.utils import mail
from askbot.conf import settings as askbot_settings
from askbot import const
+from askbot.models.question import get_tag_summary_from_questions
def email_alert_test(test_func):
"""decorator for test methods in
@@ -685,8 +686,7 @@ class DelayedAlertSubjectLineTests(TestCase):
q1:'', q2:'', q3:'', q4:'', q5:'', q6:'', q7:'',
q8:'', q9:'', q10:'', q11:'',
}
- from askbot.management.commands import send_email_alerts as cmd
- subject = cmd.get_update_subject_line(q_dict)
+ subject = get_tag_summary_from_questions(q_dict.keys())
self.assertTrue('one' not in subject)
self.assertTrue('two' in subject)
@@ -701,9 +701,9 @@ class DelayedAlertSubjectLineTests(TestCase):
i6 = subject.index('six')
order = [i6, i5, i4, i3, i2]
self.assertEquals(
- order,
- sorted(order)
- )
+ order,
+ sorted(order)
+ )
class FeedbackTests(utils.AskbotTestCase):
def setUp(self):
@@ -800,3 +800,22 @@ class TagFollowedInstantWholeForumEmailAlertTests(utils.AskbotTestCase):
self.assertTrue(
self.user1.email in outbox[0].recipients()
)
+
+class UnansweredReminderTests(utils.AskbotTestCase):
+ def setUp(self):
+ self.u1 = self.create_user(username = 'user1')
+ self.u2 = self.create_user(username = 'user2')
+
+ def test_reminder_simple(self):
+ """a positive test - user must receive a reminder
+ """
+ askbot_settings.update('ENABLE_UNANSWERED_REMINDERS', True)
+ days_ago = 5*askbot_settings.DAYS_BEFORE_SENDING_UNANSWERED_REMINDER
+ long_ago = datetime.datetime.now() - datetime.timedelta(days_ago)
+ self.post_question(
+ user = self.u1,
+ timestamp = long_ago
+ )
+ management.call_command('send_unanswered_question_reminders')
+ outbox = django.core.mail.outbox
+ self.assertEqual(len(outbox), 1)
diff --git a/askbot/views/avatar_views.py b/askbot/views/avatar_views.py
index ea16380d..8ac30561 100644
--- a/askbot/views/avatar_views.py
+++ b/askbot/views/avatar_views.py
@@ -5,6 +5,7 @@ does not support jinja templates
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.utils.translation import ugettext as _
+from django.views.decorators import csrf
from django.conf import settings
from django.contrib.auth.decorators import login_required
@@ -74,6 +75,7 @@ def _get_avatars(user):
return (avatar, avatars)
@login_required
+@csrf.csrf_protect
def add(request, extra_context=None, next_override=None,
upload_form=UploadAvatarForm, *args, **kwargs):
if extra_context is None:
@@ -109,6 +111,7 @@ def add(request, extra_context=None, next_override=None,
return render_into_skin('avatar/add.html', data, request)
@login_required
+@csrf.csrf_protect
def change(request, extra_context=None, next_override=None,
upload_form=UploadAvatarForm, primary_form=PrimaryAvatarForm,
*args, **kwargs):
@@ -150,6 +153,7 @@ def change(request, extra_context=None, next_override=None,
return render_into_skin('avatar/change.html', data, request)
@login_required
+@csrf.csrf_protect
def delete(request, extra_context=None, next_override=None, *args, **kwargs):
if extra_context is None:
extra_context = {}
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 8d16c35f..809d4249 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -12,6 +12,7 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
from django.forms import ValidationError
from django.shortcuts import get_object_or_404
+from django.views.decorators import csrf
from django.utils import simplejson
from django.utils.translation import ugettext as _
from askbot import models
@@ -391,6 +392,7 @@ def get_tag_list(request):
output = '\n'.join(tag_names)
return HttpResponse(output, mimetype = "text/plain")
+@csrf.csrf_protect
def subscribe_for_tags(request):
"""process subscription of users by tags"""
#todo - use special separator to split tags
@@ -471,6 +473,7 @@ def set_tag_filter_strategy(request):
@login_required
+@csrf.csrf_protect
def close(request, id):#close question
"""view to initiate and process
question close
@@ -500,6 +503,7 @@ def close(request, id):#close question
return HttpResponseRedirect(question.get_absolute_url())
@login_required
+@csrf.csrf_protect
def reopen(request, id):#re-open question
"""view to initiate and process
question close
diff --git a/askbot/views/meta.py b/askbot/views/meta.py
index 0e67f08f..8953200b 100644
--- a/askbot/views/meta.py
+++ b/askbot/views/meta.py
@@ -10,6 +10,7 @@ from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.views import static
+from django.views.decorators import csrf
from django.db.models import Max, Count
from askbot.forms import FeedbackForm
from askbot.utils.forms import get_next_url
@@ -49,6 +50,7 @@ def faq(request):
}
return render_into_skin('faq.html', data, request)
+@csrf.csrf_protect
def feedback(request):
data = {'page_class': 'meta'}
form = None
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index 20a0df58..547addec 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -18,6 +18,7 @@ from django.utils import simplejson
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils import translation
+from django.views.decorators import csrf
from django.core.urlresolvers import reverse
from django.core import exceptions as django_exceptions
from django.contrib.humanize.templatetags import humanize
@@ -354,6 +355,7 @@ def tags(request):#view showing a listing of available tags - plain list
}
return render_into_skin('tags.html', data, request)
+@csrf.csrf_protect
def question(request, id):#refactor - long subroutine. display question body, answers and comments
"""view that displays body of the question and
all answers to it
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 0db7124e..d96ceece 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -11,6 +11,7 @@ import functools
import datetime
import logging
from django.db.models import Count
+from django.conf import settings as django_settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, InvalidPage
from django.contrib.contenttypes.models import ContentType
@@ -19,7 +20,7 @@ from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.http import HttpResponseRedirect, Http404
from django.utils.translation import ugettext as _
-from django.conf import settings as django_settings
+from django.views.decorators import csrf
from askbot.utils.slug import slugify
from askbot.utils.html import sanitize_html
from askbot.utils.mail import send_mail
@@ -129,6 +130,7 @@ def users(request):
}
return render_into_skin('users.html', data, request)
+@csrf.csrf_protect
def user_moderate(request, subject):
"""user subview for moderation
"""
@@ -232,6 +234,7 @@ def set_new_email(user, new_email, nomessage=False):
# send_new_email_key(user,nomessage=nomessage)
@login_required
+@csrf.csrf_protect
def edit_user(request, id):
"""View that allows to edit user profile.
This view is accessible to profile owners or site administrators
@@ -862,6 +865,7 @@ def user_favorites(request, user):
return render_into_skin('user_profile/user_favorites.html', data, request)
@owner_or_moderator_required
+@csrf.csrf_protect
def user_email_subscriptions(request, user):
logging.debug(get_request_info(request))
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 56b37154..d103c776 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -22,6 +22,7 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.core import exceptions
from django.conf import settings
+from django.views.decorators import csrf
from askbot import forms
from askbot import models
@@ -147,7 +148,7 @@ def __import_se_data(dump_file):
sys.stdout = real_stdout
yield '<p>Done. Please, <a href="%s">Visit Your Forum</a></p></body></html>' % reverse('index')
-
+@csrf.csrf_protect
def import_data(request):
"""a view allowing the site administrator
upload stackexchange data
@@ -185,6 +186,7 @@ def import_data(request):
return render_into_skin('import_data.html', data, request)
#@login_required #actually you can post anonymously, but then must register
+@csrf.csrf_protect
def ask(request):#view used to ask a new question
"""a view to ask a new question
gives space for q title, body, tags and checkbox for to post as wiki
@@ -260,6 +262,7 @@ def ask(request):#view used to ask a new question
return render_into_skin('ask.html', data, request)
@login_required
+@csrf.csrf_protect
def retag_question(request, id):
"""retag question view
"""
@@ -313,6 +316,7 @@ def retag_question(request, id):
return HttpResponseRedirect(question.get_absolute_url())
@login_required
+@csrf.csrf_protect
def edit_question(request, id):
"""edit question view
"""
@@ -356,6 +360,7 @@ def edit_question(request, id):
revision = latest_revision,
user = request.user,
)
+ revision_form = forms.RevisionForm(question, latest_revision)
if form.is_valid():
if form.has_changed():
@@ -397,6 +402,7 @@ def edit_question(request, id):
return HttpResponseRedirect(question.get_absolute_url())
@login_required
+@csrf.csrf_protect
def edit_answer(request, id):
answer = get_object_or_404(models.Answer, id=id)
try:
@@ -426,6 +432,8 @@ def edit_answer(request, id):
)
else:
form = forms.EditAnswerForm(answer, latest_revision, request.POST)
+ revision_form = forms.RevisionForm(answer, latest_revision)
+
if form.is_valid():
if form.has_changed():
request.user.edit_answer(
diff --git a/setup.py b/setup.py
index 5f6c018d..aa01ef56 100644
--- a/setup.py
+++ b/setup.py
@@ -34,7 +34,7 @@ if sys.platform not in WIN_PLATFORMS:
setup(
name = "askbot",
- version = '0.6.76',#remember to manually set this correctly
+ version = '0.6.77',#remember to manually set this correctly
description = 'Question and Answer forum, like StackOverflow, written in python and Django',
packages = find_packages(),
author = 'Evgeny.Fadeev',