Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to mark user links (in questions/answers/comments) with nofollow #23

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a006c3b
Small rmpyc improvement - make it safe for paths containing spaces
Mar 24, 2012
e38d5fa
Add support for tag format filtering and auto-tagging questions asked…
cdman May 31, 2012
c4c0ada
Added option to mark user links (in questions/answers/comments) with …
cdman May 31, 2012
bdbbf85
Added optional MathJax support
cdman May 31, 2012
1823bf2
Fixed youtube embedding and added a clearly licensed viewbox.jx - OSQ…
cdman May 31, 2012
1702685
Merge branch 'matchjax_support'
cdman May 31, 2012
8461c8d
Merge branch 'youtube_fix'
cdman May 31, 2012
066d7f7
Adding viewbox's images, missing from previous commit
cdman May 31, 2012
a4f8306
Merge branch 'youtube_fix'
cdman May 31, 2012
f6ef72e
Make user accept rate calculation configurable
cdman May 31, 2012
1d94a8d
Merge branch 'configurable_accept_rate'
cdman May 31, 2012
f4ff7ec
Removed deprecated minified files
cdman May 31, 2012
cb504e8
Merge branch 'remove_leftover_files'
cdman May 31, 2012
18ea97c
Updated viewbox css
cdman May 31, 2012
0a210a5
Merge branch 'youtube_fix'
cdman May 31, 2012
4f498da
Fix missing "Conver to comment" buttons
cdman May 31, 2012
086202e
Merge branch 'fix_missing_convert_buttons'
cdman May 31, 2012
f68c109
Fix markdow help and privacy pages
cdman May 31, 2012
e947fbc
Merge branch 'small_fixes'
cdman May 31, 2012
51d77f9
Add a try-catch block around os.linesep since Google App Engine doesn…
cdman May 31, 2012
2260a92
Merge branch 'gae_compaitibility_fix'
cdman May 31, 2012
347c566
Added settings import/export command to management script
cdman May 31, 2012
7a6eadd
Merge branch 'settings_import_export'
cdman May 31, 2012
bda15b0
Merge branch 'master' into rmpyc_improvement
cdman Jun 1, 2012
4f63cf6
Make the WSGI script automatically detect its location rather than ha…
cdman Jun 1, 2012
9b0fa70
Merge branch 'adaptable_wsgi_script'
cdman Jun 1, 2012
ee63a0f
More small fixes to make OSQA work in subdirectories (especially site…
cdman Jun 1, 2012
345f6c2
Merge branch 'more_subdir_fixes'
cdman Jun 1, 2012
3147a3e
Setting for static file cache-busting
cdman Jun 1, 2012
f8d1543
Merge branch 'media_cache_buster'
cdman Jun 1, 2012
5afb955
Add some more options to restrict user's actions
cdman Jun 1, 2012
1fd4df4
Merge branch 'user_restrictions'
cdman Jun 1, 2012
8e6afaf
Fixed search for newly added answers/comments with PostgreSQL as per …
cdman Jun 1, 2012
e09e561
Merge branch 'pg_fulltext_search_fix'
cdman Jun 1, 2012
cbdfc3e
Add setting to disable welcome email sending
cdman Jun 2, 2012
9d4cde0
Merge branch 'disable_welcome_email'
cdman Jun 2, 2012
3f56958
Added feature to redirect FAQ to a custom link
cdman Jun 2, 2012
bbfd942
Merge branch 'faq_link'
cdman Jun 2, 2012
c03c4c7
First stab at a script to install virtualenv
cdman Jun 2, 2012
36ac662
Virtualenv installation script
cdman Jun 2, 2012
ef654c0
Merge branch 'more_small_bugfixes'
cdman Jun 3, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions forum/actions/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def repute_users(self):
self.repute(self.user, int(settings.INITIAL_REP))

def process_action(self):
if not settings.SEND_WELCOME_EMAILS:
return
hash = ValidationHash.objects.create_new(self.user, 'email', [self.user.email])
send_template_email([self.user], "auth/welcome_email.html", {'validation_code': hash})

Expand Down
19 changes: 15 additions & 4 deletions forum/forms/qanda.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.utils.translation import ugettext as _

from django.utils.encoding import smart_unicode
from django.utils.safestring import mark_safe
from general import NextUrlField, UserNameField

from forum import settings
Expand Down Expand Up @@ -71,7 +72,7 @@ def clean(self, value):


class TagNamesField(forms.CharField):
def __init__(self, user=None, *args, **kwargs):
def __init__(self, user=None, initial='', *args, **kwargs):
super(TagNamesField, self).__init__(*args, **kwargs)

self.required = True
Expand All @@ -82,7 +83,7 @@ def __init__(self, user=None, *args, **kwargs):
self.help_text = _('Tags are short keywords, with no spaces within. At least %(min)s and up to %(max)s tags can be used.') % {
'min': settings.FORM_MIN_NUMBER_OF_TAGS, 'max': settings.FORM_MAX_NUMBER_OF_TAGS
}
self.initial = ''
self.initial = initial
self.user = user

def clean(self, value):
Expand All @@ -95,6 +96,16 @@ def clean(self, value):
list = {}
for tag in split_re.split(data):
list[tag] = tag

if settings.ENFORCE_TAG_FORMAT_FLAG and settings.ENFORCE_TAG_FORMAT_RX and len(str(settings.ENFORCE_TAG_FORMAT_RX.strip())) > 0:
try:
regexp = re.compile(str(settings.ENFORCE_TAG_FORMAT_RX.strip()))
good_tags = [tag for tag in list.values() if regexp.match(tag)]
if len(good_tags) == 0:
raise forms.ValidationError(mark_safe(_(settings.ENFORCE_TAG_FORMAT_ERROR_MESSAGE)))
except re.error:
logging.error("Error while trying to match tags against '%s'" % settings.ENFORCE_TAG_FORMAT_RX)
pass

if len(list) > settings.FORM_MAX_NUMBER_OF_TAGS or len(list) < settings.FORM_MIN_NUMBER_OF_TAGS:
raise forms.ValidationError(_('please use between %(min)s and %(max)s tags') % { 'min': settings.FORM_MIN_NUMBER_OF_TAGS, 'max': settings.FORM_MAX_NUMBER_OF_TAGS})
Expand Down Expand Up @@ -175,10 +186,10 @@ class AskForm(forms.Form):
title = TitleField()
text = QuestionEditorField()

def __init__(self, data=None, user=None, *args, **kwargs):
def __init__(self, data=None, user=None, default_tag='', *args, **kwargs):
super(AskForm, self).__init__(data, *args, **kwargs)

self.fields['tags'] = TagNamesField(user)
self.fields['tags'] = TagNamesField(user, initial=default_tag)

if int(user.reputation) < settings.CAPTCHA_IF_REP_LESS_THAN and not (user.is_superuser or user.is_staff):
spam_fields = call_all_handlers('create_anti_spam_field')
Expand Down
27 changes: 27 additions & 0 deletions forum/management/commands/dump_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pprint import pformat
from django.core.management.base import NoArgsCommand
from forum import settings
from forum.settings import BaseSetting

class Command(NoArgsCommand):
def handle_noargs(self, **options):
last_value_default = None
for k in dir(settings):
attr = getattr(settings, k)
if not isinstance(attr, BaseSetting):
continue
if attr.value == attr.default:
if not(last_value_default is None) and last_value_default == False:
print
last_value_default = True
print "# %s has default value" % k
continue

print
last_value_default = False
if attr.field_context:
if attr.field_context.get('label', None):
print "'''%s'''" % unicode(attr.field_context.get('label'))
if attr.field_context.get('help_text', None):
print "'''%s'''" % unicode(attr.field_context.get('help_text'))
print "settings.%s = %s" % (k, pformat(attr.value))
21 changes: 21 additions & 0 deletions forum/management/commands/update_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.core.management.base import NoArgsCommand
from forum import settings as forum_settings
from settings_osqa import SETTINGS

class Command(NoArgsCommand):
def handle_noargs(self, **options):
for k, v in SETTINGS.dict.items():
try:
attr = getattr(forum_settings, k)
except AttributeError:
print "!!! Attribute %s not found, couldn't set it to %s" % (k, v)
continue
if attr.value == v:
print "# Skipping %s, already has the correct value" % k
continue
if attr.value != attr.default:
print ("!!! Attribute %s has a different value (%s) than its default (%s). " \
+ "Won't change it to %s") % (k, attr.value, attr.default, v)
continue
attr.set_value(v)
print "Updated %s" % k
18 changes: 18 additions & 0 deletions forum/markdownext/mdx_nofollow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import markdown
import re

R_NOFOLLOW = re.compile('<a ')
S_NOFOLLOW = '<a rel="nofollow" '

class NofollowPostprocessor(markdown.postprocessors.Postprocessor):
def run(self, text):
return R_NOFOLLOW.sub(S_NOFOLLOW, text)

class NofollowExtension(markdown.Extension):
""" Add nofollow for links to Markdown. """

def extendMarkdown(self, md, md_globals):
md.postprocessors.add('nofollow', NofollowPostprocessor(md), '_end')

def makeExtension(configs={}):
return NofollowExtension(configs=configs)
6 changes: 5 additions & 1 deletion forum/markdownext/mdx_settingsparser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from os import linesep
try:
from os import linesep
except ImportError:
linesep = "\n"

from csv import reader, QUOTE_NONE
import markdown
from markdown import Extension
Expand Down
11 changes: 6 additions & 5 deletions forum/models/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils.html import strip_tags
from forum.utils.html import sanitize_html
from forum.utils.userlinking import auto_user_link
from forum.settings import SUMMARY_LENGTH
from forum.settings import SUMMARY_LENGTH, USE_NOFOLLOW_ON_USER_LINKS
from utils import PickledObjectField

class NodeContent(models.Model):
Expand All @@ -29,7 +29,8 @@ def html(self):
return self.body

def rendered(self, content):
return auto_user_link(self, self._as_markdown(content, *['auto_linker']))
extensions = ['auto_linker', 'nofollow'] if USE_NOFOLLOW_ON_USER_LINKS else ['auto_linker']
return auto_user_link(self, self._as_markdown(content, *extensions))

@classmethod
def _as_markdown(cls, content, *extensions):
Expand Down Expand Up @@ -486,16 +487,16 @@ def delete(self, *args, **kwargs):
super(Node, self).delete(*args, **kwargs)

def save(self, *args, **kwargs):
if self.parent_id and not self.abs_parent_id:
self.abs_parent = self.parent.absolute_parent

if not self.id:
self.node_type = self.get_type()
super(BaseModel, self).save(*args, **kwargs)
self.active_revision = self._create_revision(self.author, 1, title=self.title, tagnames=self.tagnames,
body=self.body)
self.activate_revision(self.author, self.active_revision)
self.update_last_activity(self.author, time=self.added_at)

if self.parent_id and not self.abs_parent_id:
self.abs_parent = self.parent.absolute_parent

tags_changed = self._process_changes_in_tags()

Expand Down
5 changes: 4 additions & 1 deletion forum/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User as DjangoUser, AnonymousUser as DjangoAnonymousUser
from django.db.models import Q, Manager
from django.core.urlresolvers import get_script_prefix

from django.utils.encoding import smart_unicode

Expand Down Expand Up @@ -230,7 +231,9 @@ def get_profile_url(self):
return ('user_profile', (), keyword_arguments)

def get_absolute_url(self):
return "%s%s" % (django_settings.APP_URL, self.get_profile_url())
root_relative_url = self.get_profile_url()
relative_url = root_relative_url[len(get_script_prefix()):]
return '%s/%s' % (django_settings.APP_URL, relative_url)

@models.permalink
def get_asked_url(self):
Expand Down
1 change: 1 addition & 0 deletions forum/modules/ui_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __invert__(self):
Visibility.SUPERUSER = Visibility('superuser')
Visibility.OWNER = Visibility('owner')
Visibility.REPUTED = lambda r: Visibility(r)
Visibility.NOBODY = Visibility('public', negated=True)


class Url(object):
Expand Down
4 changes: 2 additions & 2 deletions forum/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from forum.utils.html import cleanup_urls
from forum import settings


try:
from django.template import get_templatetags_modules
modules_template_tags = get_modules_script('templatetags')
Expand Down Expand Up @@ -77,7 +76,8 @@ def can_render(self, context):
url=lambda u, c: reverse('user_authsettings', kwargs={'id': c['user'].id}),
span_attrs={'class': 'user-auth'},
weight=100,
name='AUTH_SETTINGS'
name='AUTH_SETTINGS',
visibility=ui.Visibility.AUTHENTICATED if settings.USERS_CAN_CHANGE_AUTH_SETTINGS else ui.Visibility.NOBODY,
),
ui.UserMenuItem(
label=_("email notification settings"),
Expand Down
5 changes: 5 additions & 0 deletions forum/settings/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,9 @@
help_text = _("If checked the daily digest won't be sent to users that haven't validated their emails."),
required=False))

SEND_WELCOME_EMAILS = Setting('SEND_WELCOME_EMAILS', True, EMAIL_SET, dict(
label = _("Send welcome emails"),
help_text = _("If checked a welcome email will be sent to new users joining the community."),
required=False))

EMAIL_DIGEST_FLAG = Setting('EMAIL_DIGEST_FLAG', None)
14 changes: 10 additions & 4 deletions forum/settings/faq.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from base import Setting, SettingSet
from django.forms.widgets import Textarea
from django.utils.translation import ugettext_lazy as _

FAQ_SET = SettingSet('faq', 'FAQ page', "Define the text in the about page. You can use markdown and some basic html tags.", 2000, True)
FAQ_SET = SettingSet('faq', _('FAQ page'), _("Define the text in the about page. You can use markdown and some basic html tags."), 2000, True)

FAQ_PAGE_TEXT = Setting('FAQ_PAGE_TEXT',
u"""
Expand Down Expand Up @@ -74,6 +75,11 @@

Please ask your question, help make our community better!
""", FAQ_SET, dict(
label = "FAQ page text",
help_text = " The faq page. ",
widget=Textarea(attrs={'rows': '25'})))
label = _("FAQ page text"),
help_text = _("The faq page."),
widget=Textarea(attrs={'rows': '25'})))

FAQ_LINK = Setting('FAQ_LINK', '', FAQ_SET, dict(
label = _("FAQ link"),
help_text = _("If not empty, faq page will redirect to this link rather than display the text above"),
required=False))
17 changes: 16 additions & 1 deletion forum/settings/form.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os.path
from base import Setting, SettingSet
from django.forms.widgets import Textarea
from django.utils.translation import ugettext_lazy as _

FORUM_SET = SettingSet('form', _('Form settings'), _("General settings for the OSQA forms."), 10)
Expand All @@ -14,6 +14,21 @@
help_text = _("Limit tag creation to super users, staff or users with a minimum reputation."),
required=False))

ENFORCE_TAG_FORMAT_FLAG = Setting('ENFORCE_TAG_FORMAT_FLAG', False, FORUM_SET, dict(
label = _("Enforce tag format"),
help_text = _("Should question's tags be checked against a regular expression"),
required=False))

ENFORCE_TAG_FORMAT_RX = Setting('ENFORCE_TAG_FORMAT_RX', '', FORUM_SET, dict(
label = _("Enforce tag format RX"),
help_text = _("Regular eXpression which must match at least one tag on the question"),
required=False))

ENFORCE_TAG_FORMAT_ERROR_MESSAGE = Setting('ENFORCE_TAG_FORMAT_ERROR_MESSAGE', '', FORUM_SET, dict(
label = _("Enforce tag format error message"),
help_text = _("Error message to show if the tags from a question fail to meet the above regular expression (can contain HTML)"),
widget=Textarea(attrs={'rows': '10'}),
required=False))

""" settings for questions """
FORM_MIN_QUESTION_TITLE = Setting('FORM_MIN_QUESTION_TITLE', 10, FORUM_SET, dict(
Expand Down
7 changes: 6 additions & 1 deletion forum/settings/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,9 @@
label = _("Custom Head"),
help_text = _("Your custom Head elements."),
widget=Textarea(attrs={'rows': '25'}),
required=False))
required=False))

MATHJAX_ENABLED = Setting('MATHJAX_ENABLED', False, HEAD_SET, dict(
label = _("Enable LaTeX support"),
help_text = _("Enable LaTeX support via MathJax, by loading MathJax javascript"),
required=False))
19 changes: 19 additions & 0 deletions forum/settings/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
help_text = _("If you check this the user accept rate will be displayed on the user posts."),
required=False))

USE_USERS_QUESTIONS_FOR_ACCEPT_RATE = Setting('USE_USERS_QUESTIONS_FOR_ACCEPT_RATE', True, USERS_SET, dict(
label = _("Calculate accept rate SO style"),
help_text = _("Calculate rate as own questions with accepted answers / own questions"),
required=False))

FREEZE_ACCEPT_RATE_FOR = Setting('FREEZE_ACCEPT_RATE_FOR',
["admin",],
USERS_SET, dict(
Expand Down Expand Up @@ -121,3 +126,17 @@
choices=GRAVATAR_DEFAULT_CHOICES,
required=False))

USERS_CAN_GIVEAWAY_KARMA = Setting('USERS_CAN_GIVEAWAY_KARMA', True, USERS_SET, dict(
label = _("Enable authenticated users to give away karma"),
help_text = _("Superuser / staff can give karma regardless of this setting"),
required=False))

USERS_CAN_OPEN_CLOSE_QUESTIONS = Setting('USERS_CAN_OPEN_CLOSE_QUESTIONS', True, USERS_SET, dict(
label = _("Enable authenticated users open/close questions"),
help_text = _("Superuser / staff can open/close questions regardless of this setting. Reputation limit is also taken into account."),
required=False))

USERS_CAN_CHANGE_AUTH_SETTINGS = Setting('USERS_CAN_CHANGE_AUTH_SETTINGS', True, USERS_SET, dict(
label = _("Enable users to change auth settings"),
help_text = _("Enable users to access authentication settings (requires server restart to fully take effect)"),
required=False))
7 changes: 7 additions & 0 deletions forum/settings/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@
label = _("Limit related tags block"),
help_text = _("Limit related tags block size in questions list pages. Set to 0 to display all all tags.")))

AUTO_SET_TAG_ON_QUESTION = Setting('AUTO_SET_TAG_ON_QUESTION', False, VIEW_SET, dict(
label = _("Automatically set tag on questions asked from tag page"), required=False,
help_text = _("Automatically set the tag on new questions asked from the tag page")))

USE_NOFOLLOW_ON_USER_LINKS = Setting('USE_NOFOLLOW_ON_USER_LINKS', False, VIEW_SET, dict(
label = _("Set nofollow on links in questions/answers"), required=False,
help_text = _("Set the nofollow attribute on links in questions/answers (only applies for new/edited questions/answers)")))
15 changes: 7 additions & 8 deletions forum/sitemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
from django.http import HttpResponse, Http404
from django.template import loader
from django.core import urlresolvers
from django.core.urlresolvers import get_script_prefix
from django.utils.encoding import smart_str
from django.core.paginator import EmptyPage, PageNotAnInteger

def index(request, sitemaps):
sites = []
for section, site in sitemaps.items():
if callable(site):
pages = site().paginator.num_pages
else:
pages = site.paginator.num_pages
sitemap_url = urlresolvers.reverse('sitemap_section_index', kwargs={'section': section})
for section in sitemaps.keys():
sitemap_url = urlresolvers.reverse('sitemap_section_index', prefix='/', kwargs={'section': section})

# Replace double forward slashes with single ones
final_url = '%s%s' % (settings.APP_URL, sitemap_url)
Expand All @@ -41,7 +38,7 @@ def sitemap_section_index(request, section, sitemaps):
locations = []

for page in paginator.page_range:
location = urlresolvers.reverse('sitemap_section_page', kwargs={ 'page' : page, 'section' : section })
location = urlresolvers.reverse('sitemap_section_page', prefix='/', kwargs={ 'page' : page, 'section' : section })
location = '%s%s' % (settings.APP_URL, location)
location = re.sub("/+", "/", location)
location = location.replace('http:/', 'http://')
Expand Down Expand Up @@ -98,7 +95,9 @@ def __get(self, name, obj, default=None):
def get_urls(self, page=1):
urls = []
for item in self.paginator.page(page).object_list:
loc = "%s%s" % (settings.APP_URL, self.__get('location', item))
root_relative_url = self.__get('location', item)
relative_url = root_relative_url[len(get_script_prefix()):]
loc = "%s/%s" % (settings.APP_URL, relative_url)
url_info = {
'location': loc,
'lastmod': self.__get('lastmod', item, None),
Expand Down
Binary file modified forum/skins/default/media/images/sprite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added forum/skins/default/media/images/viewbox/load.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added forum/skins/default/media/images/viewbox/miniL.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,7 @@ else
elapsedTime = currTime - prevTime;

pushPreviewHtml(text);
if (MathJax) { MathJax.Hub.Queue(["Typeset",MathJax.Hub]); }
};

// setTimeout is already used. Used as an event listener.
Expand Down
Loading