diff options
40 files changed, 532 insertions, 80 deletions
diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py index 7d98c9e8..a26aa4b3 100644 --- a/askbot/conf/forum_data_rules.py +++ b/askbot/conf/forum_data_rules.py @@ -200,6 +200,21 @@ settings.register( ) ) +MARKED_TAG_DISPLAY_CHOICES = ( + ('always', _('Always, for all users')), + ('never', _('Never, for all users')), + ('when-user-wants', _('Let users decide')) +) +settings.register( + livesettings.StringValue( + FORUM_DATA_RULES, + 'MARKED_TAGS_ARE_PUBLIC_WHEN', + default = 'always', + choices = MARKED_TAG_DISPLAY_CHOICES, + description = _('Publicly show user tag selections') + ) +) + settings.register( livesettings.BooleanValue( FORUM_DATA_RULES, diff --git a/askbot/const/message_keys.py b/askbot/const/message_keys.py index 0ee99e0c..9ac82e57 100644 --- a/askbot/const/message_keys.py +++ b/askbot/const/message_keys.py @@ -33,6 +33,9 @@ _('click to see the most answered questions') _('click to see least voted questions') _('by votes') _('click to see most voted questions') +_('interesting') +_('ignored') +_('subscribed') def get_i18n_message(key): messages = { diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 5cd1def9..430d98de 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -4,13 +4,14 @@ Changes in Askbot Development version ------------------- * Welcome email for the case when replying by email is enabled (Evgeny) -* Detection of email signature based on the response to the welome email (Evgeny) +* Detection of email signature based on the response to the welcome email (Evgeny) * Hide "website" and "about" section of the blocked user profiles to help prevent user profile spam (Evgeny) * Added a function to create a custom user profile tab, the feature requires access to the server (Evgeny) * Added optional top banner to the question page (Evgeny) * Made "bootstrap mode" default and created instead "large site mode" (Evgeny) +* Added interesting/ignored/subscribed tags to the user profile page (Paul Backhouse, Evgeny) 0.7.43 (May 14, 2012) --------------------- diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst index a0881532..69348b84 100644 --- a/askbot/doc/source/contributors.rst +++ b/askbot/doc/source/contributors.rst @@ -40,6 +40,7 @@ Programming and documentation * `Jim Tittsler <http://wikieducator.org/User:JimTittsler>`_ * Silvio Heuberger * `Alexandros <https://github.com/alexandros-z>`_ +* `Paul Backhouse <https://github.com/powlo>`_ Translations ------------ diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst index 4efbef6e..b96251dc 100644 --- a/askbot/doc/source/management-commands.rst +++ b/askbot/doc/source/management-commands.rst @@ -195,5 +195,5 @@ the developers of the Askbot project: +--------------------------------+-------------------------------------------------------------+ | `askbot_add_test_content` | Creates content with dummy data for testing | +--------------------------------+-------------------------------------------------------------+ -| `askbot_create_test_fixture` | Creates a test fixture at `askbot/tests/test_data.json` | +| `askbot_create_test_fixture` | Creates a test fixture at `askbot/tests/test_data.json` | +--------------------------------+-------------------------------------------------------------+ diff --git a/askbot/feed.py b/askbot/feed.py index 776aad5e..285bb452 100644 --- a/askbot/feed.py +++ b/askbot/feed.py @@ -123,7 +123,7 @@ class RssLastestQuestionsFeed(Feed): def item_author_link(self, item): """get url of the author's profile """ - return item.author.get_profile_url() + return askbot_settings.APP_URL + item.author.get_profile_url() def item_pubdate(self, item): """get date of creation for the item diff --git a/askbot/forms.py b/askbot/forms.py index cced18e9..81382a2c 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -979,6 +979,11 @@ class EditUserForm(forms.Form): required=False ) + show_marked_tags = forms.BooleanField( + label=_('Show tag choices'), + required=False + ) + birthday = forms.DateField( label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), @@ -1009,6 +1014,7 @@ class EditUserForm(forms.Form): country = user.country self.fields['country'].initial = country self.fields['show_country'].initial = user.show_country + self.fields['show_marked_tags'].initial = user.show_marked_tags if user.date_of_birth is not None: self.fields['birthday'].initial = user.date_of_birth diff --git a/askbot/management/commands/askbot_add_test_content.py b/askbot/management/commands/askbot_add_test_content.py index 6b14df59..ace629d0 100644 --- a/askbot/management/commands/askbot_add_test_content.py +++ b/askbot/management/commands/askbot_add_test_content.py @@ -14,21 +14,23 @@ NUM_COMMENTS = 20 # karma. This can be calculated dynamically - max of MIN_REP_TO_... settings INITIAL_REPUTATION = 500 +BAD_STUFF = "<script>alert('hohoho')</script>" + # Defining template inputs. -USERNAME_TEMPLATE = "test_user_%s" +USERNAME_TEMPLATE = BAD_STUFF + "test_user_%s" PASSWORD_TEMPLATE = "test_password_%s" EMAIL_TEMPLATE = "test_user_%s@askbot.org" -TITLE_TEMPLATE = "Test question title No.%s" -TAGS_TEMPLATE = ["tag-%s-0", "tag-%s-1"] # len(TAGS_TEMPLATE) tags per question +TITLE_TEMPLATE = "Test question title No.%s" + BAD_STUFF +TAGS_TEMPLATE = [BAD_STUFF + "tag-%s-0", BAD_STUFF + "tag-%s-1"] # len(TAGS_TEMPLATE) tags per question -CONTENT_TEMPLATE = """Lorem lean startup ipsum product market fit customer +CONTENT_TEMPLATE = BAD_STUFF + """Lorem lean startup ipsum product market fit customer development acquihire technical cofounder. User engagement **A/B** testing *shrink* a market venture capital pitch.""" -ANSWER_TEMPLATE = """Accelerator photo sharing business school drop out ramen +ANSWER_TEMPLATE = BAD_STUFF + """Accelerator photo sharing business school drop out ramen hustle crush it revenue traction platforms.""" -COMMENT_TEMPLATE = """Main differentiators business model micro economics +COMMENT_TEMPLATE = BAD_STUFF + """Main differentiators business model micro economics marketplace equity augmented reality human computer""" diff --git a/askbot/migrations/0125_add_show_tags_field_to_user.py b/askbot/migrations/0125_add_show_tags_field_to_user.py new file mode 100644 index 00000000..cb7ab2bd --- /dev/null +++ b/askbot/migrations/0125_add_show_tags_field_to_user.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model show_marked_tags fields to the model auth_user + try: + db.add_column( + u'auth_user', + 'show_marked_tags', + self.gf( + 'django.db.models.fields.BooleanField' + )(default=True, blank=True) + ) + except: + pass + + + def backwards(self, orm): + + # Deleting show_marked_tags fields + db.delete_column(u'auth_user', 'show_marked_tags') + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.post': { + 'Meta': {'object_name': 'Post'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['askbot.Thread']"}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.postrevision': { + 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.replyaddress': { + 'Meta': {'object_name': 'ReplyAddress'}, + 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}), + 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'to': "orm['askbot.Post']"}), + 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.tag': { + 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.thread': { + 'Meta': {'object_name': 'Thread'}, + 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index b686818f..c4c11cb4 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -13,6 +13,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.contrib.auth.models import User from django.utils.safestring import mark_safe +from django.utils.html import escape from django.db import models from django.conf import settings as django_settings from django.contrib.contenttypes.models import ContentType @@ -38,6 +39,7 @@ from askbot.models.repute import Award, Repute, Vote from askbot import auth from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify +from askbot.utils.html import sanitize_html from askbot.utils.diff import textDiff as htmldiff from askbot.utils.url_utils import strip_path from askbot import mail @@ -82,7 +84,7 @@ User.add_to_class( ) ) -User.add_to_class('email_isvalid', models.BooleanField(default=False)) +User.add_to_class('email_isvalid', models.BooleanField(default=False)) #@UndefinedVariable User.add_to_class('email_key', models.CharField(max_length=32, null=True)) #hardcoded initial reputaion of 1, no setting for this one User.add_to_class('reputation', @@ -122,6 +124,8 @@ User.add_to_class('interesting_tags', models.TextField(blank = True)) User.add_to_class('ignored_tags', models.TextField(blank = True)) User.add_to_class('subscribed_tags', models.TextField(blank = True)) User.add_to_class('email_signature', models.TextField(blank = True)) +User.add_to_class('show_marked_tags', models.BooleanField(default = True)) + User.add_to_class( 'email_tag_filter_strategy', models.SmallIntegerField( @@ -237,6 +241,42 @@ def user_get_old_vote_for_post(self, post): except Vote.MultipleObjectsReturned: raise AssertionError +def user_get_marked_tags(self, reason): + """reason is a type of mark: good, bad or subscribed""" + assert(reason in ('good', 'bad', 'subscribed')) + if reason == 'subscribed': + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED == False: + return Tag.objects.none() + + return Tag.objects.filter( + user_selections__user = self, + user_selections__reason = reason + ) + +MARKED_TAG_PROPERTY_MAP = { + 'good': 'interesting_tags', + 'bad': 'ignored_tags', + 'subscribed': 'subscribed_tags' +} +def user_get_marked_tag_names(self, reason): + """returns list of marked tag names for a give + reason: good, bad, or subscribed + will add wildcard tags as well, if used + """ + if reason == 'subscribed': + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED == False: + return list() + + tags = self.get_marked_tags(reason) + tag_names = list(tags.values_list('name', flat = True)) + + if askbot_settings.USE_WILDCARD_TAGS: + attr_name = MARKED_TAG_PROPERTY_MAP[reason] + wildcard_tags = getattr(self, attr_name).split() + tag_names.extend(wildcard_tags) + + return tag_names + def user_has_affinity_to_question(self, question = None, affinity_type = None): """returns True if number of tag overlap of the user tag selection with the question is 0 and False otherwise @@ -2056,7 +2096,7 @@ def user_get_absolute_url(self): def get_profile_link(self): profile_link = u'<a href="%s">%s</a>' \ - % (self.get_profile_url(),self.username) + % (self.get_profile_url(), escape(self.username)) return mark_safe(profile_link) @@ -2476,6 +2516,8 @@ User.add_to_class('get_absolute_url', user_get_absolute_url) User.add_to_class('get_avatar_url', user_get_avatar_url) User.add_to_class('get_default_avatar_url', user_get_default_avatar_url) User.add_to_class('get_gravatar_url', user_get_gravatar_url) +User.add_to_class('get_marked_tags', user_get_marked_tags) +User.add_to_class('get_marked_tag_names', user_get_marked_tag_names) User.add_to_class('strip_email_signature', user_strip_email_signature) User.add_to_class('get_groups_membership_info', user_get_groups_membership_info) User.add_to_class('get_anonymous_name', user_get_anonymous_name) @@ -2644,8 +2686,8 @@ def format_instant_notification_email( revisions = post.revisions.all()[:2] assert(len(revisions) == 2) content_preview = htmldiff( - revisions[1].html, - revisions[0].html, + sanitize_html(revisions[1].html), + sanitize_html(revisions[0].html), ins_start = '<b><u style="background-color:#cfc">', ins_end = '</u></b>', del_start = '<del style="color:#600;background-color:#fcc">', diff --git a/askbot/models/post.py b/askbot/models/post.py index 673b4c6f..c08b940b 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -399,9 +399,8 @@ class Post(models.Model): extra_authors = set() for name_seed in extra_name_seeds: - extra_authors.update(User.objects.filter( - username__istartswith = name_seed - ) + extra_authors.update( + User.objects.filter(username__istartswith = name_seed) ) #it is important to preserve order here so that authors of post @@ -470,9 +469,12 @@ class Post(models.Model): #because generic relation needs primary key of the related object super(self.__class__, self).save(**kwargs) if last_revision: - diff = htmldiff(last_revision, self.html) + diff = htmldiff( + sanitize_html(last_revision), + sanitize_html(self.html) + ) else: - diff = self.get_snippet() + diff = sanitize_html(self.get_snippet()) timestamp = self.get_time_of_last_edit() diff --git a/askbot/models/repute.py b/askbot/models/repute.py index 09e74067..33ec3a42 100644 --- a/askbot/models/repute.py +++ b/askbot/models/repute.py @@ -1,9 +1,10 @@ +import datetime from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext as _ -import datetime +from django.utils.html import escape from askbot import const from django.core.urlresolvers import reverse @@ -242,7 +243,7 @@ class Repute(models.Model): return '<a href="%(url)s" title="%(link_title)s">%(question_title)s</a>' \ % { 'url': self.question.get_absolute_url(), - 'question_title': self.question.thread.title, - 'link_title': link_title + 'question_title': escape(self.question.thread.title), + 'link_title': escape(link_title) } diff --git a/askbot/models/user.py b/askbot/models/user.py index 70aee263..e4077ea5 100644 --- a/askbot/models/user.py +++ b/askbot/models/user.py @@ -372,6 +372,7 @@ class GroupProfile(models.Model): ) class Meta: + #added to make account merges work properly app_label = 'askbot' def can_accept_user(self, user): diff --git a/askbot/skins/common/media/js/tag_selector.js b/askbot/skins/common/media/js/tag_selector.js index d5482992..be79a9b6 100644 --- a/askbot/skins/common/media/js/tag_selector.js +++ b/askbot/skins/common/media/js/tag_selector.js @@ -72,6 +72,7 @@ TagDetailBox.prototype.renderFor = function(wildcard){ $.each(me._tag_names, function(idx, name){ var tag = new Tag(); tag.setName(name); + tag.setUrlParams(name) //tag.setLinkable(false); me._tags.push(tag); me._tag_list_element.append(tag.getElement()); diff --git a/askbot/skins/common/templates/question/closed_question_info.html b/askbot/skins/common/templates/question/closed_question_info.html index 87d9379c..f6f3f557 100644 --- a/askbot/skins/common/templates/question/closed_question_info.html +++ b/askbot/skins/common/templates/question/closed_question_info.html @@ -1,5 +1,5 @@ <div class="question-status"> <h3>{% trans close_reason=thread.get_close_reason_display() %}The question has been closed for the following reason <b>"{{ close_reason }}"</b> <i>by{% endtrans %} - <a href="{{ thread.closed_by.get_profile_url() }}">{{ thread.closed_by.username }}</a> </i><br> + <a href="{{ thread.closed_by.get_profile_url() }}">{{ thread.closed_by.username|escape }}</a> </i><br> {% trans closed_at=thread.closed_at %}close date {{closed_at}}{% endtrans %}</h3> </div> diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index 343204fe..f5316829 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -1159,6 +1159,10 @@ ul.tags.marked-tags li, ul#ab-user-tags li { width: 160px; margin:5px; + margin-left: 0; +} +.tags-page ul.tags { + margin-left: 5px; } ul#related-tags li { @@ -3293,19 +3297,26 @@ p.signup_p { .avatar-page li { display: inline; } -.user-profile-page .avatar p { - margin-bottom: 0px; -} -.user-profile-page .tabBar a#stats { - margin-left: 0; -} -.user-profile-page img.gravatar { - margin: 2px 0 3px 0; -} -.user-profile-page h3 { - padding: 0; - margin-top: -3px; + +.user-profile-page { + .avatar p { + margin-bottom: 0px; + } + .tabBar a#stats { + margin-left: 0; + } + img.gravatar { + margin: 2px 0 3px 0; + } + h3 { + padding: 0; + margin-top: -3px; + } + ul.tags { + margin-left: 5px; + } } + .userList { font-size: 13px; } diff --git a/askbot/skins/default/templates/badge.html b/askbot/skins/default/templates/badge.html index d1f75617..b2c4ce8b 100644 --- a/askbot/skins/default/templates/badge.html +++ b/askbot/skins/default/templates/badge.html @@ -20,7 +20,7 @@ <div class="user"> <ul> <li class="thumb">{{ gravatar(recipient, 32) }}</li> - <li><a href="{{ recipient.get_absolute_url() }}">{{recipient.username}}</a></li> + <li><a href="{{ recipient.get_absolute_url() }}">{{recipient.username|escape}}</a></li> <li>{{ macros.user_score_and_badge_summary(recipient) }}</li> </ul> </div> diff --git a/askbot/skins/default/templates/email/ask_for_signature.html b/askbot/skins/default/templates/email/ask_for_signature.html index e4449433..cafeee2b 100644 --- a/askbot/skins/default/templates/email/ask_for_signature.html +++ b/askbot/skins/default/templates/email/ask_for_signature.html @@ -1,6 +1,6 @@ {% import "email/macros.html" as macros %} <p style="{{ macros.heading_style() }}"> - {% trans %}{{ username }}, please reply to this message.{% endtrans %} + {% trans user=username|escape %}{{ user }}, please reply to this message.{% endtrans %} </p> <p> {% trans %}Your post could not be published, because we could not detect signature in your email.{% endtrans %}<br/> diff --git a/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html b/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html index da4c93ca..284cc1b0 100644 --- a/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html +++ b/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html @@ -6,7 +6,7 @@ * site_link - html for the link #} <p style="{{ macros.heading_style() }}"> - {% trans %}{{ username }}, your question could not be posted by email just yet.{% endtrans %} + {% trans user=username|escape %}{{ username }}, your question could not be posted by email just yet.{% endtrans %} </p> <p> {% trans %}To make posts by email, you need to receive about {{min_upvotes}} upvotes.{% endtrans %}<br/> diff --git a/askbot/skins/default/templates/email/macros.html b/askbot/skins/default/templates/email/macros.html index 1acbf515..d7817bf9 100644 --- a/askbot/skins/default/templates/email/macros.html +++ b/askbot/skins/default/templates/email/macros.html @@ -7,7 +7,7 @@ %} {% spaceless %} {{ start_quote(quote_level) }} - {% set author = post.author.username %} + {% set author = post.author.username|escape %} {% if post.post_type == 'question' %} <p> {% if format == 'parent_subthread' %} diff --git a/askbot/skins/default/templates/feedback.html b/askbot/skins/default/templates/feedback.html index 85b5d00a..04b9a5b4 100644 --- a/askbot/skins/default/templates/feedback.html +++ b/askbot/skins/default/templates/feedback.html @@ -11,7 +11,7 @@ <form method="post" action="{% url feedback %}" accept-charset="utf-8">{% csrf_token %} {% if user.is_authenticated() %} <p class="message"> - {% trans user_name=user.username %} + {% trans user_name=user.username|escape %} <span class='big strong'>Dear {{user_name}}</span>, we look forward to hearing your feedback. Please type and send us your message below. {% endtrans %} diff --git a/askbot/skins/default/templates/help.html b/askbot/skins/default/templates/help.html index 7dc58f5d..204fc086 100644 --- a/askbot/skins/default/templates/help.html +++ b/askbot/skins/default/templates/help.html @@ -4,7 +4,7 @@ <h1 class='section-title'>{% trans %}Help{% endtrans %}</h1> <p> {% if request.user.is_authenticated() %} - {% trans username = request.user.username %}Welcome {{username}},{% endtrans %} + {% trans username = request.user.username|escape %}Welcome {{username}},{% endtrans %} {% else %} {% trans %}Welcome,{% endtrans %} {% endif %} diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index 485713aa..3e463c1c 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -10,7 +10,7 @@ {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #} <div class="follow-toggle follow-user-toggle" - id="follow-{{ name }}-{{ id }}" + id="follow-{{ name|escape }}-{{ id }}" > {% if follow %} <div class="follow">{% trans %}follow {{alias}}{% endtrans %}</div> @@ -29,18 +29,18 @@ <div class="face"> {{ gravatar(response.user, 48) }} </div> - <a style="font-size:12px" href="{{ response.user.get_absolute_url() }}">{{ response.user.username }}</a> + <a style="font-size:12px" href="{{ response.user.get_absolute_url() }}">{{ response.user.username|escape }}</a> <a style="text-decoration:none;" href="{{ response.response_url }}"> {{ response.response_type }} ({{ timeago(response.timestamp) }}):<br/> {% if inbox_section != 'flags' %} - {{ response.response_snippet }} + {{ response.response_snippet|escape }} {% endif %} </a> {% if inbox_section == 'flags' %} <a class="re_expand" href="{{ response.response_url }}"> - <!--div class="re_snippet">{{ response.response_snippet }}</div--> - <div class="re_content">{{ response.response_content }}</div> + <!--div class="re_snippet">{{ response.response_snippet|escape }}</div--> + <div class="re_content">{{ response.response_content|escape }}</div> </a> {% endif %} </div> @@ -291,14 +291,14 @@ poor design of the data or methods on data objects #} class="tag tag-right{% if css_class %} {{ css_class }}{% endif %}" {% if is_link %} href="{{ search_state.add_tag(tag).full_url() }}" - title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" + title="{% trans tag=tag|escape %}see questions tagged '{{ tag }}'{% endtrans %}" {% endif %} rel="tag" data-tag-name="{{ tag|replace('*', '✽')|escape }}" >{% if truncate_long_tag -%} - {{ tag|replace('*', '✽')|truncate(17, True) }} + {{ tag|replace('*', '✽')|truncate(17, True)|escape }} {%- else -%} - {{ tag|replace('*', '✽') }} + {{ tag|replace('*', '✽')|escape }} {%- endif %}</{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}> {% if deletable %} <div class="delete-icon" @@ -402,7 +402,7 @@ for the purposes of the AJAX comment editor #} </div> <div class="comment-body"> {{comment.html}} - <a class="author" href="{{comment.author.get_profile_url()}}">{{comment.author.username}}</a> + <a class="author" href="{{comment.author.get_profile_url()}}">{{comment.author.username|escape}}</a> <span class="age"> ({{ timeago(comment.added_at) }})</span> <a id="post-{{comment.id}}-edit" class="edit">{% trans %}edit{% endtrans %}</a> @@ -546,13 +546,13 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {%- macro follow_user_toggle(visitor = None, subject = None) -%} {% if visitor.is_anonymous() %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} + {{ follow_toggle(True, 'user', subject.username|escape, subject.id) }} {% else %} {% if visitor != subject %} {% if visitor.is_following(subject) %} - {{ follow_toggle(False, 'user', subject.username, subject.id) }} + {{ follow_toggle(False, 'user', subject.username|escape, subject.id) }} {% else %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} + {{ follow_toggle(True, 'user', subject.username|escape, subject.id) }} {% endif %} {% endif %} {% endif %} @@ -572,7 +572,7 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ endtrans %}" title="{% trans country=user.country.name, - person=user.username %}{{person}} is from {{country}}{% + person=user.username|escape %}{{person}} is from {{country}}{% endtrans %}" /> {% endif %} @@ -607,8 +607,8 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ ><img class="gravatar" width="{{size}}" height="{{size}}" src="{{ user.get_avatar_url(size) }}" - title="{{user.username}}" - alt="{% trans username=user.username %}{{username}} gravatar image{% endtrans %}" + title="{{user.username|escape}}" + alt="{% trans username=user.username|escape %}{{username}} gravatar image{% endtrans %}" /></a> {% endspaceless %} {%- endmacro -%} @@ -708,7 +708,7 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {% if user.new_response_count > 0 or user.seen_response_count > 0 %} <a id='ab-responses' href="{{user.get_absolute_url()}}?sort=inbox§ion=forum"> <img - alt="{% trans username=user.username %}responses for {{username}}{% endtrans %}" + alt="{% trans username=user.username|escape %}responses for {{username}}{% endtrans %}" {% if user.new_response_count > 0 %} src="{{ "/images/mail-envelope-full.png"|media }}" title="{% trans response_count=user.new_response_count %}you have {{response_count}} new response{% pluralize %}you have {{response_count}} new responses{% endtrans %}" diff --git a/askbot/skins/default/templates/main_page/javascript.html b/askbot/skins/default/templates/main_page/javascript.html index d968dcd5..10f8ede2 100644 --- a/askbot/skins/default/templates/main_page/javascript.html +++ b/askbot/skins/default/templates/main_page/javascript.html @@ -27,4 +27,4 @@ {% if request.user.is_authenticated() %} <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> {% endif %} -<script type="text/javascript" src="{{"/js/live_search.js"|media}}"></script> +<script type="text/javascript" src='{{"/js/live_search.js"|media}}'></script> diff --git a/askbot/skins/default/templates/reopen.html b/askbot/skins/default/templates/reopen.html index 894fa3a0..52d926ce 100644 --- a/askbot/skins/default/templates/reopen.html +++ b/askbot/skins/default/templates/reopen.html @@ -10,7 +10,7 @@ </a> </p> <p>{% trans %}This question has been closed by - <a href="{{closed_by_profile_url}}">{{closed_by_username}}</a> + <a href="{{closed_by_profile_url}}">{{closed_by_username|escape}}</a> {% endtrans %} </p> <p> diff --git a/askbot/skins/default/templates/user_profile/macros.html b/askbot/skins/default/templates/user_profile/macros.html new file mode 100644 index 00000000..ac573553 --- /dev/null +++ b/askbot/skins/default/templates/user_profile/macros.html @@ -0,0 +1,24 @@ +{% import "macros.html" as macros %} + +{% macro tag_selection(tag_names, selection_type) %} + <a name="{{selection_type}}-tags"></a> + {% spaceless %} + <h2>{% + trans counter=tag_names|length, type=gettext(selection_type)|capitalize + %}<span class="count">{{counter}}</span> {{type}} Tag{% + pluralize + %}<span class="count">{{counter}}</span> {{type}} Tags{% + endtrans + %} + </h2> + {% endspaceless %} + <div class="user-stats-table"> + <table class="tags"> + <tr> + <td valign="top"> + {{ macros.tag_list_widget(tag_names, deletable = False) }} + </td> + </tr> + </table> + </div> +{% endmacro %} diff --git a/askbot/skins/default/templates/user_profile/user.html b/askbot/skins/default/templates/user_profile/user.html index fb40b206..2f06a3c9 100644 --- a/askbot/skins/default/templates/user_profile/user.html +++ b/askbot/skins/default/templates/user_profile/user.html @@ -9,7 +9,7 @@ {% block content %} <h1 class="section-title"> {% spaceless %} - {% trans username=view_user.username %}{{username}}'s profile{% endtrans %} - {% block profilesection %}{% endblock %} + {% trans username=view_user.username|escape %}{{username}}'s profile{% endtrans %} - {% block profilesection %}{% endblock %} {% endspaceless %} </h1> {% include "user_profile/user_tabs.html" %} @@ -21,7 +21,7 @@ {% block endjs %} <script type="text/javascript"> var viewUserID = {{view_user.id}}; - askbot['data']['viewUserName'] = '{{ view_user.username }}'; + askbot['data']['viewUserName'] = '{{ view_user.username|escape }}'; askbot['data']['viewUserId'] = {{view_user.id}}; askbot['urls']['edit_group_membership'] = '{% url edit_group_membership %}'; askbot['urls']['get_groups_list'] = '{% url get_groups_list %}'; diff --git a/askbot/skins/default/templates/user_profile/user_edit.html b/askbot/skins/default/templates/user_profile/user_edit.html index 7735ba93..c95bf815 100644 --- a/askbot/skins/default/templates/user_profile/user_edit.html +++ b/askbot/skins/default/templates/user_profile/user_edit.html @@ -4,7 +4,7 @@ {% block title %}{% spaceless %}{% trans %}Edit user profile{% endtrans %}{% endspaceless %}{% endblock %} {% block content %} <h1 class="section-title"> - {{ request.user.username }} - {% trans %}edit profile{% endtrans %} + {{ request.user.username|escape }} - {% 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">{% csrf_token %} @@ -42,7 +42,7 @@ {{ form.username }} <span class="form-error"> {{ form.username.errors }} </span></td> {% else %} - {{ view_user.username }} + {{ view_user.username|escape }} {% endif %} </td> </tr> @@ -96,6 +96,14 @@ <td style="vertical-align:top">{{ form.about.label_tag() }}:</td> <td>{{ form.about }} <span class="form-error"> {{ form.about.errors }} </span></td> </tr> + {% if marked_tags_setting == 'when-user-wants' %} + <tr> + <td>{{ form.show_marked_tags.label_tag() }}:</td> + <td>{{ form.show_marked_tags }} + <span class="form-error">{{ form.show_marked_tags.errors }}</span> + </td> + </tr> + {% endif %} </table> <div style="margin:30px 0 60px 0"> <input type="submit" value="{% trans %}Update{% endtrans %}" class="submit" > diff --git a/askbot/skins/default/templates/user_profile/user_moderate.html b/askbot/skins/default/templates/user_profile/user_moderate.html index 347ec3af..a7f05b1c 100644 --- a/askbot/skins/default/templates/user_profile/user_moderate.html +++ b/askbot/skins/default/templates/user_profile/user_moderate.html @@ -5,7 +5,7 @@ {% endblock %} {% block usercontent %} {% if request.user != view_user %} - <h3>{% trans username=view_user.username, status=view_user.get_status_display() %}{{username}}'s current status is "{{status}}"{% endtrans %} + <h3>{% trans username=view_user.username|escape, status=view_user.get_status_display() %}{{username}}'s current status is "{{status}}"{% endtrans %} </h3> {% if user_status_changed %} <p class="action-status"><span>{% trans %}User status changed{% endtrans %}</span></p> @@ -40,7 +40,7 @@ </form> {% if request.user != view_user %} <hr/> -<h3>{% trans username=view_user.username %}Send message to {{username}}{% endtrans %}</h3> +<h3>{% trans username=view_user.username|escape %}Send message to {{username}}{% endtrans %}</h3> <p>{% trans %}An email will be sent to the user with 'reply-to' field set to your email address. Please make sure that your address is entered correctly.{% endtrans %}</p> {% if message_sent %} <p class="action-status"><span>{% trans %}Message sent{% endtrans %}</span></p> diff --git a/askbot/skins/default/templates/user_profile/user_network.html b/askbot/skins/default/templates/user_profile/user_network.html index e6134e0c..f64d95b0 100644 --- a/askbot/skins/default/templates/user_profile/user_network.html +++ b/askbot/skins/default/templates/user_profile/user_network.html @@ -32,7 +32,7 @@ {% if request.user == view_user %} <p>{% trans %}Your network is empty. Would you like to follow someone? - Just visit their profiles and click "follow"{% endtrans %}</p> {% else %} - <p>{% trans username = view_user.username %}{{username}}'s network is empty{% endtrans %}</p> + <p>{% trans username = view_user.username|escape %}{{username}}'s network is empty{% endtrans %}</p> {% endif %} {% endif %} {% endblock %} diff --git a/askbot/skins/default/templates/user_profile/user_reputation.html b/askbot/skins/default/templates/user_profile/user_reputation.html index 1bb9b1ba..1cdf014a 100644 --- a/askbot/skins/default/templates/user_profile/user_reputation.html +++ b/askbot/skins/default/templates/user_profile/user_reputation.html @@ -11,7 +11,7 @@ {% if view_user.id == user.id %} <h2>{% trans %}Your karma change log.{% endtrans %}</h2> {% else %} - <h2>{% trans user_name=view_user.username %}{{user_name}}'s karma change log{% endtrans %}</h2> + <h2>{% trans user_name=view_user.username|escape %}{{user_name}}'s karma change log{% endtrans %}</h2> {% endif %} {% for rep in reputation %} <p> diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html index 177df214..2ccc277f 100644 --- a/askbot/skins/default/templates/user_profile/user_stats.html +++ b/askbot/skins/default/templates/user_profile/user_stats.html @@ -1,5 +1,6 @@ {% extends "user_profile/user.html" %} {% import "macros.html" as macros %} +{% import "user_profile/macros.html" as user_profile_macros %} <!-- user_stats.html --> {% block profilesection %} {% trans %}overview{% endtrans %} @@ -9,7 +10,7 @@ {% if settings.GROUPS_ENABLED %} <div id="user-groups"> <h2>{% trans - username = view_user.username + username = view_user.username|escape %}{{username}}'s groups{% endtrans %} </h2> <table id="groups-list"> @@ -110,6 +111,15 @@ </tr> </table> </div> + {% if interesting_tag_names %} + {{ user_profile_macros.tag_selection(interesting_tag_names, 'interesting') }} + {% endif %} + {% if ignored_tag_names %} + {{ user_profile_macros.tag_selection(ignored_tag_names, 'ignored') }} + {% endif %} + {% if subscribed_tag_names %} + {{ user_profile_macros.tag_selection(subscribed_tag_names, 'subscribed') }} + {% endif %} {% if settings.BADGES_MODE == 'public' %} <a name="badges"></a> {% spaceless %} @@ -136,7 +146,7 @@ <a title="{{ award.content_object.get_snippet()|collapse }}" href="{{ award.content_object.get_absolute_url() }}" - >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a> + >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title|escape }}</a> </li> {% endif %} {% endfor %} @@ -164,5 +174,9 @@ }); }); </script> + <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> + <script type="text/javascript"> + askbot['urls']['questions'] = '{% url "questions" %}'; + </script> {% endblock %} <!-- end user_stats.html --> diff --git a/askbot/skins/default/templates/widgets/question_summary.html b/askbot/skins/default/templates/widgets/question_summary.html index c6e7bc5d..5fd51e08 100644 --- a/askbot/skins/default/templates/widgets/question_summary.html +++ b/askbot/skins/default/templates/widgets/question_summary.html @@ -46,7 +46,7 @@ {% if question.is_anonymous %} <span class="anonymous">{{ thread.last_activity_by.get_anonymous_name() }}</span> {% else %} - <a href="{% url user_profile thread.last_activity_by.id, thread.last_activity_by.username|slugify %}">{{thread.last_activity_by.username}}</a> {{ user_country_flag(thread.last_activity_by) }} + <a href="{% url user_profile thread.last_activity_by.id, thread.last_activity_by.username|slugify %}">{{thread.last_activity_by.username|escape}}</a> {{ user_country_flag(thread.last_activity_by) }} {#{user_score_and_badge_summary(thread.last_activity_by)}#} {% endif %} </div> diff --git a/askbot/skins/default/templates/widgets/user_list.html b/askbot/skins/default/templates/widgets/user_list.html index 11f2ed50..e51abc5b 100644 --- a/askbot/skins/default/templates/widgets/user_list.html +++ b/askbot/skins/default/templates/widgets/user_list.html @@ -7,7 +7,7 @@ <div class="user"> <ul> <li class="thumb">{{ gravatar(user, 32) }}</li> - <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username}}</a>{{ user_country_flag(user) }}</li> + <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username|escape}}</a>{{ user_country_flag(user) }}</li> <li>{{ user_score_and_badge_summary( user, diff --git a/askbot/skins/default/templates/widgets/user_navigation.html b/askbot/skins/default/templates/widgets/user_navigation.html index eec7e628..717cd7ee 100644 --- a/askbot/skins/default/templates/widgets/user_navigation.html +++ b/askbot/skins/default/templates/widgets/user_navigation.html @@ -1,5 +1,5 @@ {%- if request.user.is_authenticated() -%} - <a href="{{ request.user.get_absolute_url() }}">{{ request.user.username }}</a> + <a href="{{ request.user.get_absolute_url() }}">{{ request.user.username|escape }}</a> <span class="user-info"> {{ macros.inbox_link(request.user) }} {{ macros.moderation_items_link(request.user, moderation_items) }} diff --git a/askbot/utils/html.py b/askbot/utils/html.py index 1ce3ad35..2e3c1913 100644 --- a/askbot/utils/html.py +++ b/askbot/utils/html.py @@ -5,6 +5,7 @@ import re import htmlentitydefs from urlparse import urlparse from django.core.urlresolvers import reverse +from django.utils.html import escape class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin): acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big', @@ -62,7 +63,7 @@ def site_link(url_name, title): from askbot.conf import settings base_url = urlparse(settings.APP_URL) url = base_url.scheme + '://' + base_url.netloc + reverse(url_name) - return '<a href="%s">%s</a>' % (url, title) + return '<a href="%s">%s</a>' % (url, escape(title)) def unescape(text): """source: http://effbot.org/zone/re-sub.htm#unescape-html diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 8f19a802..d7e5cecc 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -23,6 +23,7 @@ from django.core.urlresolvers import reverse from django.core import exceptions as django_exceptions from django.contrib.humanize.templatetags import humanize from django.http import QueryDict +from django.conf import settings import askbot from askbot import exceptions @@ -33,6 +34,7 @@ from askbot import schedules from askbot.models.tag import Tag from askbot import const from askbot.utils import functions +from askbot.utils.html import sanitize_html from askbot.utils.decorators import anonymous_forbidden, ajax_only, get_only from askbot.search.state_manager import SearchState, DummySearchState from askbot.templatetags import extra_tags @@ -126,7 +128,7 @@ def questions(request, **kwargs): # We have tags in session - pass it to the # QueryDict but as a list - we want tags+ rss_query_dict.setlist("tags", search_state.tags) - context_feed_url = '/feeds/rss/?%s' % rss_query_dict.urlencode() # Format the url with the QueryDict + context_feed_url = '/%sfeeds/rss/?%s' % (settings.ASKBOT_URL, rss_query_dict.urlencode()) # Format the url with the QueryDict reset_method_count = len(filter(None, [search_state.query, search_state.tags, meta_data.get('author_name', None)])) @@ -561,10 +563,13 @@ def revisions(request, id, post_type = None): revisions.reverse() for i, revision in enumerate(revisions): if i == 0: - revision.diff = revisions[i].html + revision.diff = sanitize_html(revisions[i].html) revision.summary = _('initial version') else: - revision.diff = htmldiff(revisions[i-1].html, revision.html) + revision.diff = htmldiff( + sanitize_html(revisions[i-1].html), + sanitize_html(revision.html) + ) data = { 'page_class':'revisions-page', diff --git a/askbot/views/users.py b/askbot/views/users.py index 9d9419e1..8bc2909d 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -128,7 +128,7 @@ def users(request, by_group = False, group_id = None, group_slug = None): models.User.objects.order_by(order_by_parameter), const.USERS_PAGE_SIZE ) - base_url = request.path + '?sort=%s&' % sortby + base_url = request.path + '?sort=%s&' % sortby else: sortby = "reputation" matching_users = models.get_users_by_text_query(search_query) @@ -136,7 +136,7 @@ def users(request, by_group = False, group_id = None, group_slug = None): matching_users.order_by('-reputation'), const.USERS_PAGE_SIZE ) - base_url = request.path + '?name=%s&sort=%s&' % (search_query, sortby) + base_url = request.path + '?name=%s&sort=%s&' % (search_query, sortby) try: users_page = objects_list.page(page) @@ -297,7 +297,7 @@ def edit_user(request, id): user.about = sanitize_html(form.cleaned_data['about']) user.country = form.cleaned_data['country'] user.show_country = form.cleaned_data['show_country'] - + user.show_marked_tags = form.cleaned_data['show_marked_tags'] user.save() # send user updated signal if full fields have been updated award_badges_signal.send(None, @@ -308,10 +308,12 @@ def edit_user(request, id): return HttpResponseRedirect(user.get_profile_url()) else: form = forms.EditUserForm(user) + data = { 'active_tab': 'users', 'page_class': 'user-profile-edit-page', 'form' : form, + 'marked_tags_setting': askbot_settings.MARKED_TAGS_ARE_PUBLIC_WHEN, 'support_custom_avatars': ('avatar' in django_settings.INSTALLED_APPS), 'view_user': user, } @@ -368,6 +370,18 @@ def user_stats(request, user, context): order_by('-user_tag_usage_count')[:const.USER_VIEW_DATA_SIZE] user_tags = list(user_tags) # evaluate + when = askbot_settings.MARKED_TAGS_ARE_PUBLIC_WHEN + if when == 'always' or \ + (when == 'when-user-wants' and user.show_marked_tags == True): + #refactor into: user.get_marked_tag_names('good'/'bad'/'subscribed') + interesting_tag_names = user.get_marked_tag_names('good') + ignored_tag_names = user.get_marked_tag_names('bad') + subscribed_tag_names = user.get_marked_tag_names('subscribed') + else: + interesting_tag_names = None + ignored_tag_names = None + subscribed_tag_names = None + # tags = models.Post.objects.filter(author=user).values('id', 'thread', 'thread__tags') # post_ids = set() # thread_ids = set() @@ -453,6 +467,9 @@ def user_stats(request, user, context): 'user_tags' : user_tags, 'user_groups': user_groups, 'groups_membership_info': groups_membership_info, + 'interesting_tag_names': interesting_tag_names, + 'ignored_tag_names': ignored_tag_names, + 'subscribed_tag_names': subscribed_tag_names, 'badges': badges, 'total_badges' : len(badges), } diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 935b7cfa..232dbaea 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -17,7 +17,7 @@ from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 from django.utils import simplejson -from django.utils.html import strip_tags +from django.utils.html import strip_tags, escape from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from django.core import exceptions @@ -545,7 +545,7 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po 'object_id': obj.id, 'comment_added_at': str(comment.added_at.replace(microsecond = 0)) + tz, 'html': comment.html, - 'user_display_name': comment_owner.username, + 'user_display_name': escape(comment_owner.username), 'user_url': comment_owner.get_profile_url(), 'user_id': comment_owner.id, 'is_deletable': is_deletable, diff --git a/askbot_requirements_dev.txt b/askbot_requirements_dev.txt index ada0d83e..b960c76e 100644 --- a/askbot_requirements_dev.txt +++ b/askbot_requirements_dev.txt @@ -5,6 +5,7 @@ Coffin>=0.3 South>=0.7.1 #-e git+https://github.com/matthiask/south.git#egg=south oauth2 +Lamson markdown2 html5lib==0.90 django-keyedcache |