summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdolfo Fitoria <adolfo.fitoria@gmail.com>2012-07-23 09:39:25 -0600
committerAdolfo Fitoria <adolfo.fitoria@gmail.com>2012-07-23 09:39:25 -0600
commitff442a5f2841a8969435a7ab8b1b68cf52f93811 (patch)
tree4b59a7b50409c0e587af899d695ed4636122eb7f
parent9ac22fe88423759f9ace9a3dbbf9e5bf5ee84fec (diff)
parentb459613a068f73afd6a07384fd326e93ebb1ed4e (diff)
downloadaskbot-ff442a5f2841a8969435a7ab8b1b68cf52f93811.tar.gz
askbot-ff442a5f2841a8969435a7ab8b1b68cf52f93811.tar.bz2
askbot-ff442a5f2841a8969435a7ab8b1b68cf52f93811.zip
Merge branch 'master' of github.com:ASKBOT/askbot-devel
-rw-r--r--askbot/__init__.py12
-rw-r--r--askbot/conf/forum_data_rules.py11
-rw-r--r--askbot/doc/source/changelog.rst2
-rw-r--r--askbot/doc/source/settings.rst14
-rw-r--r--askbot/exceptions.py5
-rw-r--r--askbot/forms.py524
-rw-r--r--askbot/models/__init__.py11
-rw-r--r--askbot/models/post.py2
-rw-r--r--askbot/models/question.py9
-rw-r--r--askbot/skins/default/templates/meta/bottom_scripts.html47
-rw-r--r--askbot/skins/default/templates/question/content.html12
-rw-r--r--askbot/tests/badge_tests.py6
-rw-r--r--askbot/utils/forms.py2
-rw-r--r--askbot/utils/slug.py71
-rw-r--r--askbot/views/readers.py16
-rw-r--r--askbot/views/writers.py5
16 files changed, 505 insertions, 244 deletions
diff --git a/askbot/__init__.py b/askbot/__init__.py
index 859b2695..8e4f20ab 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -5,9 +5,6 @@ Functions in the askbot module perform various
basic actions on behalf of the forum application
"""
import os
-import smtplib
-import sys
-import logging
VERSION = (0, 7, 43)
@@ -43,17 +40,19 @@ try:
from askbot.deployment.assertions import assert_package_compatibility
assert_package_compatibility()
patches.patch_django()
- patches.patch_coffin()#must go after django
+ patches.patch_coffin() # must go after django
except ImportError:
pass
+
def get_install_directory():
"""returns path to directory
- where code of the askbot django application
+ where code of the askbot django application
is installed
"""
return os.path.dirname(__file__)
+
def get_path_to(relative_path):
"""returns absolute path to a file
relative to ``askbot`` directory
@@ -72,10 +71,11 @@ def get_version():
"""
return '.'.join([str(subversion) for subversion in VERSION])
+
def get_database_engine_name():
"""returns name of the database engine,
independently of the version of django
- - for django >=1.2 looks into ``settings.DATABASES['default']``,
+ - for django >=1.2 looks into ``settings.DATABASES['default']``,
(i.e. assumes that askbot uses database named 'default')
, and for django 1.1 and below returns settings.DATABASE_ENGINE
"""
diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py
index a26aa4b3..b7077a15 100644
--- a/askbot/conf/forum_data_rules.py
+++ b/askbot/conf/forum_data_rules.py
@@ -122,6 +122,17 @@ settings.register(
settings.register(
livesettings.BooleanValue(
FORUM_DATA_RULES,
+ 'LIMIT_ONE_ANSWER_PER_USER',
+ default = True,
+ description = _(
+ 'Limit one answer per question per user'
+ )
+ )
+)
+
+settings.register(
+ livesettings.BooleanValue(
+ FORUM_DATA_RULES,
'TAGS_ARE_REQUIRED',
description = _('Are tags required?'),
default = False,
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 00918e76..c0540be9 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,8 @@ Changes in Askbot
Development version
-------------------
+* Added optional support for unicode slugs (Evgeny)
+* Optionally allow limiting one answer per question per person (Evgeny)
* Added management command `build_livesettings_cache` (Adolfo)
* Welcome email for the case when replying by email is enabled (Evgeny)
* Detection of email signature based on the response to the welcome email (Evgeny)
diff --git a/askbot/doc/source/settings.rst b/askbot/doc/source/settings.rst
new file mode 100644
index 00000000..d07e697b
--- /dev/null
+++ b/askbot/doc/source/settings.rst
@@ -0,0 +1,14 @@
+=================================
+Settings for ``settings.py`` file
+=================================
+
+* ``ALLOW_UNICODE_SLUGS`` - if ``True``, slugs will use unicode, default - ``False``
+
+There are more settings that are not documented yet,
+but most are described in the ``settings.py`` template:
+
+ askbot/setup_templates/settings.py.mustache
+
+TODO: describe all of them here.
+
+
diff --git a/askbot/exceptions.py b/askbot/exceptions.py
index d2d5ddf0..12802e7e 100644
--- a/askbot/exceptions.py
+++ b/askbot/exceptions.py
@@ -19,6 +19,11 @@ class InsufficientReputation(exceptions.PermissionDenied):
"""
pass
+class AnswerAlreadyGiven(exceptions.PermissionDenied):
+ """Raised when user attempts to post a second answer
+ to the same question"""
+ pass
+
class DuplicateCommand(exceptions.PermissionDenied):
"""exception class to indicate that something
that can happen only once was attempted for the second time
diff --git a/askbot/forms.py b/askbot/forms.py
index 81382a2c..1188ed59 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -1,3 +1,5 @@
+"""Forms, custom form fields and related utility functions
+used in AskBot"""
import re
from django import forms
from askbot import const
@@ -5,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy, string_concat
from django.utils.text import get_text_list
from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
from django_countries import countries
from askbot.utils.forms import NextUrlField, UserNameField
from askbot.mail import extract_first_email_address
@@ -13,6 +14,7 @@ from recaptcha_works.fields import RecaptchaField
from askbot.conf import settings as askbot_settings
import logging
+
def cleanup_dict(dictionary, key, empty_value):
"""deletes key from dictionary if it exists
and the corresponding value equals the empty_value
@@ -20,13 +22,27 @@ def cleanup_dict(dictionary, key, empty_value):
if key in dictionary and dictionary[key] == empty_value:
del dictionary[key]
+
def format_form_errors(form):
+ """Formats form errors in HTML
+ if there is only one error - returns a plain string
+ if more than one, returns an unordered list of errors
+ in HTML format.
+ If there are no errors, returns empty string
+ """
if form.errors:
errors = form.errors.values()
if len(errors) == 1:
return errors[0]
else:
- return 'hahahahah'
+ result = '<ul>'
+ for error in errors:
+ result += '<li>%s</li>' % error
+ result += '</ul>'
+ return result
+ else:
+ return ''
+
def clean_marked_tagnames(tagnames):
"""return two strings - one containing tagnames
@@ -35,7 +51,7 @@ def clean_marked_tagnames(tagnames):
wildcard tags are those that have an asterisk at the end
the function does not verify that the tag names are valid
"""
- if askbot_settings.USE_WILDCARD_TAGS == False:
+ if askbot_settings.USE_WILDCARD_TAGS is False:
return tagnames, list()
pure_tags = list()
@@ -53,7 +69,8 @@ def clean_marked_tagnames(tagnames):
return pure_tags, wildcards
-def filter_choices(remove_choices = None, from_choices = None):
+
+def filter_choices(remove_choices=None, from_choices=None):
"""a utility function that will remove choice tuples
usable for the forms.ChoicesField from
``from_choices``, the removed ones will be those given
@@ -73,12 +90,47 @@ def filter_choices(remove_choices = None, from_choices = None):
if choice == choice_to_test[0]:
remove = True
break
- if remove == False:
- filtered_choices += ( choice_to_test, )
+ if remove is False:
+ filtered_choices += (choice_to_test, )
return filtered_choices
-COUNTRY_CHOICES = (('unknown',_('select country')),) + countries.COUNTRIES
+
+def need_mandatory_tags():
+ """true, if list of mandatory tags is not empty"""
+ from askbot import models
+ return (
+ askbot_settings.TAGS_ARE_REQUIRED
+ and len(models.tag.get_mandatory_tags()) > 0
+ )
+
+
+def mandatory_tag_missing_in_list(tag_strings):
+ """true, if mandatory tag is not present in the list
+ of ``tag_strings``"""
+ from askbot import models
+ mandatory_tags = models.tag.get_mandatory_tags()
+ for mandatory_tag in mandatory_tags:
+ for tag_string in tag_strings:
+ if tag_strings_match(tag_string, mandatory_tag):
+ return False
+ return True
+
+
+def tag_strings_match(tag_string, mandatory_tag):
+ """true if tag string matches the mandatory tag,
+ the comparison is not symmetric if tag_string ends with a
+ wildcard (asterisk)
+ """
+ if mandatory_tag.endswith('*'):
+ return tag_string.startswith(mandatory_tag[:-1])
+ else:
+ return tag_string == mandatory_tag
+
+
+
+COUNTRY_CHOICES = (('unknown', _('select country')),) + countries.COUNTRIES
+
class CountryField(forms.ChoiceField):
"""this is better placed into the django_coutries app"""
@@ -100,10 +152,13 @@ class CountryField(forms.ChoiceField):
return None
return value
+
class CountedWordsField(forms.CharField):
-
+ """a field where a number of words is expected
+ to be in a certain range"""
+
def __init__(
- self, min_words = 0, max_words = 9999, field_name = None,
+ self, min_words=0, max_words=9999, field_name=None,
*args, **kwargs
):
self.min_words = min_words
@@ -144,28 +199,38 @@ class CountedWordsField(forms.CharField):
class DomainNameField(forms.CharField):
+ """Field for Internet Domain Names
+ todo: maybe there is a standard field for this?
+ """
def clean(self, value):
#find a better regex, taking into account tlds
domain_re = re.compile(r'[a-zA-Z\d]+(\.[a-zA-Z\d]+)+')
if domain_re.match(value):
return value
else:
- raise forms.ValidationError('%s is not a valid domain name' % value)
+ raise forms.ValidationError(
+ '%s is not a valid domain name' % value
+ )
class TitleField(forms.CharField):
+ """Fild receiving question title"""
def __init__(self, *args, **kwargs):
super(TitleField, self).__init__(*args, **kwargs)
self.required = True
self.widget = forms.TextInput(
- attrs={'size' : 70, 'autocomplete' : 'off'}
- )
+ attrs={'size': 70, 'autocomplete': 'off'}
+ )
self.max_length = 255
- self.label = _('title')
- self.help_text = _('please enter a descriptive title for your question')
+ self.label = _('title')
+ self.help_text = _(
+ 'please enter a descriptive title for your question'
+ )
self.initial = ''
def clean(self, value):
+ """cleans the field for minimum and maximum length
+ also is supposed to work for unicode non-ascii characters"""
if value is None:
value = ''
if len(value) < askbot_settings.MIN_TITLE_LENGTH:
@@ -192,21 +257,22 @@ class TitleField(forms.CharField):
) % self.max_length
)
- return value.strip() # TODO: test me
+ return value.strip() # TODO: test me
+
class EditorField(forms.CharField):
- """EditorField is subclassed by the
+ """EditorField is subclassed by the
:class:`QuestionEditorField` and :class:`AnswerEditorField`
"""
length_error_template_singular = 'post content must be > %d character',
length_error_template_plural = 'post content must be > %d characters',
- min_length = 10#sentinel default value
+ min_length = 10 # sentinel default value
def __init__(self, *args, **kwargs):
super(EditorField, self).__init__(*args, **kwargs)
self.required = True
- self.widget = forms.Textarea(attrs={'id':'editor'})
- self.label = _('content')
+ self.widget = forms.Textarea(attrs={'id': 'editor'})
+ self.label = _('content')
self.help_text = u''
self.initial = ''
@@ -218,32 +284,44 @@ class EditorField(forms.CharField):
self.length_error_template_singular,
self.length_error_template_plural,
self.min_length
- ) % self.min_length
+ ) % self.min_length
raise forms.ValidationError(msg)
return value
+
class QuestionEditorField(EditorField):
+ """Editor field for the questions"""
+
def __init__(self, *args, **kwargs):
super(QuestionEditorField, self).__init__(*args, **kwargs)
- self.length_error_template_singular = 'question body must be > %d character'
- self.length_error_template_plural = 'question body must be > %d characters'
+ self.length_error_template_singular = \
+ 'question body must be > %d character'
+ self.length_error_template_plural = \
+ 'question body must be > %d characters'
self.min_length = askbot_settings.MIN_QUESTION_BODY_LENGTH
+
class AnswerEditorField(EditorField):
+ """Editor field for answers"""
+
def __init__(self, *args, **kwargs):
super(AnswerEditorField, self).__init__(*args, **kwargs)
self.length_error_template_singular = 'answer must be > %d character'
self.length_error_template_plural = 'answer must be > %d characters'
self.min_length = askbot_settings.MIN_ANSWER_BODY_LENGTH
+
class TagNamesField(forms.CharField):
+ """field that receives AskBot tag names"""
+
def __init__(self, *args, **kwargs):
super(TagNamesField, self).__init__(*args, **kwargs)
self.required = askbot_settings.TAGS_ARE_REQUIRED
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.widget = forms.TextInput(
+ attrs={'size': 50, 'autocomplete': 'off'}
+ )
self.max_length = 255
self.label = _('tags')
- #self.help_text = _('please use space to separate tags (this enables autocomplete feature)')
self.help_text = ungettext_lazy(
'Tags are short keywords, with no spaces within. '
'Up to %(max_tags)d tag can be used.',
@@ -253,38 +331,16 @@ class TagNamesField(forms.CharField):
) % {'max_tags': askbot_settings.MAX_TAGS_PER_POST}
self.initial = ''
- def need_mandatory_tags(self):
- """true, if list of mandatory tags is not empty"""
- from askbot import models
- return askbot_settings.TAGS_ARE_REQUIRED and len(models.tag.get_mandatory_tags()) > 0
-
- def tag_string_matches(self, tag_string, mandatory_tag):
- """true if tag string matches the mandatory tag"""
- if mandatory_tag.endswith('*'):
- return tag_string.startswith(mandatory_tag[:-1])
- else:
- return tag_string == mandatory_tag
-
- def mandatory_tag_missing(self, tag_strings):
- """true, if mandatory tag is not present in the list
- of ``tag_strings``"""
- from askbot import models
- mandatory_tags = models.tag.get_mandatory_tags()
- for mandatory_tag in mandatory_tags:
- for tag_string in tag_strings:
- if self.tag_string_matches(tag_string, mandatory_tag):
- return False
- return True
-
def clean(self, value):
from askbot import models
value = super(TagNamesField, self).clean(value)
data = value.strip()
if len(data) < 1:
if askbot_settings.TAGS_ARE_REQUIRED:
- raise forms.ValidationError(_('tags are required'))
+ raise forms.ValidationError(_('tags are required'))
else:
- return ''#don't test for required characters when tags is ''
+ #don't test for required characters when tags is ''
+ return ''
split_re = re.compile(const.TAG_SPLIT_REGEX)
tag_strings = split_re.split(data)
entered_tags = []
@@ -294,11 +350,11 @@ class TagNamesField(forms.CharField):
msg = ungettext_lazy(
'please use %(tag_count)d tag or less',
'please use %(tag_count)d tags or less',
- tag_count) % {'tag_count':max_tags}
+ tag_count) % {'tag_count': max_tags}
raise forms.ValidationError(msg)
- if self.need_mandatory_tags():
- if self.mandatory_tag_missing(tag_strings):
+ if need_mandatory_tags():
+ if mandatory_tag_missing_in_list(tag_strings):
msg = _(
'At least one of the following tags is required : %(tags)s'
) % {'tags': get_text_list(models.tag.get_mandatory_tags())}
@@ -307,18 +363,22 @@ class TagNamesField(forms.CharField):
for tag in tag_strings:
tag_length = len(tag)
if tag_length > askbot_settings.MAX_TAG_LENGTH:
- #singular form is odd in english, but required for pluralization
- #in other languages
- msg = ungettext_lazy('each tag must be shorter than %(max_chars)d character',#odd but added for completeness
- 'each tag must be shorter than %(max_chars)d characters',
- tag_length) % {'max_chars':tag_length}
+ #singular form is odd in english, but
+ #required for pluralization
+ #in other languages, odd but added for completeness
+ msg = ungettext_lazy(
+ 'each tag must be shorter than %(max_chars)d character',
+ 'each tag must be shorter than %(max_chars)d characters',
+ tag_length
+ ) % {'max_chars': tag_length}
raise forms.ValidationError(msg)
#todo - this needs to come from settings
tagname_re = re.compile(const.TAG_REGEX, re.UNICODE)
if not tagname_re.search(tag):
raise forms.ValidationError(_(
- 'In tags, please use letters, numbers and characters "-+.#"'
+ 'In tags, please use letters, '
+ 'numbers and characters "-+.#"'
))
#only keep unique tags
if tag not in entered_tags:
@@ -340,7 +400,7 @@ class TagNamesField(forms.CharField):
#because we need tag name cases to be the same
#as those stored in the database
stored_tag = models.Tag.objects.get(
- name__iexact = entered_tag
+ name__iexact=entered_tag
)
if stored_tag.name not in cleaned_entered_tags:
cleaned_entered_tags.append(stored_tag.name)
@@ -349,30 +409,54 @@ class TagNamesField(forms.CharField):
return u' '.join(cleaned_entered_tags)
+
class WikiField(forms.BooleanField):
+ """Rendered as checkbox turning post into
+ "community wiki"
+ """
+
def __init__(self, *args, **kwargs):
super(WikiField, self).__init__(*args, **kwargs)
self.required = False
self.initial = False
- self.label = _('community wiki (karma is not awarded & many others can edit wiki post)')
- self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
+ self.label = _(
+ 'community wiki (karma is not awarded & '
+ 'many others can edit wiki post)'
+ )
+ self.help_text = _(
+ 'if you choose community wiki option, the question '
+ 'and answer do not generate points and name of '
+ 'author will not be shown'
+ )
+
def clean(self, value):
return value and askbot_settings.WIKI_ON
+
class EmailNotifyField(forms.BooleanField):
+ """Rendered as checkbox which turns on
+ email notifications on the post"""
def __init__(self, *args, **kwargs):
super(EmailNotifyField, self).__init__(*args, **kwargs)
self.required = False
self.widget.attrs['class'] = 'nomargin'
+
class SummaryField(forms.CharField):
+
def __init__(self, *args, **kwargs):
super(SummaryField, self).__init__(*args, **kwargs)
self.required = False
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.widget = forms.TextInput(
+ attrs={'size': 50, 'autocomplete': 'off'}
+ )
self.max_length = 300
- self.label = _('update summary:')
- self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
+ self.label = _('update summary:')
+ self.help_text = _(
+ 'enter a brief summary of your revision (e.g. '
+ 'fixed spelling, grammar, improved style, this '
+ 'field is optional)'
+ )
class DumpUploadForm(forms.Form):
@@ -382,6 +466,7 @@ class DumpUploadForm(forms.Form):
"""
dump_file = forms.FileField()
+
class ShowQuestionForm(forms.Form):
"""Cleans data necessary to access answers and comments
by the respective comment or answer id - necessary
@@ -389,10 +474,10 @@ class ShowQuestionForm(forms.Form):
on the page other than the first page of answers to a question.
Same for the answers that are shown on the later pages.
"""
- answer = forms.IntegerField(required = False)
- comment = forms.IntegerField(required = False)
- page = forms.IntegerField(required = False)
- sort = forms.CharField(required = False)
+ answer = forms.IntegerField(required=False)
+ comment = forms.IntegerField(required=False)
+ page = forms.IntegerField(required=False)
+ sort = forms.CharField(required=False)
def __init__(self, data, default_sort_method):
super(ShowQuestionForm, self).__init__(data)
@@ -436,6 +521,7 @@ class ShowQuestionForm(forms.Form):
self.cleaned_data = out_data
return out_data
+
class ChangeUserReputationForm(forms.Form):
"""Form that allows moderators and site administrators
to adjust reputation of users.
@@ -445,13 +531,15 @@ class ChangeUserReputationForm(forms.Form):
"""
user_reputation_delta = forms.IntegerField(
- min_value = 1,
- label = _('Enter number of points to add or subtract')
- )
- comment = forms.CharField(max_length = 128)
+ min_value=1,
+ label=_(
+ 'Enter number of points to add or subtract'
+ )
+ )
+ comment = forms.CharField(max_length=128)
def clean_comment(self):
- if 'comment' in self.cleaned_data:
+ if 'comment' in self.cleaned_data:
comment = self.cleaned_data['comment'].strip()
if comment == '':
del self.cleaned_data['comment']
@@ -485,9 +573,7 @@ class ChangeUserStatusForm(forms.Form):
"moderation" tab
"""
- user_status = forms.ChoiceField(
- label = _('Change status to'),
- )
+ user_status = forms.ChoiceField(label=_('Change status to'))
def __init__(self, *arg, **kwarg):
@@ -508,12 +594,12 @@ class ChangeUserStatusForm(forms.Form):
#remove current status of the "subject" user from choices
user_status_choices = filter_choices(
- remove_choices = [subject.status, ],
- from_choices = user_status_choices
+ remove_choices=[subject.status, ],
+ from_choices=user_status_choices
)
#add prompt option
- user_status_choices = ( ('select', _('which one?')), ) \
+ user_status_choices = (('select', _('which one?')), ) \
+ user_status_choices
self.fields['user_status'].choices = user_status_choices
@@ -561,41 +647,39 @@ class ChangeUserStatusForm(forms.Form):
msg = _(
'If you wish to change %(username)s\'s status, '
'please make a meaningful selection.'
- ) % {'username': self.subject.username }
+ ) % {'username': self.subject.username}
raise forms.ValidationError(msg)
return self.cleaned_data
+
class SendMessageForm(forms.Form):
subject_line = forms.CharField(
- label = _('Subject line'),
- max_length = 64,
- widget = forms.TextInput(
- attrs = {'size':64},
- )
- )
+ label=_('Subject line'),
+ max_length=64,
+ widget=forms.TextInput(attrs={'size': 64}, )
+ )
body_text = forms.CharField(
- label = _('Message text'),
- max_length = 1600,
- widget = forms.Textarea(
- attrs = {'cols':64}
- )
+ label=_('Message text'),
+ max_length=1600,
+ widget=forms.Textarea(attrs={'cols': 64})
)
class NotARobotForm(forms.Form):
recaptcha = RecaptchaField(
- private_key = askbot_settings.RECAPTCHA_SECRET,
- public_key = askbot_settings.RECAPTCHA_KEY
+ private_key=askbot_settings.RECAPTCHA_SECRET,
+ public_key=askbot_settings.RECAPTCHA_KEY
)
+
class FeedbackForm(forms.Form):
name = forms.CharField(label=_('Your name (optional):'), required=False)
email = forms.EmailField(label=_('Email:'), required=False)
message = forms.CharField(
label=_('Your message:'),
max_length=800,
- widget=forms.Textarea(attrs={'cols':60})
+ widget=forms.Textarea(attrs={'cols': 60})
)
no_email = forms.BooleanField(
label=_("I don't want to give my email or receive a response:"),
@@ -612,19 +696,21 @@ class FeedbackForm(forms.Form):
def _add_recaptcha_field(self):
self.fields['recaptcha'] = RecaptchaField(
- private_key = askbot_settings.RECAPTCHA_SECRET,
- public_key = askbot_settings.RECAPTCHA_KEY
- )
+ private_key=askbot_settings.RECAPTCHA_SECRET,
+ public_key=askbot_settings.RECAPTCHA_KEY
+ )
def clean(self):
super(FeedbackForm, self).clean()
if not self.is_auth:
- if not self.cleaned_data['no_email'] and not self.cleaned_data['email']:
+ if not self.cleaned_data['no_email'] \
+ and not self.cleaned_data['email']:
msg = _('Please mark "I dont want to give my mail" field.')
self._errors['email'] = self.error_class([msg])
return self.cleaned_data
+
class FormWithHideableFields(object):
"""allows to swap a field widget to HiddenInput() and back"""
@@ -646,6 +732,7 @@ class FormWithHideableFields(object):
if name in self.__hidden_fields:
self.fields[name] = self.__hidden_fields.pop(name)
+
class AskForm(forms.Form, FormWithHideableFields):
"""the form used to askbot questions
field ask_anonymously is shown to the user if the
@@ -654,40 +741,51 @@ class AskForm(forms.Form, FormWithHideableFields):
in the cleaned data, and will evaluate to False if the
settings forbids anonymous asking
"""
- title = TitleField()
- text = QuestionEditorField()
- tags = TagNamesField()
+ title = TitleField()
+ text = QuestionEditorField()
+ tags = TagNamesField()
wiki = WikiField()
ask_anonymously = forms.BooleanField(
- label = _('ask anonymously'),
- help_text = _(
+ label=_('ask anonymously'),
+ help_text=_(
'Check if you do not want to reveal your name '
'when asking this question'
),
- required = False,
+ required=False,
+ )
+ openid = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'})
+ )
+ user = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 35})
+ )
+ email = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 35})
)
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
def __init__(self, *args, **kwargs):
super(AskForm, self).__init__(*args, **kwargs)
#hide ask_anonymously field
- if askbot_settings.ALLOW_ASK_ANONYMOUSLY == False:
+ if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
self.hide_field('ask_anonymously')
def clean_ask_anonymously(self):
"""returns false if anonymous asking is not allowed
"""
- if askbot_settings.ALLOW_ASK_ANONYMOUSLY == False:
+ if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
self.cleaned_data['ask_anonymously'] = False
return self.cleaned_data['ask_anonymously']
+
ASK_BY_EMAIL_SUBJECT_HELP = _(
'Subject line is expected in the format: '
'[tag1, tag2, tag3,...] question title'
)
+
class AskByEmailForm(forms.Form):
""":class:`~askbot.forms.AskByEmailForm`
validates question data, where question was posted
@@ -706,12 +804,13 @@ class AskByEmailForm(forms.Form):
* ``email`` - email address
* ``title`` - question title
* ``tagnames`` - tag names all in one string
- * ``body_text`` - body of question text - a pass-through, no extra validation
+ * ``body_text`` - body of question text -
+ a pass-through, no extra validation
"""
- sender = forms.CharField(max_length = 255)
+ sender = forms.CharField(max_length=255)
subject = forms.CharField(
- max_length = 255,
- error_messages = {
+ max_length=255,
+ error_messages={
'required': ASK_BY_EMAIL_SUBJECT_HELP
}
)
@@ -743,17 +842,19 @@ class AskByEmailForm(forms.Form):
match = subject_re.match(raw_subject)
if match:
#make raw tags comma-separated
- if match.group(1) is None:#no tags
+ if match.group(1) is None: # no tags
self.cleaned_data['tagnames'] = ''
else:
- tagnames = match.group(1).replace(';',',')
+ tagnames = match.group(1).replace(';', ',')
#pre-process tags
tag_list = [tag.strip() for tag in tagnames.split(',')]
tag_list = [re.sub(r'\s+', ' ', tag) for tag in tag_list]
+
if askbot_settings.REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS:
tag_list = [tag.replace(' ', '-') for tag in tag_list]
- tagnames = ' '.join(tag_list)#todo: use tag separator char here
+ #todo: use tag separator char here
+ tagnames = ' '.join(tag_list)
#clean tags - may raise ValidationError
self.cleaned_data['tagnames'] = TagNamesField().clean(tagnames)
@@ -765,22 +866,36 @@ class AskByEmailForm(forms.Form):
raise forms.ValidationError(ASK_BY_EMAIL_SUBJECT_HELP)
return self.cleaned_data['subject']
+
class AnswerForm(forms.Form):
- text = AnswerEditorField()
- wiki = WikiField()
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email_notify = EmailNotifyField(initial = False)
+ text = AnswerEditorField()
+ wiki = WikiField()
+ openid = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'})
+ )
+ user = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 35})
+ )
+ email = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 35})
+ )
+ email_notify = EmailNotifyField(initial=False)
+
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
- self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates'
+ self.fields['email_notify'].widget.attrs['id'] = \
+ 'question-subscribe-updates'
+
class VoteForm(forms.Form):
"""form used in ajax vote view (only comment_upvote so far)
"""
post_id = forms.IntegerField()
- cancel_vote = forms.CharField()#char because it is 'true' or 'false' as string
+ # char because it is 'true' or 'false' as string
+ cancel_vote = forms.CharField()
def clean_cancel_vote(self):
val = self.cleaned_data['cancel_vote']
@@ -790,7 +905,9 @@ class VoteForm(forms.Form):
result = False
else:
del self.cleaned_data['cancel_vote']
- raise forms.ValidationError('either "true" or "false" strings expected')
+ raise forms.ValidationError(
+ 'either "true" or "false" strings expected'
+ )
self.cleaned_data['cancel_vote'] = result
return self.cleaned_data['cancel_vote']
@@ -798,43 +915,57 @@ class VoteForm(forms.Form):
class CloseForm(forms.Form):
reason = forms.ChoiceField(choices=const.CLOSE_REASONS)
+
class RetagQuestionForm(forms.Form):
tags = TagNamesField()
- # initialize the default values
+
def __init__(self, question, *args, **kwargs):
+ """initialize the default values"""
super(RetagQuestionForm, self).__init__(*args, **kwargs)
self.fields['tags'].initial = question.thread.tagnames
+
class RevisionForm(forms.Form):
"""
Lists revisions of a Question or Answer
"""
- revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
+ revision = forms.ChoiceField(
+ widget=forms.Select(
+ attrs={'style': 'width:520px'}
+ )
+ )
def __init__(self, post, latest_revision, *args, **kwargs):
super(RevisionForm, self).__init__(*args, **kwargs)
revisions = post.revisions.values_list(
- 'revision', 'author__username', 'revised_at', 'summary')
+ 'revision', 'author__username', 'revised_at', 'summary'
+ )
date_format = '%c'
- self.fields['revision'].choices = [
- (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
- for r in revisions]
+ rev_choices = list()
+ for r in revisions:
+ rev_details = u'%s - %s (%s) %s' % (
+ r[0], r[1], r[2].strftime(date_format), r[3]
+ )
+ rev_choices.append((r[0], rev_details))
+
+ self.fields['revision'].choices = rev_choices
self.fields['revision'].initial = latest_revision.revision
+
class EditQuestionForm(forms.Form, FormWithHideableFields):
- title = TitleField()
- text = QuestionEditorField()
- tags = TagNamesField()
+ title = TitleField()
+ text = QuestionEditorField()
+ tags = TagNamesField()
summary = SummaryField()
wiki = WikiField()
reveal_identity = forms.BooleanField(
- help_text = _(
+ help_text=_(
'You have asked this question anonymously, '
'if you decide to reveal your identity, please check '
'this box.'
),
- label = _('reveal identity'),
- required = False,
+ label=_('reveal identity'),
+ required=False,
)
#todo: this is odd that this form takes question as an argument
@@ -855,8 +986,8 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
def can_stay_anonymous(self):
"""determines if the user cat keep editing the question
anonymously"""
- return (askbot_settings.ALLOW_ASK_ANONYMOUSLY \
- and self.question.is_anonymous \
+ return (askbot_settings.ALLOW_ASK_ANONYMOUSLY
+ and self.question.is_anonymous
and self.user.is_owner_of(self.question)
)
@@ -882,7 +1013,7 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
"""
value = self.cleaned_data['reveal_identity']
if self.question.is_anonymous:
- if value == True:
+ if value is True:
if self.user.is_owner_of(self.question):
#regardless of the ALLOW_ASK_ANONYMOUSLY
return True
@@ -900,7 +1031,7 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
else:
can_ask_anon = askbot_settings.ALLOW_ASK_ANONYMOUSLY
is_owner = self.user.is_owner_of(self.question)
- if can_ask_anon == False and is_owner:
+ if can_ask_anon is False and is_owner:
self.show_field('reveal_identity')
raise forms.ValidationError(
_(
@@ -924,11 +1055,12 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
"""
reveal_identity = self.cleaned_data.get('reveal_identity', False)
stay_anonymous = False
- if reveal_identity == False and self.can_stay_anonymous():
+ if reveal_identity is False and self.can_stay_anonymous():
stay_anonymous = True
self.cleaned_data['stay_anonymous'] = stay_anonymous
return self.cleaned_data
+
class EditAnswerForm(forms.Form):
text = AnswerEditorField()
summary = SummaryField()
@@ -939,40 +1071,42 @@ class EditAnswerForm(forms.Form):
self.fields['text'].initial = revision.text
self.fields['wiki'].initial = answer.wiki
+
class EditTagWikiForm(forms.Form):
- text = forms.CharField(required = False)
+ text = forms.CharField(required=False)
tag_id = forms.IntegerField()
+
class EditUserForm(forms.Form):
email = forms.EmailField(
label=u'Email',
required=True,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
realname = forms.CharField(
label=_('Real name'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
website = forms.URLField(
label=_('Website'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
city = forms.CharField(
label=_('City'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
- country = CountryField(required = False)
+ country = CountryField(required=False)
show_country = forms.BooleanField(
label=_('Show country'),
@@ -986,15 +1120,18 @@ class EditUserForm(forms.Form):
birthday = forms.DateField(
label=_('Date of birth'),
- help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'),
+ help_text=_(
+ 'will not be shown, used to calculate '
+ 'age, format: YYYY-MM-DD'
+ ),
required=False,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
about = forms.CharField(
label=_('Profile'),
required=False,
- widget=forms.Textarea(attrs={'cols' : 60})
+ widget=forms.Textarea(attrs={'cols': 60})
)
def __init__(self, user, *args, **kwargs):
@@ -1008,7 +1145,7 @@ class EditUserForm(forms.Form):
self.fields['realname'].initial = user.real_name
self.fields['website'].initial = user.website
self.fields['city'].initial = user.location
- if user.country == None:
+ if user.country is None:
country = 'unknown'
else:
country = user.country
@@ -1026,24 +1163,32 @@ class EditUserForm(forms.Form):
"""For security reason one unique email in database"""
if self.user.email != self.cleaned_data['email']:
#todo dry it, there is a similar thing in openidauth
- if askbot_settings.EMAIL_UNIQUE == True:
+ if askbot_settings.EMAIL_UNIQUE is True:
if 'email' in self.cleaned_data:
try:
- User.objects.get(email = self.cleaned_data['email'])
+ User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
except User.MultipleObjectsReturned:
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ raise forms.ValidationError(_(
+ 'this email has already been registered, '
+ 'please use another one')
+ )
+ raise forms.ValidationError(_(
+ 'this email has already been registered, '
+ 'please use another one')
+ )
return self.cleaned_data['email']
+
class TagFilterSelectionForm(forms.ModelForm):
email_tag_filter_strategy = forms.ChoiceField(
- choices = const.TAG_DISPLAY_FILTER_STRATEGY_CHOICES,
- initial = const.EXCLUDE_IGNORED,
- label = _('Choose email tag filter'),
- widget = forms.RadioSelect
+ choices=const.TAG_DISPLAY_FILTER_STRATEGY_CHOICES,
+ initial=const.EXCLUDE_IGNORED,
+ label=_('Choose email tag filter'),
+ widget=forms.RadioSelect
)
+
class Meta:
model = User
fields = ('email_tag_filter_strategy',)
@@ -1060,31 +1205,31 @@ class TagFilterSelectionForm(forms.ModelForm):
class EmailFeedSettingField(forms.ChoiceField):
def __init__(self, *arg, **kwarg):
kwarg['choices'] = const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES
- #kwarg['initial'] = askbot_settings.DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE
kwarg['widget'] = forms.RadioSelect
super(EmailFeedSettingField, self).__init__(*arg, **kwarg)
+
class EditUserEmailFeedsForm(forms.Form):
FORM_TO_MODEL_MAP = {
- 'all_questions':'q_all',
- 'asked_by_me':'q_ask',
- 'answered_by_me':'q_ans',
- 'individually_selected':'q_sel',
- 'mentions_and_comments':'m_and_c',
+ 'all_questions': 'q_all',
+ 'asked_by_me': 'q_ask',
+ 'answered_by_me': 'q_ans',
+ 'individually_selected': 'q_sel',
+ 'mentions_and_comments': 'm_and_c',
}
NO_EMAIL_INITIAL = {
- 'all_questions':'n',
- 'asked_by_me':'n',
- 'answered_by_me':'n',
- 'individually_selected':'n',
- 'mentions_and_comments':'n',
+ 'all_questions': 'n',
+ 'asked_by_me': 'n',
+ 'answered_by_me': 'n',
+ 'individually_selected': 'n',
+ 'mentions_and_comments': 'n',
}
INSTANT_EMAIL_INITIAL = {
- 'all_questions':'i',
- 'asked_by_me':'i',
- 'answered_by_me':'i',
- 'individually_selected':'i',
- 'mentions_and_comments':'i',
+ 'all_questions': 'i',
+ 'asked_by_me': 'i',
+ 'answered_by_me': 'i',
+ 'individually_selected': 'i',
+ 'mentions_and_comments': 'i',
}
asked_by_me = EmailFeedSettingField(
@@ -1107,7 +1252,7 @@ class EditUserEmailFeedsForm(forms.Form):
def set_initial_values(self, user=None):
from askbot import models
KEY_MAP = dict([(v, k) for k, v in self.FORM_TO_MODEL_MAP.iteritems()])
- if user != None:
+ if user is not None:
settings = models.EmailFeedSetting.objects.filter(subscriber=user)
initial_values = {}
for setting in settings:
@@ -1137,7 +1282,7 @@ class EditUserEmailFeedsForm(forms.Form):
"""
return self.FORM_TO_MODEL_MAP.values()
- def set_frequency(self, frequency = 'n'):
+ def set_frequency(self, frequency='n'):
data = {
'all_questions': frequency,
'asked_by_me': frequency,
@@ -1149,9 +1294,9 @@ class EditUserEmailFeedsForm(forms.Form):
self.cleaned_data = data
self.initial = data
- def save(self,user,save_unbound=False):
- """
- with save_unbound==True will bypass form validation and save initial values
+ def save(self, user, save_unbound=False):
+ """with save_unbound==True will bypass form
+ validation and save initial values
"""
from askbot import models
changed = False
@@ -1179,23 +1324,25 @@ class EditUserEmailFeedsForm(forms.Form):
user.followed_threads.clear()
return changed
+
class SubscribeForEmailUpdatesField(forms.ChoiceField):
"""a simple yes or no field to subscribe for email or not"""
def __init__(self, **kwargs):
kwargs['widget'] = forms.widgets.RadioSelect
kwargs['error_messages'] = {
- 'required':_('please choose one of the options above')
+ 'required': _('please choose one of the options above')
}
kwargs['choices'] = (
- ('y',_('okay, let\'s try!')),
+ ('y', _('okay, let\'s try!')),
(
'n',
- _('no %(sitename)s email please, thanks') \
+ _('no %(sitename)s email please, thanks')
% {'sitename': askbot_settings.APP_SHORT_NAME}
)
)
super(SubscribeForEmailUpdatesField, self).__init__(**kwargs)
+
class SimpleEmailSubscribeForm(forms.Form):
subscribe = SubscribeForEmailUpdatesField()
@@ -1211,11 +1358,13 @@ class SimpleEmailSubscribeForm(forms.Form):
email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
email_settings_form.save(user, save_unbound=True)
+
class GroupLogoURLForm(forms.Form):
"""form for saving group logo url"""
group_id = forms.IntegerField()
image_url = forms.CharField()
+
class EditGroupMembershipForm(forms.Form):
"""a form for adding or removing users
to and from user groups"""
@@ -1231,11 +1380,12 @@ class EditGroupMembershipForm(forms.Form):
raise forms.ValidationError('invalid action')
return action
+
class EditRejectReasonForm(forms.Form):
- reason_id = forms.IntegerField(required = False)
+ reason_id = forms.IntegerField(required=False)
title = CountedWordsField(
- min_words = 1, max_words = 4, field_name = _('Title')
+ min_words=1, max_words=4, field_name=_('Title')
)
details = CountedWordsField(
- min_words = 6, field_name = _('Description')
+ min_words=6, field_name=_('Description')
)
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index ed0d876a..1c884ed6 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -551,9 +551,16 @@ def user_assert_can_post_question(self):
)
-def user_assert_can_post_answer(self):
+def user_assert_can_post_answer(self, thread = None):
"""same as user_can_post_question
"""
+ limit_answers = askbot_settings.LIMIT_ONE_ANSWER_PER_USER
+ if limit_answers and thread.has_answer_by_user(self):
+ message = _(
+ 'Sorry, you already gave an answer, please edit it instead.'
+ )
+ raise askbot_exceptions.AnswerAlreadyGiven(message)
+
self.assert_can_post_question()
@@ -1718,7 +1725,7 @@ def user_post_answer(
assert(error_message is not None)
raise django_exceptions.PermissionDenied(error_message)
- self.assert_can_post_answer()
+ self.assert_can_post_answer(thread = question.thread)
if getattr(question, 'post_type', '') != 'question':
raise TypeError('question argument must be provided')
diff --git a/askbot/models/post.py b/askbot/models/post.py
index c08b940b..d61c1ae2 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -670,7 +670,7 @@ class Post(models.Model):
does not talk to the actual cache system
"""
self._cached_comments = comments
-
+
def get_cached_comments(self):
try:
return self._cached_comments
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 1729b531..3453ff7c 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -498,6 +498,14 @@ class Thread(models.Model):
output += answer.format_for_email_as_subthread()
return output
+ def get_answers_by_user(self, user):
+ """regardless - deleted or not"""
+ return self.posts.filter(post_type = 'answer', author = user)
+
+ def has_answer_by_user(self, user):
+ #use len to cache the queryset
+ return len(self.get_answers_by_user(user)) > 0
+
def tagname_meta_generator(self):
return u','.join([unicode(tag) for tag in self.get_tag_names()])
@@ -920,6 +928,7 @@ class AnonymousQuestion(AnonymousContent):
def publish(self,user):
added_at = datetime.datetime.now()
+ #todo: wrong - use User.post_question() instead
Thread.objects.create_new(
title = self.title,
added_at = added_at,
diff --git a/askbot/skins/default/templates/meta/bottom_scripts.html b/askbot/skins/default/templates/meta/bottom_scripts.html
index 2603ec31..6c132203 100644
--- a/askbot/skins/default/templates/meta/bottom_scripts.html
+++ b/askbot/skins/default/templates/meta/bottom_scripts.html
@@ -25,7 +25,7 @@
{% if settings.DEBUG %}
src="{{"/js/jquery-1.7.2.min.js"|media}}"
{% else %}
- src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"
+ src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
{% endif %}
></script>
<!-- History.js -->
@@ -42,33 +42,36 @@
</script>
{% endif %}
<script type="text/javascript">
-{% if active_tab != "tags" and active_tab != "users" %}
$(document).ready(function(){
- if (Modernizr.history) {
- // history management works!
- } else {
- // no history support :(
- //hash = unescape(window.location.hash).replace('#','').split("?")[0]
- hash = History.unescapeHash(window.location.hash).replace('#','').split("?")[0]
- if (hash.substring(0,11)==askbot['urls']['questions']){
- url = hash
- }else{
- url = askbot['urls']['questions']+hash
- }
- if (hash !== ''){
- window.location = 'http://'+window.location.host+url
- }
- }
-
+ {% if active_tab == 'questions' %}
+ if (Modernizr.history) {
+ // history management works!
+ } else {
+ // no history support :(
+ //hash = unescape(window.location.hash).replace('#','').split("?")[0]
+ {# todo: fix this evil code!!! #}
+ var hash = History.unescapeHash(window.location.hash).replace('#','').split("?")[0];
+ var questions_url = askbot['urls']['questions'];
+ if (hash.substring(0, questions_url.length) === questions_url) {
+ var url = hash;
+ } else {
+ var url = questions_url + hash;
+ }
+ if (hash !== '' && hash !== undefined && url !== undefined){
+ {# was this causing strange redirects in IE??? #}
+ window.location = 'http://' + window.location.host + url;
+ }
+ }
+ {% endif %}
// focus input on the search bar endcomment
- {% if active_tab != "ask" %}
+ {% if active_tab in ('users', 'questions', 'tags') %}
$('#keywords').focus();
- {% else %}
+ {% elif active_tab == 'ask' %}
$('#id_title').focus();
+ {% else %}
+ animateHashes();
{% endif %}
- animateHashes();
});
-{% endif %}
{% if user_messages %}
$('#validate_email_alert').click(function(){notify.close(true)})
notify.show();
diff --git a/askbot/skins/default/templates/question/content.html b/askbot/skins/default/templates/question/content.html
index f01c68e0..66b3014b 100644
--- a/askbot/skins/default/templates/question/content.html
+++ b/askbot/skins/default/templates/question/content.html
@@ -29,8 +29,16 @@
{% endif %}
{# ==== START: question/new_answer_form.html ==== #}
-{% include "question/new_answer_form.html" %}
-{# ==== END: question/new_answer_form.html ==== #}
+{# buttons below cannot be cached yet #}
+{% if user_already_gave_answer %}
+ <a
+ class="submit"
+ href="{% url "edit_answer" previous_answer.id %}"
+ >{% trans %}Edit Your Previous Answer{% endtrans %}</a>
+ <span>{% trans %}(only one answer per question is allowed){% endtrans %}</span>
+{% else %}
+ {% include "question/new_answer_form.html" %}
+{% endif %}
{% if question.closed == False and request.user == question.author %}{# this is outside the form on purpose #}
<input
type="button"
diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py
index dbb37dde..b66eadcc 100644
--- a/askbot/tests/badge_tests.py
+++ b/askbot/tests/badge_tests.py
@@ -68,7 +68,8 @@ class BadgeTests(AskbotTestCase):
self.assert_have_badge(badge_key, recipient = self.u2, expected_count = 1)
#post another question and check that there are no new badges
- answer2 = self.post_answer(user = self.u2, question = question)
+ question2 = self.post_question(user = self.u1)
+ answer2 = self.post_answer(user = self.u2, question = question2)
answer2.score = min_score - 1
answer2.save()
self.u1.upvote(answer2)
@@ -269,7 +270,8 @@ class BadgeTests(AskbotTestCase):
answer = self.post_answer(user = self.u2, question = question)
self.u1.accept_best_answer(answer)
self.assert_have_badge('scholar', recipient = self.u1)
- answer2 = self.post_answer(user = self.u2, question = question)
+ question2 = self.post_question(user = self.u1)
+ answer2 = self.post_answer(user = self.u2, question = question2)
self.u1.accept_best_answer(answer2)
self.assert_have_badge(
'scholar',
diff --git a/askbot/utils/forms.py b/askbot/utils/forms.py
index b8ed253b..ee7adf7e 100644
--- a/askbot/utils/forms.py
+++ b/askbot/utils/forms.py
@@ -108,7 +108,7 @@ class UserNameField(StrippedNonEmptyCharField):
raise forms.ValidationError(self.error_messages['invalid'])
if username in self.RESERVED_NAMES:
raise forms.ValidationError(self.error_messages['forbidden'])
- if slugify(username, force_unidecode = True) == '':
+ if slugify(username) == '':
raise forms.ValidationError(self.error_messages['meaningless'])
try:
user = self.db_model.objects.get(
diff --git a/askbot/utils/slug.py b/askbot/utils/slug.py
index 58f228da..f9e30cbf 100644
--- a/askbot/utils/slug.py
+++ b/askbot/utils/slug.py
@@ -5,30 +5,63 @@ the setting was added just in case - if people actually
want to see unicode characters in the slug. If this is the choice
slug will be simply equal to the input text
"""
+import re
+import unicodedata
from unidecode import unidecode
-from django.template import defaultfilters
+
from django.conf import settings
-import re
+from django.template import defaultfilters
+from django.utils.encoding import smart_unicode
+
+
+# Extra characters outside of alphanumerics that we'll allow.
+SLUG_OK = '-_~'
+
-def slugify(input_text, max_length=50, force_unidecode = False):
+def unicode_slugify(s, ok=SLUG_OK, lower=True, spaces=False):
+ """Function copied from https://github.com/mozilla/unicode-slugify
+ because the author of the package never published it on pypi.
+
+ Copyright notice below applies just to this function
+ Copyright (c) 2011, Mozilla Foundation
+ All rights reserved.
+
+ L and N signify letter/number.
+ http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
+ """
+ rv = []
+ for c in unicodedata.normalize('NFKC', smart_unicode(s)):
+ cat = unicodedata.category(c)[0]
+ if cat in 'LN' or c in ok:
+ rv.append(c)
+ if cat == 'Z': # space
+ rv.append(' ')
+ new = ''.join(rv).strip()
+ if not spaces:
+ new = re.sub('[-\s]+', '-', new)
+ return new.lower() if lower else new
+
+
+def slugify(input_text, max_length=150):
"""custom slugify function that
removes diacritic modifiers from the characters
"""
+ if input_text == '':
+ return input_text
+
allow_unicode_slugs = getattr(settings, 'ALLOW_UNICODE_SLUGS', False)
- if allow_unicode_slugs == False or force_unidecode == True:
- if input_text == '':
- return input_text
- slug = defaultfilters.slugify(unidecode(input_text))
- while len(slug) > max_length:
- # try to shorten word by word until len(slug) <= max_length
- temp = slug[:slug.rfind('-')]
- if len(temp) > 0:
- slug = temp
- else:
- #we have nothing left, do not apply the last crop,
- #apply the cut-off directly
- slug = slug[:max_length]
- break
- return slug
+ if allow_unicode_slugs:
+ slug = unicode_slugify(input_text)
else:
- return re.sub(r'\s+', '-', input_text.strip().lower())
+ slug = defaultfilters.slugify(unidecode(input_text))
+ while len(slug) > max_length:
+ # try to shorten word by word until len(slug) <= max_length
+ temp = slug[:slug.rfind('-')]
+ if len(temp) > 0:
+ slug = temp
+ else:
+ #we have nothing left, do not apply the last crop,
+ #apply the cut-off directly
+ slug = slug[:max_length]
+ break
+ return slug
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index c73b0c35..5f4d9ee4 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -455,7 +455,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
user_post_id_list = [
id for id in post_to_author if post_to_author[id] == request.user.id
]
-
+
#resolve page number and comment number for permalinks
show_comment_position = None
if show_comment:
@@ -520,7 +520,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
is_cacheable = False
elif show_comment_position > askbot_settings.MAX_COMMENTS_TO_SHOW:
is_cacheable = False
-
+
answer_form = AnswerForm(
initial = {
'wiki': question_post.wiki and askbot_settings.WIKI_ON,
@@ -532,6 +532,16 @@ def question(request, id):#refactor - long subroutine. display question body, an
request.user.is_authenticated() and request.user.can_post_comment()
)
+ user_already_gave_answer = False
+ previous_answer = None
+ if request.user.is_authenticated():
+ if askbot_settings.LIMIT_ONE_ANSWER_PER_USER:
+ for answer in answers:
+ if answer.author == request.user:
+ user_already_gave_answer = True
+ previous_answer = answer
+ break
+
data = {
'is_cacheable': False,#is_cacheable, #temporary, until invalidation fix
'long_time': const.LONG_TIME,#"forever" caching
@@ -545,6 +555,8 @@ def question(request, id):#refactor - long subroutine. display question body, an
'user_votes': user_votes,
'user_post_id_list': user_post_id_list,
'user_can_post_comment': user_can_post_comment,#in general
+ 'user_already_gave_answer': user_already_gave_answer,
+ 'previous_answer': previous_answer,
'tab_id' : answer_sort_method,
'favorited' : favorited,
'similar_threads' : thread.get_similar_threads(),
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 232dbaea..9a2de128 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -24,6 +24,7 @@ from django.core import exceptions
from django.conf import settings
from django.views.decorators import csrf
+from askbot import exceptions as askbot_exceptions
from askbot import forms
from askbot import models
from askbot.skins.loaders import render_into_skin
@@ -499,6 +500,10 @@ def answer(request, id):#process a new answer
timestamp = update_time,
)
return HttpResponseRedirect(answer.get_absolute_url())
+ except askbot_exceptions.AnswerAlreadyGiven, e:
+ request.user.message_set.create(message = unicode(e))
+ answer = question.thread.get_answers_by_user(request.user)[0]
+ return HttpResponseRedirect(answer.get_absolute_url())
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
else: