diff options
-rwxr-xr-x | .gitignore | 1 | ||||
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | askbot/conf/forum_data_rules.py | 14 | ||||
-rw-r--r-- | askbot/forms.py | 30 | ||||
-rw-r--r-- | askbot/models/tag.py | 13 | ||||
-rw-r--r-- | askbot/skins/default/templates/ask.html | 3 | ||||
-rw-r--r-- | askbot/skins/default/templates/blocks/ask_form.html | 9 | ||||
-rw-r--r-- | askbot/skins/default/templates/blocks/mandatory_tags_js.html | 25 | ||||
-rw-r--r-- | askbot/skins/default/templates/macros.html | 30 | ||||
-rw-r--r-- | askbot/skins/default/templates/question_edit.html | 14 | ||||
-rw-r--r-- | askbot/tests/form_tests.py | 24 | ||||
-rw-r--r-- | askbot/views/writers.py | 2 |
12 files changed, 158 insertions, 8 deletions
@@ -9,6 +9,7 @@ settings_local.py settings.py .idea *.iml +lint env django django/* diff --git a/MANIFEST.in b/MANIFEST.in index 0f0423d0..4788b4eb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ recursive-exclude .git prune dist prune tmp prune build +exclude lint exclude settings.py exclude manage.py exclude __init__.py diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py index dc801d41..570b8b9e 100644 --- a/askbot/conf/forum_data_rules.py +++ b/askbot/conf/forum_data_rules.py @@ -87,6 +87,20 @@ settings.register( ) settings.register( + livesettings.StringValue( + FORUM_DATA_RULES, + 'MANDATORY_TAGS', + description = _('Mandatory tags'), + default = '', + help_text = _( + 'At least one of these tags will be required for any new ' + 'or newly edited question. A mandatory tag may be wildcard, ' + 'if the wildcard tags are active.' + ) + ) +) + +settings.register( livesettings.BooleanValue( FORUM_DATA_RULES, 'FORCE_LOWERCASE_TAGS', diff --git a/askbot/forms.py b/askbot/forms.py index 2ab67213..57775ed0 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -4,6 +4,7 @@ from askbot import models from askbot import const from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext_lazy +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 @@ -135,6 +136,27 @@ class TagNamesField(forms.CharField): self.help_text = _('Tags are short keywords, with no spaces within. Up to five tags can be used.') self.initial = '' + def need_mandatory_tags(self): + """true, if list of mandatory tags is not empty""" + return 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``""" + 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): value = super(TagNamesField, self).clean(value) data = value.strip() @@ -152,6 +174,14 @@ class TagNamesField(forms.CharField): 'please use %(tag_count)d tags or less', tag_count) % {'tag_count':max_tags} raise forms.ValidationError(msg) + + if self.need_mandatory_tags(): + if self.mandatory_tag_missing(tag_strings): + msg = _( + 'At least one of the following tags is required : %(tags)s' + ) % {'tags': get_text_list(models.tag.get_mandatory_tags())} + raise forms.ValidationError(msg) + for tag in tag_strings: tag_length = len(tag) if tag_length > askbot_settings.MAX_TAG_LENGTH: diff --git a/askbot/models/tag.py b/askbot/models/tag.py index 73aa328f..e114cef7 100644 --- a/askbot/models/tag.py +++ b/askbot/models/tag.py @@ -1,9 +1,11 @@ +import re from django.db import models from django.db import connection, transaction from django.contrib.auth.models import User from django.utils.translation import ugettext as _ from askbot.models.base import DeletableContent from askbot.models.base import BaseQuerySetManager +from askbot import const def tags_match_some_wildcard(tag_names, wildcard_tags): """Same as @@ -16,6 +18,17 @@ def tags_match_some_wildcard(tag_names, wildcard_tags): return True return False +def get_mandatory_tags(): + """returns list of mandatory tags, + or an empty list, if there aren't any""" + from askbot.conf import settings as askbot_settings + raw_mandatory_tags = askbot_settings.MANDATORY_TAGS.strip() + if len(raw_mandatory_tags) == 0: + return [] + else: + split_re = re.compile(const.TAG_SPLIT_REGEX) + return split_re.split(raw_mandatory_tags) + class TagQuerySet(models.query.QuerySet): UPDATE_USED_COUNTS_QUERY = """ UPDATE tag diff --git a/askbot/skins/default/templates/ask.html b/askbot/skins/default/templates/ask.html index 508cc16e..9aaa7e8c 100644 --- a/askbot/skins/default/templates/ask.html +++ b/askbot/skins/default/templates/ask.html @@ -21,6 +21,9 @@ </script> <script type='text/javascript' src='{{"/js/live_search.js"|media}}'></script> {% include "blocks/editor_data.html" %} + {% if mandatory_tags %} + {% include "blocks/mandatory_tags_js.html" %} + {% endif %} <script type='text/javascript'> askbot['urls']['api_get_questions'] = '{% url api_get_questions %}'; {% if settings.ENABLE_MATHJAX or settings.MARKUP_CODE_FRIENDLY %} diff --git a/askbot/skins/default/templates/blocks/ask_form.html b/askbot/skins/default/templates/blocks/ask_form.html index 24196bb6..606aef78 100644 --- a/askbot/skins/default/templates/blocks/ask_form.html +++ b/askbot/skins/default/templates/blocks/ask_form.html @@ -22,7 +22,14 @@ </div> </div> <div id='question-list'></div> - {{macros.edit_post(form, post_type='question', edit_title=False)}} + {{ + macros.edit_post( + form, + post_type = 'question', + edit_title = False, + mandatory_tags = mandatory_tags + ) + }} {% if not request.user.is_authenticated() %} <input type="submit" name="post_anon" value="{% trans %}Login/signup to post your question{% endtrans %}" class="submit" /> {% else %} diff --git a/askbot/skins/default/templates/blocks/mandatory_tags_js.html b/askbot/skins/default/templates/blocks/mandatory_tags_js.html new file mode 100644 index 00000000..f04a6345 --- /dev/null +++ b/askbot/skins/default/templates/blocks/mandatory_tags_js.html @@ -0,0 +1,25 @@ +<script type="text/javascript"> + $(document).ready(function(){ + var fake_tag = new Tag(); + $('ul.tags .tag').each(function(idx, elem){ + var tag_input = $('input#id_tags'); + var callback = function(){ + var new_tag = fake_tag.decodeTagName($(elem).html()); + if (/\*$/.test(new_tag)){//strip the asterisk + new_tag = new_tag.substring(0, new_tag.length - 1); + } + if ($.trim(tag_input.val()) !== ''){ + var entered_tags = tag_input.val().split(/\s+/); + } else { + var entered_tags = []; + } + if ($.inArray(new_tag, entered_tags) === -1){ + entered_tags.push(new_tag); + tag_input.val(entered_tags.join(' ')); + tag_input.focus(); + } + }; + setupButtonEventHandlers($(elem), callback); + }); + }); +</script> diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index 23177c0b..987b3332 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -353,6 +353,7 @@ poor design of the data or methods on data objects #} <{{ html_tag }} class="tag-left{% if deletable %} deletable-tag{% endif %}"> <{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %} class="tag tag-right{% if css_class %} {{ css_class }}{% endif %}" + {% if is_link %} href="{% url questions %}?tags={{tag|urlencode}}{{ if_else( url_params != None, @@ -360,7 +361,9 @@ poor design of the data or methods on data objects #} '' )|escape }}" - title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" rel="tag" + title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" + {% endif %} + rel="tag" >{{ tag|replace('*', '✽') }}</{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}> {% if deletable %} <span class="delete-icon" @@ -612,8 +615,9 @@ poor design of the data or methods on data objects #} {%- macro edit_post( post_form, - post_type=None, - edit_title=False + post_type = None, + mandatory_tags = None, + edit_title = False ) -%} {% if edit_title %} @@ -633,8 +637,24 @@ poor design of the data or methods on data objects #} {# need label element for resizable input, b/c form validation won't find span #} {% if post_type == 'question' %} <div class="form-item"> - <strong>{{ post_form.tags.label_tag() }}:</strong> - {% trans %}(required){% endtrans %} + {% if mandatory_tags %} + <label for="id_tags"> + <strong>{% trans %}tags{% endtrans %},</strong> + {% trans %}one of these is required{% endtrans %} + </label> + {{ + tag_list_widget( + mandatory_tags, + make_links = False, + css_class = 'clearfix' + ) + }} + {% else %} + <label for="id_tags"> + <strong>{% trans %}tags{% endtrans %}:</strong> + {% trans %}(required){% endtrans %} + </label> + {% endif %} <span class="form-error">{{ post_form.tags.errors }}</span><br/> {{ post_form.tags }} <div class="title-desc"> diff --git a/askbot/skins/default/templates/question_edit.html b/askbot/skins/default/templates/question_edit.html index 6a55e58c..feee2521 100644 --- a/askbot/skins/default/templates/question_edit.html +++ b/askbot/skins/default/templates/question_edit.html @@ -8,14 +8,21 @@ {% 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" >{% 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"> {{ revision_form.revision }} <input type="submit" style="display:none" id="select_revision" name="select_revision" value="{% trans %}select revision{% endtrans %}"> </div> - {{ macros.edit_post(form, post_type='question', edit_title=True,) }} + {{ + macros.edit_post( + form, + post_type='question', + edit_title=True, + mandatory_tags = mandatory_tags + ) + }} <div class="after-editor"> <input type="submit" value="{% trans %}Save edit{% endtrans %}" class="submit" /> <input type="button" value="{% trans %}Cancel{% endtrans %}" class="submit" onclick="history.back(-1);" /> @@ -38,6 +45,9 @@ {% block endjs %} {% include "blocks/editor_data.html" %} <script type='text/javascript' src='{{"/js/editor.js"|media }}'></script> + {% if mandatory_tags %} + {% include "blocks/mandatory_tags_js.html" %} + {% endif %} <script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script> <script type='text/javascript' src='{{"/js/post.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> diff --git a/askbot/tests/form_tests.py b/askbot/tests/form_tests.py index 68fb7010..519d81c0 100644 --- a/askbot/tests/form_tests.py +++ b/askbot/tests/form_tests.py @@ -1,3 +1,4 @@ +from django import forms as django_forms from askbot.tests.utils import AskbotTestCase from askbot.conf import settings as askbot_settings from askbot import forms @@ -88,6 +89,9 @@ class TagNamesFieldTests(AskbotTestCase): self.field = forms.TagNamesField() self.user = self.create_user('user1') + def tearDown(self): + askbot_settings.update('MANDATORY_TAGS', '') + def clean(self, value): return self.field.clean(value).strip().split(' ') @@ -110,6 +114,26 @@ class TagNamesFieldTests(AskbotTestCase): cleaned_tags = self.clean('tag1 taG2 TAG1 tag3 tag3') self.assert_tags_equal(cleaned_tags, ['TAG1', 'Tag2', 'tag3']) + def test_catch_missing_mandatory_tag(self): + askbot_settings.update('MANDATORY_TAGS', 'one two') + self.assertRaises( + django_forms.ValidationError, + self.clean, + ('three',) + ) + + def test_pass_with_entered_mandatory_tag(self): + askbot_settings.update('MANDATORY_TAGS', 'one two') + cleaned_tags = self.clean('one') + self.assert_tags_equal(cleaned_tags, ['one',]) + + def test_pass_with_entered_wk_mandatory_tag(self): + askbot_settings.update('MANDATORY_TAGS', 'one* two') + askbot_settings.update('USE_WILDCARD_TAGS', True) + cleaned_tags = self.clean('oneness') + self.assert_tags_equal(cleaned_tags, ['oneness',]) + + class EditQuestionAnonymouslyFormTests(AskbotTestCase): """setup the following truth table on reveal_identity field: diff --git a/askbot/views/writers.py b/askbot/views/writers.py index c5a69c1d..0cfae751 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -258,6 +258,7 @@ def ask(request):#view used to ask a new question 'active_tab': 'ask', 'page_class': 'ask-page', 'form' : form, + 'mandatory_tags': models.tag.get_mandatory_tags(), 'email_validation_faq_url':reverse('faq') + '#validate', } return render_into_skin('ask.html', data, request) @@ -394,6 +395,7 @@ def edit_question(request, id): 'active_tab': 'questions', 'question': question, 'revision_form': revision_form, + 'mandatory_tags': models.tag.get_mandatory_tags(), 'form' : form, } return render_into_skin('question_edit.html', data, request) |