summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore1
-rw-r--r--askbot/__init__.py2
-rw-r--r--askbot/askbot0
-rw-r--r--askbot/conf/settings_wrapper.py10
-rw-r--r--askbot/conf/site_modes.py4
-rw-r--r--askbot/conf/skin_general_settings.py40
-rw-r--r--askbot/const/__init__.py9
-rw-r--r--askbot/context.py11
-rw-r--r--askbot/cron/askbot_cron_job2
-rw-r--r--askbot/deps/django_authopenid/backends.py4
-rw-r--r--askbot/deps/django_authopenid/views.py11
-rw-r--r--askbot/doc/source/changelog.rst11
-rw-r--r--askbot/doc/source/contributors.rst1
-rw-r--r--askbot/doc/source/index.rst1
-rw-r--r--askbot/doc/source/intranet-setup.rst14
-rw-r--r--askbot/feed.py59
-rw-r--r--askbot/lamson_handlers.py173
-rw-r--r--askbot/management/commands/send_email_alerts.py2
-rw-r--r--askbot/middleware/locale.py26
-rw-r--r--askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py14
-rw-r--r--askbot/migrations/0123_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py336
-rw-r--r--askbot/models/__init__.py141
-rw-r--r--askbot/models/post.py5
-rw-r--r--askbot/models/reply_by_email.py43
-rw-r--r--askbot/models/signals.py2
-rw-r--r--askbot/models/tag.py42
-rw-r--r--askbot/setup_templates/settings.py1
-rw-r--r--askbot/setup_templates/settings.py.mustache1
-rw-r--r--askbot/skins/common/media/js/editor.js5
-rw-r--r--askbot/skins/common/media/js/post.js11
-rw-r--r--askbot/skins/common/media/js/user.js22
-rw-r--r--askbot/skins/common/media/js/utils.js4
-rw-r--r--askbot/skins/default/media/bootstrap/css/bootstrap.css12
-rw-r--r--askbot/skins/default/media/images/OFL.txt93
-rw-r--r--askbot/skins/default/media/images/YanoneKaffeesatz-Bold.ttfbin0 -> 73000 bytes
-rw-r--r--askbot/skins/default/media/images/YanoneKaffeesatz-ExtraLight.ttfbin0 -> 77024 bytes
-rw-r--r--askbot/skins/default/media/images/YanoneKaffeesatz-Light.ttfbin0 -> 77296 bytes
-rw-r--r--askbot/skins/default/media/images/YanoneKaffeesatz-Regular.ttfbin0 -> 76588 bytes
-rw-r--r--askbot/skins/default/media/images/Yanone_Kaffeesatz.zipbin0 -> 154362 bytes
-rw-r--r--askbot/skins/default/media/style/lib_style.css22
-rw-r--r--askbot/skins/default/media/style/lib_style.less37
-rw-r--r--askbot/skins/default/media/style/style.css664
-rw-r--r--askbot/skins/default/media/style/style.less153
-rw-r--r--askbot/skins/default/templates/badges.html4
-rw-r--r--askbot/skins/default/templates/email/feedback_email.txt (renamed from askbot/skins/default/templates/feedback_email.txt)0
-rw-r--r--askbot/skins/default/templates/email/footer.html1
-rw-r--r--askbot/skins/default/templates/email/re_welcome_lamson_on.html7
-rw-r--r--askbot/skins/default/templates/email/reply_by_email_error.html (renamed from askbot/skins/default/templates/reply_by_email_error.html)0
-rw-r--r--askbot/skins/default/templates/email/welcome_lamson_on.html11
-rw-r--r--askbot/skins/default/templates/groups.html46
-rw-r--r--askbot/skins/default/templates/macros.html54
-rw-r--r--askbot/skins/default/templates/main_page/tab_bar.html4
-rw-r--r--askbot/skins/default/templates/meta/fonts.html20
-rw-r--r--askbot/skins/default/templates/meta/html_head_stylesheets.html6
-rw-r--r--askbot/skins/default/templates/question/question_card.html2
-rw-r--r--askbot/skins/default/templates/question/subscribe_by_email_prompt.html1
-rw-r--r--askbot/skins/default/templates/user_profile/user.html2
-rw-r--r--askbot/skins/default/templates/user_profile/user_stats.html17
-rw-r--r--askbot/skins/default/templates/users.html72
-rw-r--r--askbot/skins/default/templates/widgets/group_info.html24
-rw-r--r--askbot/skins/utils.py15
-rw-r--r--askbot/startup_procedures.py15
-rw-r--r--askbot/tests/reply_by_email_tests.py37
-rw-r--r--askbot/utils/mail.py62
-rw-r--r--askbot/views/meta.py2
-rw-r--r--askbot/views/readers.py7
-rw-r--r--askbot/views/users.py48
67 files changed, 1875 insertions, 571 deletions
diff --git a/.gitignore b/.gitignore
index 07a3f84f..08e42e57 100755
--- a/.gitignore
+++ b/.gitignore
@@ -47,3 +47,4 @@ run
recaptcha
/.ve
/db.sq3
+*.DS_Store
diff --git a/askbot/__init__.py b/askbot/__init__.py
index 12517182..859b2695 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -9,7 +9,7 @@ import smtplib
import sys
import logging
-VERSION = (0, 7, 42)
+VERSION = (0, 7, 43)
#keys are module names used by python imports,
#values - the package qualifier to use for pip
diff --git a/askbot/askbot b/askbot/askbot
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/askbot/askbot
diff --git a/askbot/conf/settings_wrapper.py b/askbot/conf/settings_wrapper.py
index 78d16397..b6b5f302 100644
--- a/askbot/conf/settings_wrapper.py
+++ b/askbot/conf/settings_wrapper.py
@@ -58,8 +58,14 @@ class ConfigSettings(object):
self.update(key, self.get_default(key))
def update(self, key, value):
- setting = config_get(self.__group_map[key], key)
- setting.update(value)
+ try:
+ setting = config_get(self.__group_map[key], key)
+ setting.update(value)
+ except:
+ from askbot.deps.livesettings.models import Setting
+ setting = Setting.objects.get(key=key)
+ setting.value = value
+ setting.save()
#self.prime_cache()
def register(self, value):
diff --git a/askbot/conf/site_modes.py b/askbot/conf/site_modes.py
index a88103b4..efbbcca0 100644
--- a/askbot/conf/site_modes.py
+++ b/askbot/conf/site_modes.py
@@ -76,9 +76,9 @@ settings.register(
"Bootstrap mode lowers reputation and certain badge "
"thresholds, to values, more suitable "
"for the smaller communities, "
- "<strong>WARNING:</strong> your current value for "
+ "<strong>WARNING:</strong> your current values for "
"Minimum reputation, "
- "Bagde Settings and "
+ "Badge Settings and "
"Vote Rules will "
"be changed after you modify this setting."
),
diff --git a/askbot/conf/skin_general_settings.py b/askbot/conf/skin_general_settings.py
index d81c984b..323550ce 100644
--- a/askbot/conf/skin_general_settings.py
+++ b/askbot/conf/skin_general_settings.py
@@ -30,6 +30,42 @@ settings.register(
)
)
+LANGUAGE_CHOICES = (
+ ('en', _("English")),
+ ('es', _("Spanish")),
+ ('ca', _("Catalan")),
+ ('de', _("German")),
+ ('el', _("Greek")),
+ ('fi', _("Finnish")),
+ ('fr', _("French")),
+ ('hi', _("Hindi")),
+ ('hu', _("Hungarian")),
+ ('it', _("Italian")),
+ ('ja', _("Japanese")),
+ ('ko', _("Korean")),
+ ('pt', _("Portuguese")),
+ ('pt_BR', _("Brazilian Portuguese")),
+ ('ro', _("Romanian")),
+ ('ru', _("Russian")),
+ ('sr', _("Serbian")),
+ ('tr', _("Turkish")),
+ ('vi', _("Vietnamese")),
+ ('zh_CN', _("Chinese")),
+ ('zh_TW', _("Chinese (Taiwan)")),
+ )
+
+"""
+settings.register(
+ values.StringValue(
+ GENERAL_SKIN_SETTINGS,
+ 'ASKBOT_LANGUAGE',
+ default = 'en',
+ choices = LANGUAGE_CHOICES,
+ description = _('Select Language'),
+ )
+)
+"""
+
settings.register(
values.BooleanValue(
GENERAL_SKIN_SETTINGS,
@@ -198,7 +234,7 @@ settings.register(
description = _('Apply custom style sheet (CSS)'),
help_text = _(
'Check if you want to change appearance '
- 'of your form by adding custom style sheet rules '
+ 'of your form by adding custom style sheet rules '
'(please see the next item)'
),
default = False
@@ -214,7 +250,7 @@ settings.register(
'<strong>To use this function</strong>, check '
'"Apply custom style sheet" option above. '
'The CSS rules added in this window will be applied '
- 'after the default style sheet rules. '
+ 'after the default style sheet rules. '
'The custom style sheet will be served dynamically at '
'url "&lt;forum url&gt;/custom.css", where '
'the "&lt;forum url&gt; part depends (default is '
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 7eef91d1..f3091800 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -55,6 +55,10 @@ POST_SORT_METHODS = (
POST_TYPES = ('answer', 'comment', 'question', 'tag_wiki', 'reject_reason')
REPLY_SEPARATOR_TEMPLATE = '==== %(user_action)s %(instruction)s -=-=='
+REPLY_WITH_COMMENT_TEMPLATE = _(
+ 'Note: to reply with a comment, '
+ 'please use <a href="mailto://%(addr)s">this link</a>'
+)
REPLY_SEPARATOR_REGEX = re.compile('==== .* -=-==', re.MULTILINE)
ANSWER_SORT_METHODS = (#no translations needed here
@@ -129,6 +133,7 @@ TYPE_ACTIVITY_MODERATED_NEW_POST = 24
TYPE_ACTIVITY_MODERATED_POST_EDIT = 25
TYPE_ACTIVITY_CREATE_REJECT_REASON = 26
TYPE_ACTIVITY_UPDATE_REJECT_REASON = 27
+TYPE_ACTIVITY_VALIDATION_EMAIL_SENT = 28
#TYPE_ACTIVITY_EDIT_QUESTION = 17
#TYPE_ACTIVITY_EDIT_ANSWER = 18
@@ -182,6 +187,10 @@ TYPE_ACTIVITY = (
TYPE_ACTIVITY_UPDATE_REJECT_REASON,
_('updated post reject reason')
),
+ (
+ TYPE_ACTIVITY_VALIDATION_EMAIL_SENT,
+ 'sent email address validation message'#don't translate, internal
+ ),
)
diff --git a/askbot/context.py b/askbot/context.py
index ea10a890..03a2d1d8 100644
--- a/askbot/context.py
+++ b/askbot/context.py
@@ -26,7 +26,16 @@ def application_settings(request):
my_settings['LANGUAGE_CODE'] = getattr(request, 'LANGUAGE_CODE', settings.LANGUAGE_CODE)
my_settings['ASKBOT_URL'] = settings.ASKBOT_URL
my_settings['STATIC_URL'] = settings.STATIC_URL
- my_settings['ASKBOT_CSS_DEVEL'] = getattr(settings, 'ASKBOT_CSS_DEVEL', False)
+ my_settings['ASKBOT_CSS_DEVEL'] = getattr(
+ settings,
+ 'ASKBOT_CSS_DEVEL',
+ False
+ )
+ my_settings['USE_LOCAL_FONTS'] = getattr(
+ settings,
+ 'ASKBOT_USE_LOCAL_FONTS',
+ False
+ )
my_settings['DEBUG'] = settings.DEBUG
my_settings['USING_RUNSERVER'] = 'runserver' in sys.argv
my_settings['ASKBOT_VERSION'] = askbot.get_version()
diff --git a/askbot/cron/askbot_cron_job b/askbot/cron/askbot_cron_job
index 38bf0337..04ba2303 100644
--- a/askbot/cron/askbot_cron_job
+++ b/askbot/cron/askbot_cron_job
@@ -9,7 +9,7 @@ PROJECT_PARENT_DIR=/path/to/dir_containing_askbot_site
PROJECT_DIR_NAME=askbot_site
export PYTHONPATH=$PROJECT_PARENT_DIR:$PYTHONPATH
-PROJECT_ROOT=$PYTHONPATH/$PROJECT_NAME
+PROJECT_ROOT=$PROJECT_DIR_NAME/$PROJECT_NAME
#these are actual commands that are to be run
python $PROJECT_ROOT/manage.py send_email_alerts
diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py
index f3d8f64b..ed99e44f 100644
--- a/askbot/deps/django_authopenid/backends.py
+++ b/askbot/deps/django_authopenid/backends.py
@@ -10,6 +10,7 @@ from django.utils.translation import ugettext as _
from askbot.deps.django_authopenid.models import UserAssociation
from askbot.deps.django_authopenid import util
from askbot.conf import settings as askbot_settings
+from askbot.models.signals import user_registered
log = logging.getLogger('configuration')
@@ -62,6 +63,7 @@ def ldap_authenticate(username, password):
user.is_superuser = False
user.is_active = True
user.save()
+ user_registered.send(None, user = user)
log.info('Created New User : [{0}]'.format(exact_username))
return user
@@ -157,11 +159,13 @@ class AuthBackend(object):
if created:
user.set_password(password)
user.save()
+ user_registered.send(None, user = user)
else:
#have username collision - so make up a more unique user name
#bug: - if user already exists with the new username - we are in trouble
new_username = '%s@%s' % (username, provider_name)
user = User.objects.create_user(new_username, '', password)
+ user_registered.send(None, user = user)
message = _(
'Welcome! Please set email address (important!) in your '
'profile and adjust screen name, if necessary.'
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 22be8460..2f80d366 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -78,11 +78,11 @@ from askbot.deps.django_authopenid.backends import AuthBackend
import logging
from askbot.utils.forms import get_next_url
from askbot.utils.http import get_request_info
+from askbot.models.signals import user_logged_in, user_registered
#todo: decouple from askbot
def login(request,user):
from django.contrib.auth import login as _login
- from askbot.models import signals
# get old session key
session_key = request.session.session_key
@@ -93,7 +93,7 @@ def login(request,user):
# send signal with old session key as argument
logging.debug('logged in user %s with session key %s' % (user.username, session_key))
#todo: move to auth app
- signals.user_logged_in.send(
+ user_logged_in.send(
request = request,
user = user,
session_key=session_key,
@@ -326,7 +326,7 @@ def signin(request):
request = request,
user = user,
user_identifier = username,
- login_provider_name = ldap_provider_name,
+ login_provider_name = provider_name,
redirect_url = next_url
)
@@ -829,6 +829,7 @@ def register(request, login_provider_name=None, user_identifier=None):
email = register_form.cleaned_data['email']
user = User.objects.create_user(username, email)
+ user_registered.send(None, user = user)
logging.debug('creating new openid user association for %s')
@@ -951,7 +952,9 @@ def signup_with_password(request):
email = form.cleaned_data['email']
provider_name = form.cleaned_data['login_provider']
- User.objects.create_user(username, email, password)
+ new_user = User.objects.create_user(username, email, password)
+ user_registered.send(None, user = new_user)
+
logging.debug('new user %s created' % username)
if provider_name != 'local':
raise NotImplementedError('must run create external user code')
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 25d96e88..06808a9c 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -1,13 +1,20 @@
Changes in Askbot
=================
-Future version
---------------
+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)
+
+0.7.43 (May 14, 2012)
+---------------------
* User groups (Evgeny)
* Public/Private/Hidden reputation (Evgeny)
* Enabling/disabling the badges system (Evgeny)
* Created a basic post moderation feature (Evgeny)
* Created a way to specify reasons for rejecting posts in a modal dialog (Evgeny)
+* A number of bug fixes (Adolfo Fitoria, Jim Tittsler,
+ Evgeny Fadeev, Robin Stocker, Radim Řehůřek, Silvio Heuberger)
0.7.41, 0.7.42 (April 21, 2012)
-------------------------------
diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst
index 71bc5cc9..81729979 100644
--- a/askbot/doc/source/contributors.rst
+++ b/askbot/doc/source/contributors.rst
@@ -38,6 +38,7 @@ Programming and documentation
* `Radim Řehůřek <https://github.com/piskvorky>`_
* `monkut <https://github.com/monkut>`_
* `Jim Tittsler <http://wikieducator.org/User:JimTittsler>`_
+* Silvio Heuberger
Translations
------------
diff --git a/askbot/doc/source/index.rst b/askbot/doc/source/index.rst
index 81f21fcc..353b9105 100644
--- a/askbot/doc/source/index.rst
+++ b/askbot/doc/source/index.rst
@@ -26,6 +26,7 @@ at the forum_ or by email at admin@askbot.org
Appendix C: Optional modules <optional-modules>
Appendix D: Askbot as reusable Django application <askbot-as-reusable-django-application>
Appendix E: Customizing skin in askbot <customizing-skin-in-askbot>
+ Appendix F: Intranet setup <intranet-setup>
Footnotes <footnotes>
Contributors <contributors>
Changelog<changelog>
diff --git a/askbot/doc/source/intranet-setup.rst b/askbot/doc/source/intranet-setup.rst
new file mode 100644
index 00000000..224ffb89
--- /dev/null
+++ b/askbot/doc/source/intranet-setup.rst
@@ -0,0 +1,14 @@
+==========================================================
+Setting up Askbot for use on the closed network (Intranet)
+==========================================================
+
+When using Askbot on the Intranet (for example - within your
+Company network), it will be useful to disable references to
+all external resources - such as custom fonts, gravatars.
+
+Please change the following settings in your ``settings.py`` file::
+
+ ASKBOT_USE_LOCAL_FONTS=True
+
+In addition, in the "live settings":
+* disable gravatar in "settings->User settings"
diff --git a/askbot/feed.py b/askbot/feed.py
index c1933afe..776aad5e 100644
--- a/askbot/feed.py
+++ b/askbot/feed.py
@@ -25,20 +25,29 @@ from askbot.conf import settings as askbot_settings
class RssIndividualQuestionFeed(Feed):
"""rss feed class for particular questions
"""
- title = askbot_settings.APP_TITLE + _(' - ')+ _('Individual question feed')
- link = askbot_settings.APP_URL
- description = askbot_settings.APP_DESCRIPTION
- copyright = askbot_settings.APP_COPYRIGHT
+
+ def title(self):
+ return askbot_settings.APP_TITLE + _(' - ') + \
+ _('Individual question feed')
+
+ def feed_copyright(self):
+ return askbot_settings.APP_COPYRIGHT
+
+ def description(self):
+ return askbot_settings.APP_DESCRIPTION
def get_object(self, bits):
if len(bits) != 1:
raise ObjectDoesNotExist
return Post.objects.get_questions().get(id__exact = bits[0])
-
+
def item_link(self, item):
"""get full url to the item
"""
- return self.link + item.get_absolute_url()
+ return askbot_settings.APP_URL + item.get_absolute_url()
+
+ def link(self):
+ return askbot_settings.APP_URL
def item_pubdate(self, item):
"""get date of creation for the item
@@ -56,7 +65,7 @@ class RssIndividualQuestionFeed(Feed):
chain_elements.append(
Post.objects.get_comments().filter(parent=item)
)
-
+
answers = Post.objects.get_answers().filter(thread = item.thread)
for answer in answers:
chain_elements.append([answer,])
@@ -65,7 +74,7 @@ class RssIndividualQuestionFeed(Feed):
)
return itertools.chain(*chain_elements)
-
+
def item_title(self, item):
"""returns the title for the item
"""
@@ -77,7 +86,7 @@ class RssIndividualQuestionFeed(Feed):
elif item.post_type == "comment":
title = "Comment by %s for %s" % (item.author, self.title)
return title
-
+
def item_description(self, item):
"""returns the description for the item
"""
@@ -87,16 +96,24 @@ class RssIndividualQuestionFeed(Feed):
class RssLastestQuestionsFeed(Feed):
"""rss feed class for the latest questions
"""
- title = askbot_settings.APP_TITLE + _(' - ')+ _('latest questions')
- link = askbot_settings.APP_URL
- description = askbot_settings.APP_DESCRIPTION
- #ttl = 10
- copyright = askbot_settings.APP_COPYRIGHT
+
+ def title(self):
+ return askbot_settings.APP_TITLE + _(' - ') + \
+ _('Individual question feed')
+
+ def feed_copyright(self):
+ return askbot_settings.APP_COPYRIGHT
+
+ def description(self):
+ return askbot_settings.APP_DESCRIPTION
def item_link(self, item):
"""get full url to the item
"""
- return self.link + item.get_absolute_url()
+ return askbot_settings.APP_URL + item.get_absolute_url()
+
+ def link(self):
+ return askbot_settings.APP_URL
def item_author_name(self, item):
"""get name of author
@@ -117,10 +134,10 @@ class RssLastestQuestionsFeed(Feed):
"""returns url without the slug
because the slug can change
"""
- return self.link + item.get_absolute_url(no_slug = True)
-
+ return askbot_settings.APP_URL + item.get_absolute_url(no_slug = True)
+
def item_description(self, item):
- """returns the desciption for the item
+ """returns the description for the item
"""
return item.text
@@ -142,12 +159,12 @@ class RssLastestQuestionsFeed(Feed):
if tags:
#if there are tags in GET, filter the
#questions additionally
- for tag in tags:
+ for tag in tags:
qs = qs.filter(thread__tags__name = tag)
-
+
return qs.order_by('-thread__last_activity_at')[:30]
-
+
def main():
"""main function for use as a script
diff --git a/askbot/lamson_handlers.py b/askbot/lamson_handlers.py
index 9062594c..f6b6b752 100644
--- a/askbot/lamson_handlers.py
+++ b/askbot/lamson_handlers.py
@@ -1,12 +1,15 @@
import re
-from lamson.routing import route, stateless
-from lamson.server import Relay
-from django.utils.translation import ugettext as _
+import functools
from django.core.files.uploadedfile import SimpleUploadedFile
from django.conf import settings as django_settings
+from django.template import Context
+from django.utils.translation import ugettext as _
+from lamson.routing import route, stateless
+from lamson.server import Relay
from askbot.models import ReplyAddress, Tag
from askbot.utils import mail
from askbot.conf import settings as askbot_settings
+from askbot.skins.loaders import get_template
#we might end up needing to use something like this
@@ -114,11 +117,70 @@ def get_parts(message):
parts.append((part_type, part_content))
return parts
+def process_reply(func):
+ @functools.wraps(func)
+ def wrapped(message, host = None, address = None):
+ """processes forwarding rules, and run the handler
+ in the case of error, send a bounce email
+ """
+ try:
+ for rule in django_settings.LAMSON_FORWARD:
+ if re.match(rule['pattern'], message.base['to']):
+ relay = Relay(host=rule['host'],
+ port=rule['port'], debug=1)
+ relay.deliver(message)
+ return
+ except AttributeError:
+ pass
+
+ error = None
+ try:
+ reply_address = ReplyAddress.objects.get(
+ address = address,
+ allowed_from_email = message.From
+ )
+
+ #here is the business part of this function
+ func(
+ from_address = message.From,
+ subject_line = message['Subject'],
+ parts = get_parts(message),
+ reply_address_object = reply_address
+ )
+
+ except ReplyAddress.DoesNotExist:
+ error = _("You were replying to an email address\
+ unknown to the system or you were replying from a different address from the one where you\
+ received the notification.")
+ except Exception, e:
+ import sys
+ sys.stderr.write(str(e))
+ import traceback
+ sys.stderr.write(traceback.format_exc())
+
+ if error is not None:
+ template = get_template('email/reply_by_email_error.html')
+ body_text = template.render(Context({'error':error}))
+ mail.send_mail(
+ subject_line = "Error posting your reply",
+ body_text = body_text,
+ recipient_list = [message.From],
+ )
+
+ return wrapped
+
@route('(addr)@(host)', addr = '.+')
@stateless
def ASK(message, host = None, addr = None):
+ """lamson handler for asking by email,
+ to the forum in general and to a specific group"""
+
+ #we need to exclude some other emails by prefix
if addr.startswith('reply-'):
return
+ if addr.startswith('welcome-'):
+ return
+
parts = get_parts(message)
from_address = message.From
subject = message['Subject']#why lamson does not give it normally?
@@ -141,52 +203,69 @@ def ASK(message, host = None, addr = None):
except Tag.MultipleObjectsReturned:
return
+@route('welcome-(address)@host', address='.+')
+@stateless
+@process_reply
+def VALIDATE_EMAIL(
+ parts = None,
+ reply_address_object = None,
+ **kwargs
+):
+ """process the validation email and save
+ the email signature
+ todo: go a step further and
+ """
+ content, stored_files = mail.process_parts(parts)
+ reply_code = reply_address_object.address
+ if reply_code in content:
+
+ #extract the signature
+ tail = list()
+ for line in reversed(content.splitlines()):
+ if reply_code in line:
+ break
+ tail.append(line)
+ signature = '\n'.join(reversed(tail))
+
+ #save the signature and mark email as valid
+ user = reply_address_object.user
+ user.email_signature = signature
+ user.email_isvalid = True
+ user.save()
+
+ data = {
+ 'site_name': askbot_settings.APP_SHORT_NAME,
+ 'site_url': askbot_settings.APP_URL,
+ 'ask_address': 'ask@' + askbot_settings.REPLY_BY_EMAIL_HOSTNAME
+ }
+ template = get_template('email/re_welcome_lamson_on.html')
+
+ mail.send_mail(
+ subject_line = _('Re: Welcome to %(site_name)s') % data,
+ body_text = template.render(Context(data)),
+ recipient_list = [from_address,]
+ )
+
+ else:
+ raise ValueError(
+ _(
+ 'Please reply to the welcome email '
+ 'without editing it'
+ )
+ )
+
@route('reply-(address)@(host)', address='.+')
@stateless
-def PROCESS(message, address = None, host = None):
+@process_reply
+def PROCESS(
+ parts = None,
+ reply_address_object = None,
+ **kwargs
+):
"""handler to process the emailed message
and make a post to askbot based on the contents of
the email, including the text body and the file attachments"""
- try:
- for rule in django_settings.LAMSON_FORWARD:
- if re.match(rule['pattern'], message.base['to']):
- relay = Relay(host=rule['host'],
- port=rule['port'], debug=1)
- relay.deliver(message)
- return
- except AttributeError:
- pass
-
- error = None
- try:
- reply_address = ReplyAddress.objects.get(
- address = address,
- allowed_from_email = message.From
- )
- parts = get_parts(message)
- if reply_address.was_used:
- reply_address.edit_post(parts)
- else:
- reply_address.create_reply(parts)
- except ReplyAddress.DoesNotExist:
- error = _("You were replying to an email address\
- unknown to the system or you were replying from a different address from the one where you\
- received the notification.")
- except Exception, e:
- import sys
- sys.stderr.write(str(e))
- import traceback
- sys.stderr.write(traceback.format_exc())
-
- if error is not None:
- from askbot.utils import mail
- from django.template import Context
- from askbot.skins.loaders import get_template
-
- template = get_template('reply_by_email_error.html')
- body_text = template.render(Context({'error':error}))
- mail.send_mail(
- subject_line = "Error posting your reply",
- body_text = body_text,
- recipient_list = [message.From],
- )
+ if reply_address_object.was_used:
+ reply_address_object.edit_post(parts)
+ else:
+ reply_address_object.create_reply(parts)
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index 218bd9a9..b7624e21 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -475,7 +475,7 @@ class Command(NoArgsCommand):
text += _(
'<p>Please remember that you can always <a '
- 'hrefl"%(email_settings_link)s">adjust</a> frequency of the email updates or '
+ 'href="%(email_settings_link)s">adjust</a> frequency of the email updates or '
'turn them off entirely.<br/>If you believe that this message was sent in an '
'error, please email about it the forum administrator at %(admin_email)s.</'
'p><p>Sincerely,</p><p>Your friendly %(sitename)s server.</p>'
diff --git a/askbot/middleware/locale.py b/askbot/middleware/locale.py
new file mode 100644
index 00000000..c92e977a
--- /dev/null
+++ b/askbot/middleware/locale.py
@@ -0,0 +1,26 @@
+"Taken from django.middleware.locale: this is the locale selecting middleware that will look at accept headers"
+
+from django.utils.cache import patch_vary_headers
+from django.utils import translation
+from askbot.conf import settings
+
+class LocaleMiddleware(object):
+ """
+ This is a very simple middleware that parses a request
+ and decides what translation object to install in the current
+ thread context. This allows pages to be dynamically
+ translated to the language the user desires (if the language
+ is available, of course).
+ """
+
+ def process_request(self, request):
+ language = settings.ASKBOT_LANGUAGE
+ translation.activate(language)
+ request.LANGUAGE_CODE = translation.get_language()
+
+ def process_response(self, request, response):
+ patch_vary_headers(response, ('Accept-Language',))
+ if 'Content-Language' not in response:
+ response['Content-Language'] = translation.get_language()
+ translation.deactivate()
+ return response
diff --git a/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py b/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
index d26bdeb0..70ef2f8d 100644
--- a/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
+++ b/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
@@ -5,7 +5,7 @@ from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
-
+
def forwards(self, orm):
# Removing unique constraint on 'BadgeData', fields ['type', 'name']
@@ -34,10 +34,10 @@ class Migration(SchemaMigration):
except:
db.rollback_transaction()
-
-
+
+
def backwards(self, orm):
-
+
# Adding field 'BadgeData.multiple'
db.add_column('askbot_badgedata', 'multiple', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)
@@ -55,8 +55,8 @@ class Migration(SchemaMigration):
# Removing unique constraint on 'BadgeData', fields ['slug']
db.delete_unique('askbot_badgedata', ['slug'])
-
-
+
+
models = {
'askbot.activity': {
'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},
@@ -335,5 +335,5 @@ class Migration(SchemaMigration):
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
-
+
complete_apps = ['askbot']
diff --git a/askbot/migrations/0123_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py b/askbot/migrations/0123_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py
new file mode 100644
index 00000000..ecb66552
--- /dev/null
+++ b/askbot/migrations/0123_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py
@@ -0,0 +1,336 @@
+# -*- 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 field 'Post.is_private'
+ db.add_column('askbot_post', 'is_private',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'ReplyAddress.reply_action'
+ db.add_column('askbot_replyaddress', 'reply_action',
+ self.gf('django.db.models.fields.CharField')(default='auto_answer_or_comment', max_length=32),
+ keep_default=False)
+
+ # Changing field 'ReplyAddress.post'
+ db.alter_column('askbot_replyaddress', 'post_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['askbot.Post']))
+
+ try:
+ # Adding field 'User.interesting_tags'
+ db.add_column(u'auth_user', 'email_signature', self.gf('django.db.models.fields.TextField')(blank=True, default = ''), keep_default=False)
+ except:
+ pass
+
+ def backwards(self, orm):
+ db.delete_column('askbot_post', 'is_private')
+ db.delete_column('askbot_replyaddress', 'reply_action')
+ db.delete_column('auth_user', 'email_signature')
+ db.alter_column('askbot_replyaddress', 'post_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['askbot.Post']))
+
+ 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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+ 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
+ },
+ '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'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ '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'}),
+ 'is_private': ('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', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', '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.postflagreason': {
+ 'Meta': {'object_name': 'PostFlagReason'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'askbot.postrevision': {
+ 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'},
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}),
+ 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ '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']"}),
+ 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}),
+ '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'}),
+ 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ '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'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ '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'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ '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 88c8f7b1..967880be 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -1,9 +1,10 @@
from askbot import startup_procedures
startup_procedures.run()
-import logging
-import hashlib
+import collections
import datetime
+import hashlib
+import logging
import urllib
from django.core.urlresolvers import reverse, NoReverseMatch
from django.db.models import signals as django_signals
@@ -120,6 +121,7 @@ User.add_to_class('about', models.TextField(blank=True))
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(
'email_tag_filter_strategy',
models.SmallIntegerField(
@@ -203,6 +205,13 @@ def user_update_avatar_type(self):
self.avatar_type = _check_gravatar(self.gravatar)
self.save()
+def user_strip_email_signature(self, text):
+ """strips email signature from the end of the text"""
+ text = '\n'.join(text.splitlines())#normalize the line endings
+ if text.endswith(self.email_signature):
+ return text[0:-len(self.email_signature)]
+ return text
+
def _check_gravatar(gravatar):
gravatar_url = "http://www.gravatar.com/avatar/%s?d=404" % gravatar
code = urllib.urlopen(gravatar_url).getcode()
@@ -2035,6 +2044,35 @@ def get_profile_link(self):
return mark_safe(profile_link)
+def user_get_groups_membership_info(self, groups):
+ """returts a defaultdict with values that are
+ dictionaries with the following keys and values:
+ * key: can_join, value: True if user can join group
+ * key: is_member, value: True if user is member of group
+
+ ``groups`` is a group tag query set
+ """
+ groups = groups.select_related('group_profile')
+
+ group_ids = groups.values_list('id', flat = True)
+ memberships = GroupMembership.objects.filter(
+ user__id = self.id,
+ group__id__in = group_ids
+ )
+
+ info = collections.defaultdict(
+ lambda: {'can_join': False, 'is_member': False}
+ )
+ for membership in memberships:
+ info[membership.group_id]['is_member'] = True
+
+ for group in groups:
+ info[group.id]['can_join'] = group.group_profile.can_accept_user(self)
+
+ return info
+
+
+
def user_get_karma_summary(self):
"""returns human readable sentence about
status of user's karma"""
@@ -2416,6 +2454,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('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)
User.add_to_class('update_avatar_type', user_update_avatar_type)
User.add_to_class('post_question', user_post_question)
@@ -2534,6 +2574,7 @@ def format_instant_notification_email(
to_user = None,
from_user = None,
post = None,
+ reply_with_comment_address = None,
update_type = None,
template = None,
):
@@ -2630,6 +2671,9 @@ def format_instant_notification_email(
'user_action': user_action,
'instruction': _('To reply, PLEASE WRITE ABOVE THIS LINE.')
}
+ if post.post_type == 'question' and reply_with_comment_address:
+ data = {'addr': reply_with_comment_address}
+ reply_separator += '<br>' + const.REPLY_WITH_COMMENT_TEMPLATE % data
else:
reply_separator = user_action
@@ -2678,26 +2722,45 @@ def send_instant_notifications_about_activity_in_post(
origin_post = post.get_origin_post()
for user in recipients:
- subject_line, body_text = format_instant_notification_email(
- to_user = user,
- from_user = update_activity.user,
- post = post,
- update_type = update_type,
- template = get_template('instant_notification.html')
- )
-
#todo: this could be packaged as an "action" - a bundle
#of executive function with the activity log recording
#TODO check user reputation
headers = mail.thread_headers(post, origin_post, update_activity.activity_type)
+ reply_with_comment_address = None#only used for questions in some cases
if askbot_settings.REPLY_BY_EMAIL:
- reply_address = "noreply"
+ reply_addr = "noreply"
if user.reputation >= askbot_settings.MIN_REP_TO_POST_BY_EMAIL:
- reply_address = ReplyAddress.objects.create_new(post, user).address
- reply_to = 'reply-%s@%s' % (reply_address, askbot_settings.REPLY_BY_EMAIL_HOSTNAME)
+
+ reply_args = {
+ 'post': post,
+ 'user': user,
+ 'reply_action': 'post_comment'
+ }
+ if post.post_type in ('answer', 'comment'):
+ reply_addr = ReplyAddress.objects.create_new(**reply_args)
+ elif post.post_type == 'question':
+ reply_with_comment_address = ReplyAddress.objects.create_new(**reply_args)
+ #default action is to post answer
+ reply_args['reply_action'] = 'post_answer'
+ reply_addr = ReplyAddress.objects.create_new(**reply_args)
+
+ reply_to = 'reply-%s@%s' % (
+ reply_addr,
+ askbot_settings.REPLY_BY_EMAIL_HOSTNAME
+ )
headers.update({'Reply-To': reply_to})
else:
reply_to = django_settings.DEFAULT_FROM_EMAIL
+
+ subject_line, body_text = format_instant_notification_email(
+ to_user = user,
+ from_user = update_activity.user,
+ post = post,
+ reply_with_comment_address = reply_with_comment_address,
+ update_type = update_type,
+ template = get_template('instant_notification.html')
+ )
+
mail.send_mail(
subject_line = subject_line,
body_text = body_text,
@@ -2992,6 +3055,57 @@ def record_user_full_updated(instance, **kwargs):
)
activity.save()
+def send_respondable_email_validation_message(
+ user = None, subject_line = None, data = None, template_name = None
+):
+ """sends email validation message to the user
+
+ We validate email by getting user's reply
+ to the validation message by email, which also gives
+ an opportunity to extract user's email signature.
+ """
+ reply_address = ReplyAddress.objects.create_new(
+ user = user,
+ reply_action = 'validate_email'
+ )
+ data['email_code'] = reply_address.address
+
+ from askbot.skins.loaders import get_template
+ template = get_template(template_name)
+ body_text = template.render(Context(data))
+
+ reply_to_address = 'welcome-%s@%s' % (
+ reply_address.address,
+ askbot_settings.REPLY_BY_EMAIL_HOSTNAME
+ )
+
+ mail.send_mail(
+ subject_line = subject_line,
+ body_text = body_text,
+ recipient_list = [user.email, ],
+ activity_type = const.TYPE_ACTIVITY_VALIDATION_EMAIL_SENT,
+ headers = {'Reply-To': reply_to_address}
+ )
+
+
+def send_welcome_email(user, **kwargs):
+ """sends welcome email to the newly created user
+
+ todo: second branch should send email with a simple
+ clickable link.
+ """
+ if askbot_settings.REPLY_BY_EMAIL:#with this on we also collect signature
+ data = {
+ 'site_name': askbot_settings.APP_SHORT_NAME
+ }
+ send_respondable_email_validation_message(
+ user = user,
+ subject_line = _('Welcome to %(site_name)s') % data,
+ data = data,
+ template_name = 'email/welcome_email_lamson_on.html'
+ )
+
+
def complete_pending_tag_subscriptions(sender, request, *args, **kwargs):
"""save pending tag subscriptions saved in the session"""
if 'subscribe_for_tags' in request.session:
@@ -3088,6 +3202,7 @@ signals.delete_question_or_answer.connect(record_delete_question, sender=Post)
signals.flag_offensive.connect(record_flag_offensive, sender=Post)
signals.remove_flag_offensive.connect(remove_flag_offensive, sender=Post)
signals.tags_updated.connect(record_update_tags)
+signals.user_registered.connect(send_welcome_email)
signals.user_updated.connect(record_user_full_updated, sender=User)
signals.user_logged_in.connect(complete_pending_tag_subscriptions)#todo: add this to fake onlogin middleware
signals.user_logged_in.connect(post_anonymous_askbot_content)
diff --git a/askbot/models/post.py b/askbot/models/post.py
index a0fdc4ff..b6ee6d2a 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -332,6 +332,11 @@ class Post(models.Model):
#the reason is that the title and tags belong to thread,
#but the question body to Post
is_anonymous = models.BooleanField(default=False)
+ #When is_private == True
+ #the post is visible only to the some privileged users.
+ #The privilege may be defined through groups to which
+ #the thread belongs or in some other way.
+ is_private = models.BooleanField(default=False)
objects = PostManager()
diff --git a/askbot/models/reply_by_email.py b/askbot/models/reply_by_email.py
index d329d38b..4398f693 100644
--- a/askbot/models/reply_by_email.py
+++ b/askbot/models/reply_by_email.py
@@ -1,9 +1,10 @@
from datetime import datetime
import random
import string
+import logging
from django.db import models
from django.contrib.auth.models import User
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy as _
from askbot.models.post import Post
from askbot.models.base import BaseQuerySetManager
from askbot.conf import settings as askbot_settings
@@ -19,13 +20,10 @@ class ReplyAddressManager(BaseQuerySetManager):
used_at__isnull = True
)
- def create_new(self, post, user):
+ def create_new(self, **kwargs):
"""creates a new reply address"""
- reply_address = ReplyAddress(
- post = post,
- user = user,
- allowed_from_email = user.email
- )
+ kwargs['allowed_from_email'] = kwargs['user'].email
+ reply_address = ReplyAddress(**kwargs)
while True:
reply_address.address = ''.join(random.choice(string.letters +
string.digits) for i in xrange(random.randint(12, 25))).lower()
@@ -35,14 +33,26 @@ class ReplyAddressManager(BaseQuerySetManager):
return reply_address
+REPLY_ACTION_CHOICES = (
+ ('post_answer', _('Post an answer')),
+ ('post_comment', _('Post a comment')),
+ ('auto_answer_or_comment', _('Answer or comment, depending on the size of post')),
+ ('validate_email', _('Validate email and record signature')),
+)
class ReplyAddress(models.Model):
"""Stores a reply address for the post
and the user"""
address = models.CharField(max_length = 25, unique = True)
post = models.ForeignKey(
Post,
+ null = True,#reply not necessarily to posts
related_name = 'reply_addresses'
)#the emailed post
+ reply_action = models.CharField(
+ max_length = 32,
+ choices = REPLY_ACTION_CHOICES,
+ default = 'auto_answer_or_comment'
+ )
response_post = models.ForeignKey(
Post,
null = True,
@@ -92,19 +102,32 @@ class ReplyAddress(models.Model):
by_email = True
)
elif self.post.post_type == 'question':
- wordcount = len(content)/6#this is a simplistic hack
- if wordcount > askbot_settings.MIN_WORDS_FOR_ANSWER_BY_EMAIL:
+ if self.reply_action == 'auto_answer_or_comment':
+ wordcount = len(content)/6#todo: this is a simplistic hack
+ if wordcount > askbot_settings.MIN_WORDS_FOR_ANSWER_BY_EMAIL:
+ reply_action = 'post_answer'
+ else:
+ reply_action = 'post_comment'
+ else:
+ reply_action = self.reply_action
+
+ if reply_action == 'post_answer':
result = self.user.post_answer(
self.post,
content,
by_email = True
)
- else:
+ elif reply_action == 'post_comment':
result = self.user.post_comment(
self.post,
content,
by_email = True
)
+ else:
+ logging.critical(
+ 'Unexpected reply action: "%s", post by email failed' % reply_action
+ )
+ return None#todo: there may be a better action to take here...
elif self.post.post_type == 'comment':
result = self.user.post_comment(
self.post.parent,
diff --git a/askbot/models/signals.py b/askbot/models/signals.py
index baa4c149..28fe70b0 100644
--- a/askbot/models/signals.py
+++ b/askbot/models/signals.py
@@ -21,6 +21,7 @@ delete_question_or_answer = django.dispatch.Signal(
flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by'])
remove_flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by'])
user_updated = django.dispatch.Signal(providing_args=['instance', 'updated_by'])
+user_registered = django.dispatch.Signal(providing_args=['user',])
#todo: move this to authentication app
user_logged_in = django.dispatch.Signal(providing_args=['session'])
@@ -65,6 +66,7 @@ def pop_all_db_signal_receivers():
remove_flag_offensive,
user_updated,
user_logged_in,
+ user_registered,
post_updated,
award_badges_signal,
#django signals
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 779c0b68..5a83c67c 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -82,21 +82,34 @@ class TagManager(BaseQuerySetManager):
def get_query_set(self):
return TagQuerySet(self.model)
-#todo: implement this
-#class GroupTagQuerySet(models.query.QuerySet):
-# """Custom query set for the group"""
-# def __init__(self, model):
+class GroupTagQuerySet(TagQuerySet):
+ """Custom query set for the group"""
+
+ def get_for_user(self, user = None):
+ return self.filter(user_memberships__user = user)
+
+ def get_all(self):
+ return self.annotate(
+ member_count = models.Count('user_memberships')
+ ).filter(
+ member_count__gt = 0
+ )
+
+ def get_by_name(self, group_name = None):
+ return self.get(name = clean_group_name(group_name))
+
+
def clean_group_name(name):
"""group names allow spaces,
tag names do not, so we use this method
to replace spaces with dashes"""
return re.sub('\s+', '-', name.strip())
-class GroupTagManager(TagManager):
+class GroupTagManager(BaseQuerySetManager):
"""manager for group tags"""
-# def get_query_set(self):
-# return GroupTagQuerySet(self.model)
+ def get_query_set(self):
+ return GroupTagQuerySet(self.model)
def get_or_create(self, group_name = None, user = None):
"""creates a group tag or finds one, if exists"""
@@ -114,21 +127,6 @@ class GroupTagManager(TagManager):
group_profile.save()
return tag
- #todo: maybe move this to query set
- def get_for_user(self, user = None):
- return self.filter(user_memberships__user = user)
-
- #todo: remove this when the custom query set is done
- def get_all(self):
- return self.annotate(
- member_count = models.Count('user_memberships')
- ).filter(
- member_count__gt = 0
- )
-
- def get_by_name(self, group_name = None):
- return self.get(name = clean_group_name(group_name))
-
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
created_by = models.ForeignKey(User, related_name='created_tags')
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 191d13f3..32af9920 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -98,6 +98,7 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
#'django.middleware.gzip.GZipMiddleware',
+ #'askbot.middleware.locale.LocaleMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
#'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index 3287fb18..eb1cb1c1 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -97,6 +97,7 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
#'django.middleware.gzip.GZipMiddleware',
+ 'askbot.middleware.locale.LocaleMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
#'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
diff --git a/askbot/skins/common/media/js/editor.js b/askbot/skins/common/media/js/editor.js
index 2d1f5670..ae4f5aea 100644
--- a/askbot/skins/common/media/js/editor.js
+++ b/askbot/skins/common/media/js/editor.js
@@ -46,6 +46,11 @@ function ajaxFileUpload(imageUrl, startUploadHandler)
success: function (data, status)
{
var fileURL = $(data).find('file_url').text();
+ /*
+ * hopefully a fix for the "fakepath" issue
+ * https://www.mediawiki.org/wiki/Special:Code/MediaWiki/83225
+ */
+ fileURL = fileURL.replace(/\w:.*\\(.*)$/,'$1');
var error = $(data).find('error').text();
if(error != ''){
alert(error);
diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js
index 294c5f41..7df01c83 100644
--- a/askbot/skins/common/media/js/post.js
+++ b/askbot/skins/common/media/js/post.js
@@ -2354,9 +2354,8 @@ UserGroupProfileEditor.prototype.decorate = function(element){
logo_changer.decorate(change_logo_btn);
};
-var GroupJoinButton = function(group_id){
+var GroupJoinButton = function(){
TwoStateToggle.call(this);
- this._group_id = group_id;
};
inherits(GroupJoinButton, TwoStateToggle);
@@ -2375,7 +2374,8 @@ GroupJoinButton.prototype.getHandler = function(){
url: askbot['urls']['join_or_leave_group'],
success: function(data){
if (data['success']){
- me.setOn(data['is_member']);
+ var new_state = data['is_member'] ? 'on-state':'off-state';
+ me.setState(new_state);
} else {
showMessage(me.getElement(), data['message']);
}
@@ -2384,6 +2384,11 @@ GroupJoinButton.prototype.getHandler = function(){
};
};
+GroupJoinButton.prototype.decorate = function(elem) {
+ GroupJoinButton.superClass_.decorate.call(this, elem);
+ this._group_id = this._element.data('groupId');
+};
+
$(document).ready(function() {
$('[id^="comments-for-"]').each(function(index, element){
var comments = new PostCommentsWidget();
diff --git a/askbot/skins/common/media/js/user.js b/askbot/skins/common/media/js/user.js
index b7dc0951..ad0b8365 100644
--- a/askbot/skins/common/media/js/user.js
+++ b/askbot/skins/common/media/js/user.js
@@ -644,13 +644,13 @@ UserGroup.prototype.decorate = function(element){
this._name = $.trim(element.find('a').html());
var deleter = new DeleteIcon();
deleter.setHandler(this.getDeleteHandler());
- deleter.setContent('x');
- this._element.find('.group-name').append(deleter.getElement());
+ deleter.setContent(gettext('Remove'));
+ this._element.find('td:last').append(deleter.getElement());
this._delete_icon = deleter;
};
UserGroup.prototype.createDom = function(){
- var element = this.makeElement('li');
+ var element = this.makeElement('tr');
element.html(this._content);
this._element = element;
this.decorate(element);
@@ -675,7 +675,7 @@ GroupsContainer.prototype.decorate = function(element){
var group_names = [];
var me = this;
//collect list of groups
- $.each(element.find('li'), function(idx, li){
+ $.each(element.find('tr'), function(idx, li){
var group = new UserGroup();
group.setGroupsContainer(me);
group.decorate($(li));
@@ -829,21 +829,25 @@ UserGroupsEditor.prototype.decorate = function(element){
adder.decorate(add_link);
var groups_container = new GroupsContainer();
- groups_container.decorate(element.find('ul'));
+ groups_container.decorate(element.find('#groups-list'));
adder.setGroupsContainer(groups_container);
//todo - add group deleters
};
(function(){
- var fbtn = $('.follow-toggle');
+ var fbtn = $('.follow-user-toggle');
if (fbtn.length === 1){
var follow_user = new FollowUser();
follow_user.decorate(fbtn);
follow_user.setUserName(askbot['data']['viewUserName']);
}
- if (askbot['data']['userIsAdminOrMod']){
- var group_editor = new UserGroupsEditor();
- group_editor.decorate($('#user-groups'));
+ if (askbot['data']['userId'] !== askbot['data']['viewUserId']) {
+ if (askbot['data']['userIsAdminOrMod']){
+ var group_editor = new UserGroupsEditor();
+ group_editor.decorate($('#user-groups'));
+ } else {
+ $('#add-group').remove();
+ }
} else {
$('#add-group').remove();
}
diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js
index 297e3f9a..8c2a478e 100644
--- a/askbot/skins/common/media/js/utils.js
+++ b/askbot/skins/common/media/js/utils.js
@@ -786,9 +786,7 @@ TwoStateToggle.prototype.decorate = function(element){
this.toggleUrl = element.attr('data-toggle-url');
//detect state and save it
- if (
- element.attr('nodeName') === 'INPUT' && element.attr('type', 'checkbox')
- ) {
+ if (this.isCheckBox()) {
this._state = element.attr('checked') ? 'state-on' : 'state-off';
} else {
var text = $.trim(element.html());
diff --git a/askbot/skins/default/media/bootstrap/css/bootstrap.css b/askbot/skins/default/media/bootstrap/css/bootstrap.css
index 9447a9a2..3e829732 100644
--- a/askbot/skins/default/media/bootstrap/css/bootstrap.css
+++ b/askbot/skins/default/media/bootstrap/css/bootstrap.css
@@ -918,18 +918,6 @@ input,
textarea,
select,
.uneditable-input {
- display: inline-block;
- width: 210px;
- height: 18px;
- padding: 4px;
- margin-bottom: 9px;
- font-size: 13px;
- line-height: 18px;
- color: #555555;
- border: 1px solid #cccccc;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
}
.uneditable-textarea {
width: auto;
diff --git a/askbot/skins/default/media/images/OFL.txt b/askbot/skins/default/media/images/OFL.txt
new file mode 100644
index 00000000..3bc11311
--- /dev/null
+++ b/askbot/skins/default/media/images/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2010, Jan Gerner (post@yanone.de)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/askbot/skins/default/media/images/YanoneKaffeesatz-Bold.ttf b/askbot/skins/default/media/images/YanoneKaffeesatz-Bold.ttf
new file mode 100644
index 00000000..c693c4b3
--- /dev/null
+++ b/askbot/skins/default/media/images/YanoneKaffeesatz-Bold.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/YanoneKaffeesatz-ExtraLight.ttf b/askbot/skins/default/media/images/YanoneKaffeesatz-ExtraLight.ttf
new file mode 100644
index 00000000..b59e4894
--- /dev/null
+++ b/askbot/skins/default/media/images/YanoneKaffeesatz-ExtraLight.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/YanoneKaffeesatz-Light.ttf b/askbot/skins/default/media/images/YanoneKaffeesatz-Light.ttf
new file mode 100644
index 00000000..5026d3bd
--- /dev/null
+++ b/askbot/skins/default/media/images/YanoneKaffeesatz-Light.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/YanoneKaffeesatz-Regular.ttf b/askbot/skins/default/media/images/YanoneKaffeesatz-Regular.ttf
new file mode 100644
index 00000000..808ce0d0
--- /dev/null
+++ b/askbot/skins/default/media/images/YanoneKaffeesatz-Regular.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/Yanone_Kaffeesatz.zip b/askbot/skins/default/media/images/Yanone_Kaffeesatz.zip
new file mode 100644
index 00000000..55e9731a
--- /dev/null
+++ b/askbot/skins/default/media/images/Yanone_Kaffeesatz.zip
Binary files differ
diff --git a/askbot/skins/default/media/style/lib_style.css b/askbot/skins/default/media/style/lib_style.css
new file mode 100644
index 00000000..a92af477
--- /dev/null
+++ b/askbot/skins/default/media/style/lib_style.css
@@ -0,0 +1,22 @@
+/* General Predifined classes, read more in lesscss.org */
+/* Variables for Colors*/
+/* Variables for fonts*/
+/* "Trebuchet MS", sans-serif;*/
+/* Buttons */
+.button-style-hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+}
+/* General styles for gradients */
+/* Receive exactly positions for background Sprite */
+/* CSS3 Elements */
diff --git a/askbot/skins/default/media/style/lib_style.less b/askbot/skins/default/media/style/lib_style.less
index 795733e5..63389526 100644
--- a/askbot/skins/default/media/style/lib_style.less
+++ b/askbot/skins/default/media/style/lib_style.less
@@ -17,6 +17,43 @@
@main-font:'Yanone Kaffeesatz', Arial, sans-serif;
@secondary-font:Arial;
+/* Buttons */
+
+.button-style(@w:100px ,@h:20px, @f:14px){
+ width:@w;
+ height:@h;
+ font-size:@f;
+ text-align:center;
+ text-decoration:none;
+ cursor:pointer;
+ color:@button-label;
+ font-family:@main-font;
+ .text-shadow(0px,1px,0px,#c6d9dd);
+ border-top:#eaf2f3 1px solid;
+ .linear-gradient(#d1e2e5,#a9c2c7);
+ .rounded-corners(4px);
+ .box-shadow(1px, 1px, 2px, #636363)
+}
+
+.button-style-hover{
+ .linear-gradient(#cde5e9,#94b3ba);
+ text-decoration:none;
+ .text-shadow(0px, 1px, 0px, #c6d9dd);
+}
+
+/* General styles for gradients */
+
+.linear-gradient(@start:#eee,@end:#fff,@stop:25%){
+ background-color: @start;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@start), color-stop(@stop, @start), to(@end));
+ background-image: -webkit-linear-gradient(@start, @start @stop, @end);
+ background-image: -moz-linear-gradient(top, @start, @start @stop, @end);
+ background-image: -ms-linear-gradient(@start, @start @stop, @end);
+ background-image: -o-linear-gradient(@start, @start @stop, @end);
+ background-image: linear-gradient(@start, @start @stop, @end);
+}
+
/* Receive exactly positions for background Sprite */
.sprites(@hor,@vert,@back:url(../images/sprites.png)){
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index 73664c57..3d53eba8 100644
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -3,6 +3,22 @@
/* Variables for Colors*/
/* Variables for fonts*/
/* "Trebuchet MS", sans-serif;*/
+/* Buttons */
+.button-style-hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+}
+/* General styles for gradients */
/* Receive exactly positions for background Sprite */
/* CSS3 Elements */
/* Library of predifined less functions styles */
@@ -148,7 +164,7 @@ a:hover {
}
h1 {
font-size: 24px;
- padding: 10px 0 5px 0px;
+ padding: 0px 0 5px 0px;
}
/* ----- Extra space above for messages ----- */
body.user-messages {
@@ -179,7 +195,7 @@ body.user-messages {
text-align: center;
background-color: #f5dd69;
border-top: #fff 1px solid;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.notify p.notification {
margin-top: 6px;
@@ -206,7 +222,7 @@ body.user-messages {
#header {
margin-top: 0px;
background: #16160f;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.content-wrapper {
/* wrapper positioning class */
@@ -295,6 +311,9 @@ body.user-messages {
#metaNav #navUsers {
background: -125px -5px url(../images/sprites.png) no-repeat;
}
+#metaNav #navGroups {
+ background: -125px -5px url(../images/sprites.png) no-repeat;
+}
#metaNav #navBadges {
background: -210px -5px url(../images/sprites.png) no-repeat;
}
@@ -318,7 +337,7 @@ body.user-messages {
border-bottom: #d3d3c2 1px solid;
border-top: #fcfcfc 1px solid;
margin-bottom: 10px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
#secondaryHeader #homeButton {
border-right: #afaf9e 1px solid;
@@ -427,28 +446,49 @@ body.anon #searchBar .searchInputCancelable {
#askButton {
/* check blocks/secondary_header.html and widgets/ask_button.html*/
- background: url(../images/bigbutton.png) repeat-x bottom;
line-height: 44px;
- text-align: center;
+ margin-top: 6px;
+ float: right;
+ text-transform: uppercase;
width: 200px;
height: 42px;
font-size: 23px;
+ text-align: center;
+ text-decoration: none;
+ cursor: pointer;
color: #4a757f;
- margin-top: 7px;
- float: right;
- text-transform: uppercase;
- border-radius: 5px;
- -ms-border-radius: 5px;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
- -khtml-border-radius: 5px;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ border-radius: 4px;
+ -ms-border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ -khtml-border-radius: 4px;
-webkit-box-shadow: 1px 1px 2px #636363;
-moz-box-shadow: 1px 1px 2px #636363;
box-shadow: 1px 1px 2px #636363;
}
#askButton:hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
text-decoration: none;
- background: url(../images/bigbutton.png) repeat-x top;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -481,6 +521,8 @@ body.anon #searchBar .searchInputCancelable {
}
.box p {
margin-bottom: 4px;
+ color: #707070;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.box p.info-box-follow-up-links {
text-align: right;
@@ -497,14 +539,15 @@ body.anon #searchBar .searchInputCancelable {
color: #656565;
padding-right: 10px;
margin-bottom: 10px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ width: 190px;
}
.box h3 {
color: #4a757f;
font-size: 18px;
text-align: left;
font-weight: normal;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
padding-left: 0px;
}
.box .contributorback {
@@ -516,12 +559,13 @@ body.anon #searchBar .searchInputCancelable {
display: block;
float: right;
text-align: left;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
width: 80px;
margin-right: 18px;
}
-.box #displayTagFilterControl label {
- /*Especial width just for the display tag filter box in index page*/
+.box #displayTagFilterControl label,
+.box #emailTagFilterControl label {
+ /*Especial width just for the tag filter boxes in index page*/
width: 160px;
}
@@ -547,57 +591,108 @@ body.anon #searchBar .searchInputCancelable {
font-size: 15px;
}
.box .inputs #interestingTagInput,
-.box .inputs #ignoredTagInput {
+.box .inputs #ignoredTagInput,
+.box .inputs #subscribedTagInput,
+.box .inputs #ab-tag-search {
width: 153px;
padding-left: 5px;
border: #c9c9b5 1px solid;
height: 25px;
}
+.box .inputs #ab-tag-search {
+ width: 135px;
+}
.box .inputs #interestingTagAdd,
-.box .inputs #ignoredTagAdd {
- background: url(../images/small-button-blue.png) repeat-x top;
+.box .inputs #ignoredTagAdd,
+.box .inputs #subscribedTagAdd,
+.box .inputs #ab-tag-search-add {
border: 0;
- color: #4a757f;
font-weight: bold;
- font-size: 12px;
+ margin-top: -2px;
width: 30px;
height: 27px;
- margin-top: -2px;
+ font-size: 14px;
+ text-align: center;
+ text-decoration: none;
cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ border-radius: 4px;
+ -ms-border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ -khtml-border-radius: 4px;
+ -webkit-box-shadow: 1px 1px 2px #636363;
+ -moz-box-shadow: 1px 1px 2px #636363;
+ box-shadow: 1px 1px 2px #636363;
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-khtml-border-radius: 4px;
- text-shadow: 0px 1px 0px #e6f6fa;
- -moz-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-box-shadow: 1px 1px 2px #808080;
- -moz-box-shadow: 1px 1px 2px #808080;
- box-shadow: 1px 1px 2px #808080;
}
.box .inputs #interestingTagAdd:hover,
-.box .inputs #ignoredTagAdd:hover {
- background: url(../images/small-button-blue.png) repeat-x bottom;
+.box .inputs #ignoredTagAdd:hover,
+.box .inputs #ab-tag-search-add:hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+}
+.box .inputs #ab-tag-search-add {
+ width: 47px;
+ margin-left: 3px;
}
.box img.gravatar {
margin: 1px;
}
.box a.followed,
.box a.follow {
- background: url(../images/medium-button.png) top repeat-x;
- height: 34px;
line-height: 34px;
- text-align: center;
border: 0;
- font-family: 'Yanone Kaffeesatz', sans-serif;
- color: #4a757f;
font-weight: normal;
- font-size: 21px;
margin-top: 3px;
display: block;
width: 120px;
+ height: 34px;
+ font-size: 21px;
+ text-align: center;
text-decoration: none;
+ cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
@@ -611,8 +706,18 @@ body.anon #searchBar .searchInputCancelable {
}
.box a.followed:hover,
.box a.follow:hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
text-decoration: none;
- background: url(../images/medium-button.png) bottom repeat-x;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -638,6 +743,10 @@ body.anon #searchBar .searchInputCancelable {
.box .notify-sidebar #question-subscribe-sidebar {
margin: 7px 0 0 3px;
}
+.users-page .box label {
+ display: inline;
+ float: none;
+}
.statsWidget p {
color: #707070;
font-size: 16px;
@@ -730,8 +839,7 @@ body.anon #searchBar .searchInputCancelable {
.tabsC .label {
float: left;
color: #646464;
- margin-top: 4px;
- margin-right: 5px;
+ margin: 4px 5px 0px 8px;
}
.main-page .tabsA .label {
margin-left: 8px;
@@ -776,14 +884,14 @@ body.anon #searchBar .searchInputCancelable {
float: left;
margin-bottom: 8px;
padding-top: 6px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
#listSearchTags {
float: left;
margin-top: 3px;
color: #707070;
font-size: 16px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
ul#searchTags {
margin-left: 10px;
@@ -797,7 +905,7 @@ ul#searchTags {
margin: 5px 0 10px 0;
padding: 0px;
float: left;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.search-tips a {
text-decoration: underline;
@@ -811,6 +919,9 @@ ul#searchTags {
padding: 0;
width: 100%;
}
+.main-page #question-list {
+ margin-top: 10px;
+}
.short-summary {
position: relative;
filter: inherit;
@@ -829,7 +940,7 @@ ul#searchTags {
padding-left: 0;
margin-bottom: 6px;
display: block;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.short-summary a {
color: #464646;
@@ -840,7 +951,7 @@ ul#searchTags {
font-family: Arial;
padding-right: 4px;
}
-.short-summary .userinfo .relativetime,
+.short-summary .userinfo .timeago,
.short-summary span.anonymous {
font-size: 11px;
clear: both;
@@ -854,12 +965,12 @@ ul#searchTags {
.short-summary .counts {
float: right;
margin: 4px 0 0 5px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.short-summary .counts .item-count {
padding: 0px 5px 0px 5px;
font-size: 25px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.short-summary .counts .votes div,
.short-summary .counts .views div,
@@ -1049,7 +1160,7 @@ ul#related-tags {
ul.tags li {
float: left;
display: block;
- margin: 0 8px 0 0;
+ margin: 0 8px 8px 0;
padding: 0;
height: 20px;
}
@@ -1173,7 +1284,7 @@ ul#related-tags li {
/* ----- Ask and Edit Question Form template----- */
.section-title {
color: #7ea9b3;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
font-weight: bold;
font-size: 24px;
}
@@ -1199,7 +1310,7 @@ ul#related-tags li {
margin: 0px;
padding: 0px 0 0 5px;
border: #cce6ec 3px solid;
- width: 725px;
+ width: 719px;
}
.ask-page div#question-list,
.edit-question-page div#question-list {
@@ -1261,14 +1372,28 @@ ul#related-tags li {
.ask-page input.submit,
.edit-question-page input.submit {
float: left;
- background: url(../images/medium-button.png) top repeat-x;
- height: 34px;
- border: 0;
- font-family: 'Yanone Kaffeesatz', sans-serif;
- color: #4a757f;
font-weight: normal;
- font-size: 21px;
margin-top: 3px;
+ width: 160px;
+ height: 34px;
+ font-size: 21px;
+ text-align: center;
+ text-decoration: none;
+ cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
@@ -1282,33 +1407,60 @@ ul#related-tags li {
#fmanswer input.submit:hover,
.ask-page input.submit:hover,
.edit-question-page input.submit:hover {
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
text-decoration: none;
- background: url(../images/medium-button.png) bottom repeat-x;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+}
+.wmd-container {
+ border: #cce6ec 3px solid;
+}
+.users-page .wmd-container {
+ width: 200px;
+}
+.ask-page .wmd-container,
+.question-page .wmd-container,
+.edit-question-page .wmd-container,
+.edit-answer-page .wmd-container {
+ width: 723px;
+}
+.ask-page #editor,
+.question-page #editor,
+.edit-question-page #editor,
+.edit-answer-page #editor {
+ width: 710px;
+ padding: 6px;
}
#editor {
- /*adjustment for editor preview*/
+ /* adjustment for editor preview */
+ display: block;
font-size: 100%;
min-height: 200px;
line-height: 18px;
margin: 0;
- border-left: #cce6ec 3px solid;
- border-bottom: #cce6ec 3px solid;
- border-right: #cce6ec 3px solid;
- border-top: 0;
- padding: 10px;
- margin-bottom: 10px;
- width: 717px;
+ border: 0;
+}
+.users-page #editor {
+ width: 192px;
}
#id_title {
width: 100%;
}
.wmd-preview {
- margin: 3px 0 5px 0;
- padding: 6px;
+ margin: 0;
+ padding: 5px;
background-color: #F5F5F5;
min-height: 20px;
overflow: auto;
@@ -1320,6 +1472,9 @@ ul#related-tags li {
line-height: 1.4;
font-size: 14px;
}
+.wmd-preview p:last-child {
+ margin-bottom: 0;
+}
.wmd-preview pre {
background-color: #E7F1F8;
}
@@ -1329,6 +1484,9 @@ ul#related-tags li {
.wmd-preview IMG {
max-width: 600px;
}
+.user-page .wmd-buttons {
+ width: 725px;
+}
.preview-toggle {
width: 100%;
color: #b6a475;
@@ -1383,7 +1541,7 @@ ul#related-tags li {
margin: 0px;
padding: 0px 0 0 5px;
border: #cce6ec 3px solid;
- width: 725px;
+ width: 719px;
margin-bottom: 10px;
}
.edit-question-page #id_summary,
@@ -1403,7 +1561,7 @@ ul#related-tags li {
/* ----- Question template ----- */
.question-page h1 {
padding-top: 0px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.question-page h1 a {
color: #464646;
@@ -1421,7 +1579,7 @@ ul#related-tags li {
margin-left: 0px !important;
}
.question-page p.rss a {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
vertical-align: top;
}
.question-page .question-content {
@@ -1586,7 +1744,7 @@ ul#related-tags li {
}
.question-page #questionCount {
float: left;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
line-height: 15px;
}
.question-page .question-img-upvote,
@@ -1632,13 +1790,11 @@ ul#related-tags li {
color: #7ea9b3;
width: 200px;
float: left;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.question-page .comments {
font-size: 12px;
clear: both;
- /* A small hack to solve 1px problem on webkit browsers */
-
}
.question-page .comments div.controls {
clear: both;
@@ -1688,11 +1844,6 @@ ul#related-tags li {
padding-top: 3px;
border: #cce6ec 3px solid;
}
-@media screen and (-webkit-min-device-pixel-ratio: 0) {
- .question-page .comments textarea {
- padding-left: 3px !important;
- }
-}
.question-page .comments input {
margin-left: 10px;
margin-top: 1px;
@@ -1700,31 +1851,49 @@ ul#related-tags li {
width: 100px;
}
.question-page .comments button {
- background: url(../images/small-button-blue.png) repeat-x top;
- border: 0;
- color: #4a757f;
- font-family: Arial;
- font-size: 13px;
- width: 100px;
- font-weight: bold;
- height: 27px;
line-height: 25px;
margin-bottom: 5px;
+ width: 100px;
+ height: 27px;
+ font-size: 12px;
+ text-align: center;
+ text-decoration: none;
cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-khtml-border-radius: 4px;
- text-shadow: 0px 1px 0px #e6f6fa;
- -moz-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-box-shadow: 1px 1px 2px #808080;
- -moz-box-shadow: 1px 1px 2px #808080;
- box-shadow: 1px 1px 2px #808080;
+ -webkit-box-shadow: 1px 1px 2px #636363;
+ -moz-box-shadow: 1px 1px 2px #636363;
+ box-shadow: 1px 1px 2px #636363;
+ font-family: Arial;
+ font-weight: bold;
}
.question-page .comments button:hover {
- background: url(../images/small-button-blue.png) bottom repeat-x;
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -1865,12 +2034,17 @@ ul#related-tags li {
text-align: center;
padding-top: 2px;
margin: 10px 10px 0px 3px;
+ /* smalls IE fixes */
+
+ *margin: 0;
+ *height: 210px;
+ *width: 30px;
}
.question-page .vote-buttons IMG {
cursor: pointer;
}
.question-page .vote-number {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
padding: 0px 0 5px 0;
font-size: 25px;
font-weight: bold;
@@ -1948,7 +2122,7 @@ ul#related-tags li {
margin-top: 10px;
}
.question-page #fmanswer h2 {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
color: #7ea9b3;
font-size: 24px;
}
@@ -1993,7 +2167,6 @@ ul#related-tags li {
/* -----Content pages, Login, About, FAQ, Users----- */
.openid-signin,
.meta,
-.users-page,
.user-profile-edit-page {
font-size: 13px;
line-height: 1.3;
@@ -2001,7 +2174,6 @@ ul#related-tags li {
}
.openid-signin p,
.meta p,
-.users-page p,
.user-profile-edit-page p {
font-size: 13px;
color: #707070;
@@ -2012,7 +2184,6 @@ ul#related-tags li {
}
.openid-signin h2,
.meta h2,
-.users-page h2,
.user-profile-edit-page h2 {
color: #525252;
padding-left: 0px;
@@ -2070,35 +2241,55 @@ ul#related-tags li {
.users-page input.submit,
.user-profile-edit-page input.submit,
.user-profile-page input.submit {
- background: url(../images/small-button-blue.png) repeat-x top;
- border: 0;
- color: #4a757f;
- font-weight: bold;
- font-size: 13px;
- font-family: Arial;
- height: 26px;
+ font-weight: normal;
margin: 5px 0px;
width: 100px;
+ height: 26px;
+ font-size: 15px;
+ text-align: center;
+ text-decoration: none;
cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-khtml-border-radius: 4px;
- text-shadow: 0px 1px 0px #e6f6fa;
- -moz-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-box-shadow: 1px 1px 2px #808080;
- -moz-box-shadow: 1px 1px 2px #808080;
- box-shadow: 1px 1px 2px #808080;
+ -webkit-box-shadow: 1px 1px 2px #636363;
+ -moz-box-shadow: 1px 1px 2px #636363;
+ box-shadow: 1px 1px 2px #636363;
+ font-family: Arial;
}
.openid-signin input.submit:hover,
.meta input.submit:hover,
.users-page input.submit:hover,
.user-profile-edit-page input.submit:hover,
.user-profile-page input.submit:hover {
- background: url(../images/small-button-blue.png) repeat-x bottom;
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
}
.openid-signin .cancel,
.meta .cancel,
@@ -2115,6 +2306,9 @@ ul#related-tags li {
.user-profile-page .cancel:hover {
background: url(../images/small-button-cancel.png) repeat-x bottom !important;
}
+.openid-signin form {
+ margin-bottom: 5px;
+}
#email-input-fs,
#local_login_buttons,
#password-fs,
@@ -2145,34 +2339,55 @@ ul#related-tags li {
#local_login_buttons .submit-b,
#password-fs .submit-b,
#openid-fs .submit-b {
- background: url(../images/small-button-blue.png) repeat-x top;
- border: 0;
- color: #4a757f;
- font-weight: bold;
- font-size: 13px;
- font-family: Arial;
+ width: 100px;
height: 24px;
- margin-top: -2px;
- padding-left: 10px;
- padding-right: 10px;
+ font-size: 15px;
+ text-align: center;
+ text-decoration: none;
cursor: pointer;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-khtml-border-radius: 4px;
- text-shadow: 0px 1px 0px #e6f6fa;
- -moz-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-box-shadow: 1px 1px 2px #808080;
- -moz-box-shadow: 1px 1px 2px #808080;
- box-shadow: 1px 1px 2px #808080;
+ -webkit-box-shadow: 1px 1px 2px #636363;
+ -moz-box-shadow: 1px 1px 2px #636363;
+ box-shadow: 1px 1px 2px #636363;
+ font-family: Arial;
+ font-weight: bold;
+ padding-right: 10px;
+ border: 0;
}
#email-input-fs .submit-b:hover,
#local_login_buttons .submit-b:hover,
#password-fs .submit-b:hover,
#openid-fs .submit-b:hover {
- background: url(../images/small-button-blue.png) repeat-x bottom;
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
}
.openid-input {
background: url(../images/openid.gif) no-repeat;
@@ -2199,20 +2414,15 @@ ul#related-tags li {
font-size: 120%;
}
/* People page */
-.tabBar-user {
- width: 375px;
-}
+/*.users-page .tabBar{
+ width:375px;
+}*/
.user {
- padding: 5px;
+ padding: 5px 10px 5px 0;
line-height: 140%;
width: 166px;
- border: #eee 1px solid;
+ height: 32px;
margin-bottom: 5px;
- border-radius: 3px;
- -ms-border-radius: 3px;
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- -khtml-border-radius: 3px;
}
.user .user-micro-info {
color: #525252;
@@ -2280,7 +2490,7 @@ a:hover.medal {
}
.user-profile-page h2 {
padding: 10px 0px 10px 0px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
.user-details {
font-size: 13px;
@@ -2302,31 +2512,52 @@ a:hover.medal {
.follow-toggle,
.submit {
border: 0 !important;
- color: #4a757f;
font-weight: bold;
- font-size: 12px;
- height: 26px;
line-height: 26px;
margin-top: -2px;
- font-size: 15px;
+ width: 100px;
+ height: 26px;
+ font-size: 12px;
+ text-align: center;
+ text-decoration: none;
cursor: pointer;
- font-family: 'Yanone Kaffeesatz', sans-serif;
- background: url(../images/small-button-blue.png) repeat-x top;
+ color: #4a757f;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
+ border-top: #eaf2f3 1px solid;
+ background-color: #d1e2e5;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
+ background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
+ background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7);
border-radius: 4px;
-ms-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-khtml-border-radius: 4px;
- text-shadow: 0px 1px 0px #e6f6fa;
- -moz-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-text-shadow: 0px 1px 0px #e6f6fa;
- -webkit-box-shadow: 1px 1px 2px #808080;
- -moz-box-shadow: 1px 1px 2px #808080;
- box-shadow: 1px 1px 2px #808080;
+ -webkit-box-shadow: 1px 1px 2px #636363;
+ -moz-box-shadow: 1px 1px 2px #636363;
+ box-shadow: 1px 1px 2px #636363;
}
.follow-toggle:hover,
.submit:hover {
- background: url(../images/small-button-blue.png) repeat-x bottom;
+ background-color: #cde5e9;
+ background-repeat: no-repeat;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#cde5e9), color-stop(25%, #cde5e9), to(#94b3ba));
+ background-image: -webkit-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -moz-linear-gradient(top, #cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -ms-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: -o-linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ background-image: linear-gradient(#cde5e9, #cde5e9 25%, #94b3ba);
+ text-decoration: none;
+ text-shadow: 0px 1px 0px #c6d9dd;
+ -moz-text-shadow: 0px 1px 0px #c6d9dd;
+ -webkit-text-shadow: 0px 1px 0px #c6d9dd;
text-decoration: none !important;
}
.follow-toggle .follow {
@@ -2346,13 +2577,13 @@ a:hover.medal {
display: none;
}
.count {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
font-size: 200%;
font-weight: 700;
color: #777777;
}
.scoreNumber {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
font-size: 35px;
font-weight: 800;
color: #777;
@@ -2463,7 +2694,7 @@ a:hover.medal {
color: #525252;
}
.revision h3 {
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
font-size: 21px;
padding-left: 0px;
}
@@ -2570,7 +2801,7 @@ ins {
padding: 6px 0 0 0;
background: #16160f;
font-size: 16px;
- font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
}
#ground p {
margin-bottom: 0;
@@ -2714,7 +2945,7 @@ span.form-error {
padding: 0px;
margin: 0px;
}
-.relativetime {
+.timeago {
font-weight: bold;
text-decoration: none;
}
@@ -2935,6 +3166,9 @@ button::-moz-focus-inner {
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
}
+.list-table {
+ border-spacing: 0;
+}
.list-table td {
vertical-align: top;
}
@@ -3144,7 +3378,6 @@ img.flag {
.main-page img.flag {
vertical-align: text-bottom;
}
-
/* Pretty printing styles. Used with prettify.js. */
a.edit {
padding-left: 3px;
@@ -3247,3 +3480,110 @@ body.anon.lang-es #searchBar .searchInput {
body.anon.lang-es #searchBar .searchInputCancelable {
width: 390px;
}
+/* user groups */
+#user-groups ul {
+ margin-bottom: 0px;
+}
+#user-groups .delete-icon {
+ float: none;
+ display: inline;
+ color: #525252;
+ padding: 0 3px 0 3px;
+ background: #ccc;
+ border-radius: 4px;
+ line-height: inherit;
+ -moz-border-radius: 4px;
+ -khtml-border-radius: 4px;
+ -webkit-border-radius: 4px;
+}
+#user-groups .delete-icon:hover {
+ color: white;
+ background: #b32f2f;
+}
+.users-page .wmd-prompt-dialog {
+ background: #ccc;
+}
+.group-wiki .content > p:last-child {
+ margin-bottom: 5px;
+}
+.group-wiki .group-logo {
+ float: left;
+ margin: 0 5px 3px 0;
+}
+.group-wiki .follow-toggle.group-join-btn {
+ width: 150px;
+ margin: 4px auto 10px auto;
+ display: block;
+}
+.group-wiki .controls {
+ margin: 0 0 10px 0;
+}
+img.group-logo {
+ height: 60px;
+ /* important to align with the line spacing */
+
+}
+#groups-list {
+ margin-left: 0px;
+}
+#groups-list li {
+ display: inline;
+ list-style-type: none;
+ list-style-position: inside;
+ float: left;
+ text-align: center;
+}
+#groups-list .group-logo,
+#groups-list .group-name {
+ display: block;
+}
+#reject-edit-modal input,
+#reject-edit-modal textarea {
+ width: 514px;
+}
+input.tipped-input,
+textarea.tipped-input {
+ padding-left: 5px;
+}
+.tipped-input.blank {
+ color: #707070;
+}
+.select-box {
+ margin: 0;
+}
+.select-box li {
+ list-style-type: none;
+ list-style-position: inside;
+ padding-left: 7px;
+ font-size: 14px;
+ line-height: 25px;
+}
+.select-box li.selected,
+.select-box li.selected:hover {
+ background-color: #fcf8e3;
+ color: #c09853;
+}
+.select-box li:hover {
+ background-color: #cecece;
+ color: white;
+}
+/* fixes for bootstrap */
+.caret {
+ margin-bottom: 7px;
+}
+.btn-group {
+ text-align: left;
+}
+.btn-toolbar {
+ margin: 0;
+}
+.modal-footer {
+ text-align: left;
+}
+.modal p {
+ font-size: 14px;
+}
+.modal-body > textarea {
+ width: 515px;
+ margin-bottom: 0px;
+}
diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less
index 69cdede0..f015ae0c 100644
--- a/askbot/skins/default/media/style/style.less
+++ b/askbot/skins/default/media/style/style.less
@@ -154,6 +154,7 @@ h1 {
padding: 0px 0 5px 0px;
}
+
/* ----- Extra space above for messages ----- */
body.user-messages {
@@ -467,24 +468,15 @@ body.anon {
#askButton{ /* check blocks/secondary_header.html and widgets/ask_button.html*/
- background: url(../images/bigbutton.png) repeat-x bottom;
line-height:44px;
- text-align:center;
- width:200px;
- height:42px;
- font-size:23px;
- color:@button-label;
- margin-top:7px;
+ margin-top:6px;
float:right;
text-transform:uppercase;
- .rounded-corners(5px);
- .box-shadow(1px, 1px, 2px, #636363)
+ .button-style(200px, 42px, 23px);
}
#askButton:hover{
- text-decoration:none;
- background: url(../images/bigbutton.png) repeat-x top;
- .text-shadow(0px, 1px, 0px, #c6d9dd)
+ .button-style-hover;
}
/* ----- Content layout, check two_column_body.html or one_column_body.html ----- */
@@ -537,6 +529,7 @@ body.anon {
padding-right:10px;
margin-bottom:10px;
font-family:@main-font;
+ width:190px;
}
h3{
color:#4a757f;
@@ -606,26 +599,21 @@ body.anon {
#ignoredTagAdd,
#subscribedTagAdd,
#ab-tag-search-add {
- background:url(../images/small-button-blue.png) repeat-x top;
border:0;
- color:@button-label;
font-weight:bold;
- font-size:12px;
- width:30px;
- height:27px;
margin-top:-2px;
- cursor:pointer;
+ .button-style(30px, 27px, 14px);
.rounded-corners(4px);
- .text-shadow(0px,1px,0px,#E6F6FA);
- .box-shadow(1px, 1px, 2px, #808080);
+ }
+ #interestingTagAdd:hover,
+ #ignoredTagAdd:hover,
+ #ab-tag-search-add:hover {
+ .button-style-hover;
}
#ab-tag-search-add {
width: 47px;
margin-left: 3px;
}
- #interestingTagAdd:hover, #ignoredTagAdd:hover, #subscribedTag:hover {
- background:url(../images/small-button-blue.png) repeat-x bottom;
- }
}
img.gravatar {
@@ -635,28 +623,17 @@ body.anon {
/* widgets for question template */
a.followed, a.follow{
- background: url(../images/medium-button.png) top repeat-x;
- height:34px;
line-height:34px;
- text-align:center;
border:0;
- font-family:@main-font;
- color:@button-label;
font-weight:normal;
- font-size:21px;
margin-top:3px;
-
display:block;
- width:120px;
- text-decoration:none;
- .rounded-corners(4px);
- .box-shadow(1px, 1px, 2px, #636363);
+ .button-style(120px,34px,21px);
.center;
}
a.followed:hover, a.follow:hover{
- text-decoration:none;
- background: url(../images/medium-button.png) bottom repeat-x;
+ .button-style-hover;
.text-shadow(0px, 1px, 0px, #c6d9dd);
}
@@ -1159,7 +1136,7 @@ ul#related-tags {
ul.tags li {
float:left;
display: block;
- margin: 0 8px 0 0;
+ margin: 0 8px 8px 0;
padding: 0;
height:20px;
}
@@ -1239,7 +1216,9 @@ ul#related-tags li {
color: #1A1A1A;
}
-.users-page h1, .tags-page h1 {
+.users-page h1,
+.tags-page h1,
+.groups-page h1 {
float: left;
}
@@ -1377,24 +1356,16 @@ ul#related-tags li {
.ask-page input.submit,
.edit-question-page input.submit {
float: left;
- background: url(../images/medium-button.png) top repeat-x;
- height:34px;
- border:0;
- font-family:@main-font;
- color:@button-label;
font-weight:normal;
- font-size:21px;
margin-top:3px;
- .rounded-corners(4px);
- .box-shadow(1px, 1px, 2px, #636363);
+ .button-style(160px,34px,21px);
margin-right:7px;
}
#fmanswer input.submit:hover,
.ask-page input.submit:hover,
.edit-question-page input.submit:hover{
- text-decoration:none;
- background: url(../images/medium-button.png) bottom repeat-x;
+ .button-style-hover;
.text-shadow(0px, 1px, 0px, #c6d9dd)
}
@@ -1841,24 +1812,14 @@ ul#related-tags li {
width: 100px;
}
button{
- background:url(../images/small-button-blue.png) repeat-x top;
- border:0;
- color:@button-label;
- font-family:@body-font;
- font-size:13px;
- width:100px;
- font-weight:bold;
- height:27px;
line-height:25px;
margin-bottom:5px;
- cursor:pointer;
- .rounded-corners(4px);
- .text-shadow(0px,1px,0px,#E6F6FA);
- .box-shadow(1px, 1px, 2px, #808080);
+ .button-style(100px, 27px, 12px);
+ font-family:@body-font;
+ font-weight:bold;
}
button:hover{
- background: url(../images/small-button-blue.png) bottom repeat-x;
- .text-shadow(0px, 1px, 0px, #c6d9dd);
+ .button-style-hover;
}
.counter {
display: inline-block;
@@ -2003,6 +1964,10 @@ ul#related-tags li {
text-align: center;
padding-top: 2px;
margin:10px 10px 0px 3px;
+ /* smalls IE fixes */
+ *margin:0;
+ *height:210px;
+ *width:30px;
}
.vote-buttons IMG {
@@ -2199,23 +2164,13 @@ ul#related-tags li {
font-size:14px;
}
input.submit{
- background:url(../images/small-button-blue.png) repeat-x top;
- border:0;
- color:@button-label;
- font-weight:bold;
- font-size:13px;
- font-family:@body-font;
- height:26px;
+ font-weight:normal;
margin:5px 0px;
- width:100px;
- cursor:pointer;
- .rounded-corners(4px);
- .text-shadow(0px,1px,0px,#E6F6FA);
- .box-shadow(1px, 1px, 2px, #808080);
+ .button-style(100px,26px,15px);
+ font-family:@body-font;
}
input.submit:hover{
- background:url(../images/small-button-blue.png) repeat-x bottom;
- text-decoration:none;
+ .button-style-hover;
}
.cancel{
background:url(../images/small-button-cancel.png) repeat-x top !important;
@@ -2242,25 +2197,19 @@ ul#related-tags li {
width:200px;
}
.submit-b{
- background:url(../images/small-button-blue.png) repeat-x top;
- border:0;
- color:@button-label;
- font-weight:bold;
- font-size:13px;
+ .button-style(100px,24px,15px);
font-family:@body-font;
- height:24px;
- margin-top:-2px;
- padding-left:10px;
+ font-weight:bold;
padding-right:10px;
- cursor:pointer;
- .rounded-corners(4px);
- .text-shadow(0px,1px,0px,#E6F6FA);
- .box-shadow(1px, 1px, 2px, #808080)
+ border:0;
}
+
.submit-b:hover{
- background:url(../images/small-button-blue.png) repeat-x bottom;
+ .button-style-hover;
}
}
+
+
.openid-input {
background: url(../images/openid.gif) no-repeat;
padding-left: 15px;
@@ -2404,23 +2353,14 @@ a:hover.medal {
.follow-toggle,.submit {
border:0 !important;
- color:@button-label;
font-weight:bold;
- font-size:12px;
- height:26px;
line-height:26px;
margin-top:-2px;
- font-size:15px;
- cursor:pointer;
- font-family:@main-font;
- background:url(../images/small-button-blue.png) repeat-x top;
- .rounded-corners(4px);
- .text-shadow(0px,1px,0px,#E6F6FA);
- .box-shadow(1px, 1px, 2px, #808080)
+ .button-style(100px,26px,14px);
}
.follow-toggle:hover, .submit:hover {
- background:url(../images/small-button-blue.png) repeat-x bottom;
+ .button-style-hover;
text-decoration:none !important;
}
@@ -3487,19 +3427,14 @@ img.group-logo {
#groups-list {
margin-left: 0px;
- li {
- display: inline;
- list-style-type: none;
- list-style-position: inside;
- float: left;
- text-align: center;
+ .group-name {
+ padding-right: 20px;
}
- .group-logo, .group-name {
- display: block;
+ td {
+ padding-bottom: 5px;
}
}
-
#reject-edit-modal {
input, textarea {
width: 514px;
diff --git a/askbot/skins/default/templates/badges.html b/askbot/skins/default/templates/badges.html
index 668de4f8..ce76e76b 100644
--- a/askbot/skins/default/templates/badges.html
+++ b/askbot/skins/default/templates/badges.html
@@ -38,7 +38,7 @@ badges? Please, give us your <a href='{{feedback_faq_url}}'>feedback</a>
<a style="cursor:default;" title="{% trans %}gold badge: the highest honor and is very rare{% endtrans %}" class="medal"><span class="badge1">&#9679;</span>&nbsp;{% trans %}gold{% endtrans %}</a>
</p>
<p>
- {% trans %}Gold badge is the highest award in this community. To obtain it have to show
+ {% trans %}Gold badge is the highest award in this community. To obtain it you have to show
profound knowledge and ability in addition to your active participation.{% endtrans %}
</p>
<p>
@@ -48,7 +48,7 @@ profound knowledge and ability in addition to your active participation.{% endtr
class="medal"><span class="badge2">&#9679;</span>&nbsp;{% trans %}silver{% endtrans %}</a>
</p>
<p>
- {% trans %}msgid "silver badge: occasionally awarded for the very high quality contributions{% endtrans %}
+ {% trans %}silver badge: occasionally awarded for the very high quality contributions{% endtrans %}
</p>
<p>
<a style="cursor:default;" title="{% trans %}bronze badge: often given as a special honor{% endtrans %}" class="medal">
diff --git a/askbot/skins/default/templates/feedback_email.txt b/askbot/skins/default/templates/email/feedback_email.txt
index a729066a..a729066a 100644
--- a/askbot/skins/default/templates/feedback_email.txt
+++ b/askbot/skins/default/templates/email/feedback_email.txt
diff --git a/askbot/skins/default/templates/email/footer.html b/askbot/skins/default/templates/email/footer.html
new file mode 100644
index 00000000..eda1269d
--- /dev/null
+++ b/askbot/skins/default/templates/email/footer.html
@@ -0,0 +1 @@
+<p>{% trans %}Sincerely,<br>{{ site_name }} Administrator{% endtrans %}</p>
diff --git a/askbot/skins/default/templates/email/re_welcome_lamson_on.html b/askbot/skins/default/templates/email/re_welcome_lamson_on.html
new file mode 100644
index 00000000..412fede8
--- /dev/null
+++ b/askbot/skins/default/templates/email/re_welcome_lamson_on.html
@@ -0,0 +1,7 @@
+<p style="font-size:16px;font-weight:bold;">
+ {% trans %}Great, you are ready to use {{ site_name }}!{% endtrans %}
+</p>
+<p>{% trans %}You can post questions by emailing them at {{ ask_address }}.{% endtrans %}</p>
+<p>{% trans %}When you receive update notifications, you will be able to respond to them, also by email.{% endtrans %}</p>
+<p>{% trans %}Of course, you can always visit the {{ site_name }} at <a href="{{ site_url }}">{{ site_url }}</a>{% endtrans %}</p>
+{% include "email/footer.html" %}
diff --git a/askbot/skins/default/templates/reply_by_email_error.html b/askbot/skins/default/templates/email/reply_by_email_error.html
index 53648184..53648184 100644
--- a/askbot/skins/default/templates/reply_by_email_error.html
+++ b/askbot/skins/default/templates/email/reply_by_email_error.html
diff --git a/askbot/skins/default/templates/email/welcome_lamson_on.html b/askbot/skins/default/templates/email/welcome_lamson_on.html
new file mode 100644
index 00000000..8bed898c
--- /dev/null
+++ b/askbot/skins/default/templates/email/welcome_lamson_on.html
@@ -0,0 +1,11 @@
+{# site_name - is short name of the site, email_code - address portion
+of the reply email used for this message, we scan to the last appearance
+of the email code to detect the response signature that will appear under #}
+<p style="font-size:16px;font-weight:bold;">
+ {% trans %}Welcome to {{ site_name }}!{% endtrans %}
+</p>
+<p>
+ {% trans %}Please send a <em>blank reply</em> to this message. This helps us determine your email signature. Until we receive the response from you, you will not be able ask or answer questions on {{ site_name }} by email.{% endtrans %}
+</p>
+{% include "email/footer.html" %}
+<p style="color:#aaa;font-size:8px">{{ email_code }}</p>{# important #}
diff --git a/askbot/skins/default/templates/groups.html b/askbot/skins/default/templates/groups.html
index eda0c3ff..2499ac9f 100644
--- a/askbot/skins/default/templates/groups.html
+++ b/askbot/skins/default/templates/groups.html
@@ -1,18 +1,48 @@
{% import "macros.html" as macros %}
-{% extends 'one_column_body.html' %}
+{% extends 'two_column_body.html' %}
{% block title %}{% trans %}Groups{% endtrans %}{% endblock %}
{% block content %}
- <h1 class="section-title">{% trans %}Groups{% endtrans %}</h1>
- {% if can_edit %}
+ <div id="content-header">
+ <h1 class="section-title">{% trans %}Groups{% endtrans %}</h1>
+ {% if request.user.is_authenticated() %}
+ <div class="tabBar">
+ <div class="tabsC">
+ <a id="all-groups" class="first{% if tab_name=="all-groups" %} on{% endif %}"
+ title="{% trans %}All groups{% endtrans %}"
+ href="{% url groups %}?sort=all-groups"
+ ><span>{% trans %}all groups{% endtrans %}</span></a>
+ <a id="my-groups" {% if tab_name=="my-groups" %}class="on"{% endif %}
+ title="{% trans %}My groups{% endtrans %}"
+ href="{% url groups %}?sort=my-groups"
+ ><span>{% trans %}my groups{% endtrans %}</span></a>
+ </div>
+ </div>
+ {% endif %}
+ <div class="clearfix"></div>
+ </div>
+ {% if user_can_add_groups %}
<p id="group-add-tip">
{% trans %}Tip: to create a new group - please go to some user profile and add the new group there. That user will be the first member of the group{% endtrans %}
</p>
{% endif %}
- <ul id="groups-list">
+ <table id="groups-list">
{% for group in groups %}
- <li>
- {{ macros.user_group(group) }}
- </li>
+ <tr>
+ {{ macros.user_group(group, groups_membership_info[group.id]) }}
+ </tr>
{% endfor %}
- </ul>
+ </table>
+{% endblock %}
+{% block endjs %}
+ <script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script>
+ <script src='{{"/js/post.js"|media}}' type='text/javascript'></script>
+ {% if request.user.is_authenticated() %}
+ <script type="text/javascript">
+ askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}';
+ $.each($('.group-join-btn'), function(idx, elem){
+ var group_join_btn = new GroupJoinButton();
+ group_join_btn.decorate($(elem));
+ });
+ </script>
+ {% endif %}
{% endblock %}
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index 4bae1e45..6ba15373 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -9,7 +9,7 @@
{%- macro follow_toggle(follow, name, alias, id) -%}
{# follow - boolean; name - object type name; alias - e.g. users name; id - object id #}
<div
- class="follow-toggle"
+ class="follow-toggle follow-user-toggle"
id="follow-{{ name }}-{{ id }}"
>
{% if follow %}
@@ -221,22 +221,50 @@ poor design of the data or methods on data objects #}
</ul>
{%- endmacro -%}
-{%- macro user_group(group) -%}
- {% if group.group_profile.logo_url %}
- <a href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}">
- <img class="group-logo" src="{{group.group_profile.logo_url}}" alt='{% trans name=group.name|escape %}logo for user group "{{name}}"{% endtrans %}' />
- </a>
- {% endif %}
- <div class="group-name">
- <a
+{%- macro user_group(group, membership_info) -%}
+ <td>
+ <a class="group-name"
href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}"
>{{ group.name|escape }}</a>
- </div>
- <!--div id="group-{{group.id}}-description">
+ </td>
+ <td>
+ <span class="group-description">
{% if group.tag_wiki %}
- {{ group.tag_wiki.html }}
+ {{ group.tag_wiki.summary }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if membership_info %}
+ {{ group_join_button(
+ group_id = group.id,
+ can_join = membership_info['can_join'],
+ is_member = membership_info['is_member']
+ )
+ }}
+ {% endif %}
+ </td>
+{%- endmacro -%}
+
+{%- macro group_join_button(group_id = None, can_join = False, is_member = False) -%}
+ {% if can_join or is_member %}
+ <button
+ class="group-join-btn follow-toggle {% if is_member %}on on-state{% endif %}"
+ data-group-id="{{group_id}}"
+ data-off-prompt-text="{% trans %}Leave this group{% endtrans %}"
+ data-on-prompt-text="{% trans %}Join this group{% endtrans %}"
+ data-on-state-text="{% trans %}You are a member{% endtrans %}"
+ data-off-state-text="{% trans %}Join this group{% endtrans %}"
+ >
+ {% if is_member %}
+ {% trans %}You are a member{% endtrans %}
+ {% else %}
+ {% if can_join %}
+ {% trans %}Join this group{% endtrans %}
+ {% endif %}
+ {% endif %}
+ </button>
{% endif %}
- </div-->
{%- endmacro -%}
{# todo: remove the extra content argument to make its usage more explicit #}
diff --git a/askbot/skins/default/templates/main_page/tab_bar.html b/askbot/skins/default/templates/main_page/tab_bar.html
index 8b666155..17ab810e 100644
--- a/askbot/skins/default/templates/main_page/tab_bar.html
+++ b/askbot/skins/default/templates/main_page/tab_bar.html
@@ -3,9 +3,9 @@
{% cache 0 "scope_sort_tabs" search_tags request.user author_name scope sort query context.page language_code %}
<a class="rss"
{% if feed_url %}
- href="{{settings.APP_URL}}{{feed_url}}"
+ href="{{feed_url}}"
{% else %}
- href="{{settings.APP_URL}}/feeds/rss/"
+ href="/feeds/rss/"
{% endif %}
title="{% trans %}subscribe to the questions feed{% endtrans %}"
>{% trans %}RSS{% endtrans %}
diff --git a/askbot/skins/default/templates/meta/fonts.html b/askbot/skins/default/templates/meta/fonts.html
new file mode 100644
index 00000000..f55d567c
--- /dev/null
+++ b/askbot/skins/default/templates/meta/fonts.html
@@ -0,0 +1,20 @@
+<style type="text/css">
+@font-face {
+ font-family: 'Yanone Kaffeesatz';
+ font-style: normal;
+ font-weight: 400;
+ src: url('{{"/images/YanoneKaffeesatz-Regular.ttf"|media}}');
+}
+@font-face {
+ font-family: 'Yanone Kaffeesatz';
+ font-style: normal;
+ font-weight: 700;
+ src: url('{{"/images/YanoneKaffeesatz-Bold.ttf"|media}}');
+}
+@font-face {
+ font-family: 'Yanone Kaffeesatz';
+ font-style: normal;
+ font-weight: 300;
+ src: url('{{"/images/YanoneKaffeesatz-Light.ttf"|media}}');
+}
+</style>
diff --git a/askbot/skins/default/templates/meta/html_head_stylesheets.html b/askbot/skins/default/templates/meta/html_head_stylesheets.html
index 14f3c106..0d2ba463 100644
--- a/askbot/skins/default/templates/meta/html_head_stylesheets.html
+++ b/askbot/skins/default/templates/meta/html_head_stylesheets.html
@@ -4,7 +4,11 @@
<link href="{{"/style/style.less"|media }}" rel="stylesheet/less" type="text/css" />
<script type="text/javascript" src="{{"/js/less.min.js"|media}}"></script>
{% endif %}
-<link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700' rel='stylesheet' type='text/css'>
+{% if settings.USE_LOCAL_FONTS %}
+ {% include "meta/fonts.html" %}
+{% else %}
+ <link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700' rel='stylesheet' type='text/css'>
+{% endif %}
{{ skin.get_extra_css_link() }}
{% if settings.USE_CUSTOM_CSS %}
<link
diff --git a/askbot/skins/default/templates/question/question_card.html b/askbot/skins/default/templates/question/question_card.html
index 08f7ccee..dd52ea0f 100644
--- a/askbot/skins/default/templates/question/question_card.html
+++ b/askbot/skins/default/templates/question/question_card.html
@@ -29,4 +29,4 @@
</div>
</div>
-<div class="clean"></div>
+
diff --git a/askbot/skins/default/templates/question/subscribe_by_email_prompt.html b/askbot/skins/default/templates/question/subscribe_by_email_prompt.html
index a9158143..6a77601c 100644
--- a/askbot/skins/default/templates/question/subscribe_by_email_prompt.html
+++ b/askbot/skins/default/templates/question/subscribe_by_email_prompt.html
@@ -8,7 +8,6 @@
{% else %}
<p>
{{ answer.email_notify }}
- <label>{% trans %}once you sign in you will be able to subscribe for any updates here{% endtrans %}</label>
<label>{% trans %}<span class='strong'>Here</span> (once you log in) you will be able to sign up for the periodic email updates about this question.{% endtrans %}</label>
</p>
{% endif %}
diff --git a/askbot/skins/default/templates/user_profile/user.html b/askbot/skins/default/templates/user_profile/user.html
index 15e0622a..bb293b9b 100644
--- a/askbot/skins/default/templates/user_profile/user.html
+++ b/askbot/skins/default/templates/user_profile/user.html
@@ -30,6 +30,8 @@
<script type='text/javascript' src='{{"/js/jquery.form.js"|media}}'></script>
{% endif %}
<script type="text/javascript" src='{{"/js/user.js"|media}}'></script>
+ <script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script>
+ <script type="text/javascript" src='{{"/js/post.js"|media}}'></script>
{% block userjs %}
{% endblock %}
{% endblock %}
diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html
index 43b7f4fa..b125589c 100644
--- a/askbot/skins/default/templates/user_profile/user_stats.html
+++ b/askbot/skins/default/templates/user_profile/user_stats.html
@@ -12,15 +12,13 @@
username = view_user.username
%}{{username}}'s groups{% endtrans %}
</h2>
- <ul id="groups-list">
- {% if user_groups %}
+ <table id="groups-list">
{% for group in user_groups %}
- <li>
- {{ macros.user_group(group) }}
- </li>
+ <tr>
+ {{ macros.user_group(group, groups_membership_info[group.id]) }}
+ </tr>
{% endfor %}
- {% endif %}
- </ul>
+ </table>
<div class="clearfix"></div>
<a id="add-group">{% trans %}add group{% endtrans %}</a>
</div>
@@ -156,8 +154,13 @@
{% block endjs %}
{{ super() }}
<script type="text/javascript">
+ askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}';
$(document).ready(function(){
setup_badge_details_toggle();
+ $.each($('.group-join-btn'), function(idx, elem){
+ var group_join_btn = new GroupJoinButton();
+ group_join_btn.decorate($(elem));
+ });
});
</script>
{% endblock %}
diff --git a/askbot/skins/default/templates/users.html b/askbot/skins/default/templates/users.html
index b7a16be1..a6346fc3 100644
--- a/askbot/skins/default/templates/users.html
+++ b/askbot/skins/default/templates/users.html
@@ -95,45 +95,45 @@
{% else %}
var codeFriendlyMarkdown = false;
{% endif %}
- $().ready(function(){
- {% if group and request.user.is_authenticated() %}
- var group_join_btn = new GroupJoinButton({{ group.id }});
+ {% if group and request.user.is_authenticated() %}
+ $().ready(function(){
+ var group_join_btn = new GroupJoinButton();
group_join_btn.decorate($('.group-join-btn'));
- {% endif %}
- //setup WMD editor
- if (askbot['data']['userIsAdminOrMod'] === true){
- //todo: this is kind of Attacklab.init ... should not be here
- Attacklab.wmd = function(){
- Attacklab.loadEnv = function(){
- var mergeEnv = function(env){
- if(!env){
- return;
- }
-
- for(var key in env){
- Attacklab.wmd_env[key] = env[key];
- }
+ //setup WMD editor
+ if (askbot['data']['userIsAdminOrMod'] === true){
+ //todo: this is kind of Attacklab.init ... should not be here
+ Attacklab.wmd = function(){
+ Attacklab.loadEnv = function(){
+ var mergeEnv = function(env){
+ if(!env){
+ return;
+ }
+
+ for(var key in env){
+ Attacklab.wmd_env[key] = env[key];
+ }
+ };
+
+ mergeEnv(Attacklab.wmd_defaults);
+ mergeEnv(Attacklab.account_options);
+ mergeEnv(top["wmd_options"]);
+ Attacklab.full = true;
+
+ var defaultButtons = "bold italic link blockquote code image ol ul heading hr";
+ Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons;
};
-
- mergeEnv(Attacklab.wmd_defaults);
- mergeEnv(Attacklab.account_options);
- mergeEnv(top["wmd_options"]);
- Attacklab.full = true;
-
- var defaultButtons = "bold italic link blockquote code image ol ul heading hr";
- Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons;
+ Attacklab.loadEnv();
};
- Attacklab.loadEnv();
- };
- Attacklab.wmd();
- Attacklab.wmdBase();
- var group_editor = new UserGroupProfileEditor();
- group_editor.decorate($('#group-wiki-{{group.id}}'));
- }
- Hilite.exact = false;
- Hilite.elementid = "main-body";
- Hilite.debug_referrer = location.href;
- });
+ Attacklab.wmd();
+ Attacklab.wmdBase();
+ var group_editor = new UserGroupProfileEditor();
+ group_editor.decorate($('#group-wiki-{{group.id}}'));
+ }
+ Hilite.exact = false;
+ Hilite.elementid = "main-body";
+ Hilite.debug_referrer = location.href;
+ });
+ {% endif %}
</script>
{% endblock %}
<!-- end users.html -->
diff --git a/askbot/skins/default/templates/widgets/group_info.html b/askbot/skins/default/templates/widgets/group_info.html
index 601930af..5d3a4c7f 100644
--- a/askbot/skins/default/templates/widgets/group_info.html
+++ b/askbot/skins/default/templates/widgets/group_info.html
@@ -1,3 +1,4 @@
+{% import "macros.html" as macros %}
<div id="group-wiki-{{group.id}}" class="box group-wiki">
<h2>{% trans %}Group info{% endtrans %}</h2>
<img class="group-logo"
@@ -13,23 +14,12 @@
{% endif %}
</div>
<div class="clearfix"></div>
- {% if user_can_join_group or user_is_group_member %}
- <button
- class="group-join-btn follow-toggle {% if user_is_group_member %}on on-state{% endif %}"
- data-off-prompt-text="{% trans %}Leave this group{% endtrans %}"
- data-on-prompt-text="{% trans %}Join this group{% endtrans %}"
- data-on-state-text="{% trans %}You are a member{% endtrans %}"
- data-off-state-text="{% trans %}Join this group{% endtrans %}"
- >
- {% if user_is_group_member %}
- {% trans %}You are a member{% endtrans %}
- {% else %}
- {% if user_can_join_group %}
- {% trans %}Join this group{% endtrans %}
- {% endif %}
- {% endif %}
- </button>
- {% endif %}
+ {{ macros.group_join_button(
+ group_id = group.id,
+ can_join = user_can_join_group,
+ is_member = user_is_group_member
+ )
+ }}
{% if request.user.is_authenticated() and request.user.is_administrator() %}
<div class="controls">
<a class="edit-description"
diff --git a/askbot/skins/utils.py b/askbot/skins/utils.py
index 9f67d5c9..0c0dba9c 100644
--- a/askbot/skins/utils.py
+++ b/askbot/skins/utils.py
@@ -3,7 +3,7 @@
the lookup resolution process for templates and media works as follows:
* look up item in selected skin
* if not found look in 'default'
-* raise an exception
+* raise an exception
"""
import os
import logging
@@ -56,7 +56,7 @@ def get_available_skins(selected=None):
#re-insert default as a last item
skins['default'] = default_dir
- skins['common'] = common_dir
+ skins['common'] = common_dir
return skins
@@ -71,7 +71,7 @@ def get_path_to_skin(skin):
return skin_dirs.get(skin, None)
def get_skin_choices():
- """returns a tuple for use as a set of
+ """returns a tuple for use as a set of
choices in the form"""
available_skins = get_available_skins().keys()
available_skins.remove('common')
@@ -88,7 +88,7 @@ def resolve_skin_for_media(media=None, preferred_skin = None):
def get_media_url(url, ignore_missing = False):
"""returns url prefixed with the skin name
- of the first skin that contains the file
+ of the first skin that contains the file
directories are searched in this order:
askbot_settings.ASKBOT_DEFAULT_SKIN, then 'default', then 'commmon'
if file is not found - returns None
@@ -158,7 +158,7 @@ def get_media_url(url, ignore_missing = False):
url = django_settings.STATIC_URL + use_skin + '/media/' + url
url = os.path.normpath(url).replace('\\', '/')
-
+
if resource_revision:
url += '?v=%d' % resource_revision
@@ -176,7 +176,7 @@ def update_media_revision(skin = None):
if skin in get_skin_choices():
skin_path = get_path_to_skin(skin)
else:
- raise MediaNotFound('Skin %s not found' % skin)
+ raise MediaNotFound('Skin %s not found' % skin)
else:
skin = 'default'
skin_path = get_path_to_skin(askbot_settings.ASKBOT_DEFAULT_SKIN)
@@ -195,6 +195,5 @@ def update_media_revision(skin = None):
if current_hash != askbot_settings.MEDIA_RESOURCE_REVISION_HASH:
askbot_settings.update('MEDIA_RESOURCE_REVISION', resource_revision + 1)
- askbot_settings.update('MEDIA_RESOURCE_REVISION_HASH', current_hash)
+ askbot_settings.update('MEDIA_RESOURCE_REVISION_HASH', current_hash)
logging.debug('MEDIA_RESOURCE_REVISION changed')
- askbot_settings.MEDIA_RESOURCE_REVISION
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 9678fdf1..a5b0c940 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -11,7 +11,8 @@ import sys
import os
import re
import askbot
-from django.db import transaction
+import south
+from django.db import transaction, connection
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from askbot.utils.loading import load_module
@@ -166,7 +167,7 @@ def try_import(module_name, pypi_package_name, short_message = False):
try:
load_module(module_name)
except ImportError, error:
- message = 'Error: ' + unicode(error)
+ message = 'Error: ' + unicode(error)
message += '\n\nPlease run: >pip install %s' % pypi_package_name
if short_message == False:
message += '\n\nTo install all the dependencies at once, type:'
@@ -306,10 +307,10 @@ class SettingsTester(object):
)
if len(self.messages) != 0:
raise AskbotConfigError(
- '\n\nTime to do some maintenance of your settings.py:\n\n* ' +
+ '\n\nTime to do some maintenance of your settings.py:\n\n* ' +
'\n\n* '.join(self.messages)
)
-
+
def test_staticfiles():
"""tests configuration of the staticfiles app"""
errors = list()
@@ -350,7 +351,7 @@ def test_staticfiles():
if static_url is None or str(static_url).strip() == '':
errors.append(
'Add STATIC_URL setting to your settings.py file. '
- 'The setting must be a url at which static files '
+ 'The setting must be a url at which static files '
'are accessible.'
)
url = urlparse(static_url).path
@@ -407,7 +408,7 @@ def test_staticfiles():
' python manage.py collectstatic\n'
)
-
+
print_errors(errors)
if django_settings.STATICFILES_STORAGE == \
'django.contrib.staticfiles.storage.StaticFilesStorage':
@@ -479,8 +480,6 @@ def test_avatar():
'-e git+git://github.com/ericflo/django-avatar.git#egg=avatar',
short_message = True
)
-
-
def run_startup_tests():
"""function that runs
diff --git a/askbot/tests/reply_by_email_tests.py b/askbot/tests/reply_by_email_tests.py
index a1272b0d..e46d6b3d 100644
--- a/askbot/tests/reply_by_email_tests.py
+++ b/askbot/tests/reply_by_email_tests.py
@@ -1,6 +1,6 @@
from django.utils.translation import ugettext as _
from askbot.models import ReplyAddress
-from askbot.lamson_handlers import PROCESS
+from askbot.lamson_handlers import PROCESS, get_parts
from askbot import const
@@ -21,7 +21,7 @@ class MockPart(object):
self.body = body
self.content_encoding = {'Content-Type':('text/plain',)}
-class MockMessage(object):
+class MockMessage(dict):
def __init__(self, body, from_email):
self._body = body
@@ -61,7 +61,10 @@ class EmailProcessingTests(AskbotTestCase):
self.comment = self.post_comment(user = self.u2, parent_post = self.answer)
def test_process_correct_answer_comment(self):
- addr = ReplyAddress.objects.create_new( self.answer, self.u1).address
+ addr = ReplyAddress.objects.create_new(
+ post = self.answer,
+ user = self.u1
+ ).address
reply_separator = const.REPLY_SEPARATOR_TEMPLATE % {
'user_action': 'john did something',
'instruction': 'reply above this line'
@@ -71,7 +74,8 @@ class EmailProcessingTests(AskbotTestCase):
"wrote something \n\n%s\nlorem ipsum " % (reply_separator),
"user1@domain.com"
)
- PROCESS(msg, addr, '')
+ msg['Subject'] = 'test subject'
+ PROCESS(msg, address = addr)
self.assertEquals(self.answer.comments.count(), 2)
self.assertEquals(self.answer.comments.all().order_by('-pk')[0].text.strip(), "This is a test reply")
@@ -101,20 +105,29 @@ class ReplyAddressModelTests(AskbotTestCase):
def test_address_creation(self):
self.assertEquals(ReplyAddress.objects.all().count(), 0)
- result = ReplyAddress.objects.create_new( self.answer, self.u1)
+ result = ReplyAddress.objects.create_new(
+ post = self.answer,
+ user = self.u1
+ )
self.assertTrue(len(result.address) >= 12 and len(result.address) <= 25)
self.assertEquals(ReplyAddress.objects.all().count(), 1)
def test_create_answer_reply(self):
- result = ReplyAddress.objects.create_new( self.answer, self.u1)
+ result = ReplyAddress.objects.create_new(
+ post = self.answer,
+ user = self.u1
+ )
post = result.create_reply(TEST_EMAIL_PARTS)
self.assertEquals(post.post_type, "comment")
self.assertEquals(post.text, TEST_CONTENT)
self.assertEquals(self.answer.comments.count(), 2)
def test_create_comment_reply(self):
- result = ReplyAddress.objects.create_new( self.comment, self.u1)
+ result = ReplyAddress.objects.create_new(
+ post = self.comment,
+ user = self.u1
+ )
post = result.create_reply(TEST_EMAIL_PARTS)
self.assertEquals(post.post_type, "comment")
self.assertEquals(post.text, TEST_CONTENT)
@@ -122,13 +135,19 @@ class ReplyAddressModelTests(AskbotTestCase):
def test_create_question_comment_reply(self):
- result = ReplyAddress.objects.create_new( self.question, self.u3)
+ result = ReplyAddress.objects.create_new(
+ post = self.question,
+ user = self.u3
+ )
post = result.create_reply(TEST_EMAIL_PARTS)
self.assertEquals(post.post_type, "comment")
self.assertEquals(post.text, TEST_CONTENT)
def test_create_question_answer_reply(self):
- result = ReplyAddress.objects.create_new( self.question, self.u3)
+ result = ReplyAddress.objects.create_new(
+ post = self.question,
+ user = self.u3
+ )
post = result.create_reply(TEST_LONG_EMAIL_PARTS)
self.assertEquals(post.post_type, "answer")
self.assertEquals(post.text, TEST_LONG_CONTENT)
diff --git a/askbot/utils/mail.py b/askbot/utils/mail.py
index aa4df320..4f653e6b 100644
--- a/askbot/utils/mail.py
+++ b/askbot/utils/mail.py
@@ -144,21 +144,29 @@ def mail_moderators(
if raise_on_failure == True:
raise exceptions.EmailNotSent(unicode(error))
-ASK_BY_EMAIL_USAGE = _(
-"""<p>To ask by email, please:</p>
-<ul>
- <li>Format the subject line as: [Tag1; Tag2] Question title</li>
- <li>Type details of your question into the email body</li>
-</ul>
-<p>Note that tags may consist of more than one word, and tags
-may be separated by a semicolon or a comma</p>
-"""
+INSTRUCTIONS_PREAMBLE = _('<p>To ask by email, please:</p>')
+QUESTION_TITLE_INSTRUCTION = _(
+ '<li>Type title in the subject line</li>'
+)
+QUESTION_DETAILS_INSTRUCTION = _(
+ '<li>Type details of your question into the email body</li>'
+)
+OPTIONAL_TAGS_INSTRUCTION = _(
+"""<li>The beginning of the subject line can contain tags,
+<em>enclosed in the square brackets</em> like so: [Tag1; Tag2]</li>"""
+)
+REQUIRED_TAGS_INSTRUCTION = _(
+"""<li>In the beginning of the subject add at least one tag
+<em>enclosed in the brackets</em> like so: [Tag1; Tag2].</li>"""
+)
+TAGS_INSTRUCTION_FOOTNOTE = _(
+"""<p>Note that a tag may consist of more than one word, to separate
+the tags, use a semicolon or a comma, for example, [One tag; Other tag]</p>"""
)
def bounce_email(email, subject, reason = None, body_text = None):
"""sends a bounce email at address ``email``, with the subject
line ``subject``, accepts several reasons for the bounce:
-
* ``'problem_posting'``, ``unknown_user`` and ``permission_denied``
* ``body_text`` in an optional parameter that allows to append
extra text to the message
@@ -168,7 +176,28 @@ def bounce_email(email, subject, reason = None, body_text = None):
'<p>Sorry, there was an error posting your question '
'please contact the %(site)s administrator</p>'
) % {'site': askbot_settings.APP_SHORT_NAME}
- error_message = string_concat(error_message, ASK_BY_EMAIL_USAGE)
+
+ if askbot_settings.TAGS_ARE_REQUIRED:
+ error_message = string_concat(
+ INSTRUCTIONS_PREAMBLE,
+ '<ul>',
+ QUESTION_TITLE_INSTRUCTION,
+ REQUIRED_TAGS_INSTRUCTION,
+ QUESTION_DETAILS_INSTRUCTION,
+ '</ul>',
+ TAGS_INSTRUCTION_FOOTNOTE
+ )
+ else:
+ error_message = string_concat(
+ INSTRUCTIONS_PREAMBLE,
+ '<ul>',
+ QUESTION_TITLE_INSTRUCTION,
+ QUESTION_DETAILS_INSTRUCTION,
+ OPTIONAL_TAGS_INSTRUCTION,
+ '</ul>',
+ TAGS_INSTRUCTION_FOOTNOTE
+ )
+
elif reason == 'unknown_user':
error_message = _(
'<p>Sorry, in order to post questions on %(site)s '
@@ -271,6 +300,10 @@ def process_emailed_question(from_address, subject, parts, tags = None):
user = User.objects.get(
email__iexact = email_address
)
+
+ if user.email_isvalid == False:
+ raise PermissionDenied('Lacking email signature')
+
tagnames = form.cleaned_data['tagnames']
title = form.cleaned_data['title']
body_text = form.cleaned_data['body_text']
@@ -279,10 +312,15 @@ def process_emailed_question(from_address, subject, parts, tags = None):
if tags:
tagnames += ' ' + ' '.join(tags)
+ stripped_body_text = user.strip_email_signature(body_text)
+ if stripped_body_text == body_text and user.email_signature:
+ #todo: send an email asking to update the signature
+ raise ValueError('email signature changed')
+
user.post_question(
title = title,
tags = tagnames.strip(),
- body_text = body_text,
+ body_text = stripped_body_text,
by_email = True,
email_address = from_address
)
diff --git a/askbot/views/meta.py b/askbot/views/meta.py
index 3e5a0b35..5f4c70ac 100644
--- a/askbot/views/meta.py
+++ b/askbot/views/meta.py
@@ -87,7 +87,7 @@ def feedback(request):
data['email'] = form.cleaned_data.get('email',None)
data['message'] = form.cleaned_data['message']
data['name'] = form.cleaned_data.get('name',None)
- template = get_template('feedback_email.txt', request)
+ template = get_template('email/feedback_email.txt', request)
message = template.render(RequestContext(request, data))
mail_moderators(_('Q&A forum feedback'), message)
msg = _('Thanks for the feedback!')
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index 3259cddd..8f19a802 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -68,6 +68,7 @@ def questions(request, **kwargs):
List of Questions, Tagged questions, and Unanswered questions.
matching search query or user selection
"""
+ #before = datetime.datetime.now()
if request.method != 'GET':
return HttpResponseNotAllowed(['GET'])
@@ -311,7 +312,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
"""
#process url parameters
#todo: fix inheritance of sort method from questions
- before = datetime.datetime.now()
+ #before = datetime.datetime.now()
default_sort_method = request.session.get('questions_sort_method', 'votes')
form = ShowQuestionForm(request.GET, default_sort_method)
form.full_clean()#always valid
@@ -551,9 +552,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
'show_comment_position': show_comment_position,
}
- result = render_into_skin('question.html', data, request)
- #print datetime.datetime.now() - before
- return result
+ return render_into_skin('question.html', data, request)
def revisions(request, id, post_type = None):
assert post_type in ('question', 'answer')
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 10f643f5..a5860b14 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -13,7 +13,7 @@ import datetime
import logging
import operator
-from django.db.models import Count, Q
+from django.db.models import Count
from django.conf import settings as django_settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, InvalidPage
@@ -421,6 +421,13 @@ def user_stats(request, user, context):
badges = badges_dict.items()
badges.sort(key=operator.itemgetter(1), reverse=True)
+ user_groups = models.Tag.group_tags.get_for_user(user = user)
+
+ if request.user == user:
+ groups_membership_info = user.get_groups_membership_info(user_groups)
+ else:
+ groups_membership_info = collections.defaultdict()
+
data = {
'active_tab':'users',
'page_class': 'user-profile-page',
@@ -442,7 +449,8 @@ def user_stats(request, user, context):
'votes_total_per_day': votes_total,
'user_tags' : user_tags,
- 'user_groups': models.Tag.group_tags.get_for_user(user = user),
+ 'user_groups': user_groups,
+ 'groups_membership_info': groups_membership_info,
'badges': badges,
'total_badges' : len(badges),
}
@@ -688,7 +696,7 @@ def user_responses(request, user, context):
#6) sort responses by time
filtered_response_list.sort(lambda x,y: cmp(y['timestamp'], x['timestamp']))
- reject_reasons = models.PostFlagReason.objects.all().order_by('title');
+ reject_reasons = models.PostFlagReason.objects.all().order_by('title')
data = {
'active_tab':'users',
'page_class': 'user-profile-page',
@@ -914,12 +922,38 @@ def groups(request, id = None, slug = None):
"""
if askbot_settings.GROUPS_ENABLED == False:
raise Http404
- groups = models.Tag.group_tags.get_all()
- can_edit = request.user.is_authenticated() and \
+
+ #6 lines of input cleaning code
+ if request.user.is_authenticated():
+ scope = request.GET.get('sort', 'all-groups')
+ if scope not in ('all-groups', 'my-groups'):
+ scope = 'all-groups'
+ else:
+ scope = 'all-groups'
+
+ if scope == 'all-groups':
+ groups = models.Tag.group_tags.get_all()
+ else:
+ groups = models.Tag.group_tags.get_for_user(
+ user = request.user
+ )
+
+ groups = groups.select_related('group_profile')
+
+ user_can_add_groups = request.user.is_authenticated() and \
request.user.is_administrator_or_moderator()
+
+ groups_membership_info = collections.defaultdict()
+ if request.user.is_authenticated():
+ #collect group memberhship information
+ groups_membership_info = request.user.get_groups_membership_info(groups)
+
data = {
'groups': groups,
- 'can_edit': can_edit,
- 'active_tab': 'users'
+ 'groups_membership_info': groups_membership_info,
+ 'user_can_add_groups': user_can_add_groups,
+ 'active_tab': 'groups',#todo vars active_tab and tab_name are too similar
+ 'tab_name': scope,
+ 'page_class': 'groups-page'
}
return render_into_skin('groups.html', data, request)