summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/api.py4
-rw-r--r--askbot/conf/__init__.py1
-rw-r--r--askbot/conf/access_control.py38
-rw-r--r--askbot/conf/email.py8
-rw-r--r--askbot/conf/user_settings.py15
-rw-r--r--askbot/conf/widgets.py99
-rw-r--r--askbot/context.py11
-rw-r--r--askbot/db0
-rw-r--r--askbot/deps/django_authopenid/urls.py12
-rw-r--r--askbot/deps/django_authopenid/util.py7
-rw-r--r--askbot/deps/django_authopenid/views.py414
-rw-r--r--askbot/doc/source/changelog.rst4
-rw-r--r--askbot/forms.py16
-rw-r--r--askbot/mail/__init__.py14
-rw-r--r--askbot/mail/lamson_handlers.py2
-rw-r--r--askbot/migrations/0133_apply_global_group_to_posts_and_users.py12
-rw-r--r--askbot/migrations/0135_auto__add_questionwidget__add_askwidget.py393
-rw-r--r--askbot/models/__init__.py45
-rw-r--r--askbot/models/question.py44
-rw-r--r--askbot/models/tag.py8
-rw-r--r--askbot/models/user.py15
-rw-r--r--askbot/models/widgets.py123
-rw-r--r--askbot/setup_templates/settings.py2
-rw-r--r--askbot/setup_templates/settings.py.mustache2
-rw-r--r--askbot/setup_templates/tinymce_sample_config.py26
-rw-r--r--askbot/skins/common/media/images/sprites.pngbin12545 -> 12940 bytes
-rw-r--r--askbot/skins/common/media/js/autocompleter.js28
-rw-r--r--askbot/skins/common/media/js/post.js87
-rw-r--r--askbot/skins/common/media/js/utils.js927
-rw-r--r--askbot/skins/common/templates/authopenid/complete.html8
-rw-r--r--askbot/skins/common/templates/authopenid/signin.html32
-rw-r--r--askbot/skins/common/templates/authopenid/signup_with_password.html9
-rw-r--r--askbot/skins/common/templates/authopenid/verify_email.html14
-rw-r--r--askbot/skins/default/media/bootstrap/css/bootstrap.css2
-rw-r--r--askbot/skins/default/media/images/sprites.pngbin12705 -> 12478 bytes
-rw-r--r--askbot/skins/default/media/images/sprites_source/sprites.svg160
-rw-r--r--askbot/skins/default/media/style/style.css414
-rw-r--r--askbot/skins/default/media/style/style.less130
-rw-r--r--askbot/skins/default/templates/embed/ask_by_widget.html40
-rw-r--r--askbot/skins/default/templates/embed/ask_widget_complete.html8
-rwxr-xr-xaskbot/skins/default/templates/embed/askbot_widget.css25
-rwxr-xr-xaskbot/skins/default/templates/embed/askbot_widget.js73
-rw-r--r--askbot/skins/default/templates/embed/delete_widget.html14
-rw-r--r--askbot/skins/default/templates/embed/list_widgets.html45
-rw-r--r--askbot/skins/default/templates/embed/question_widget.html (renamed from askbot/skins/default/templates/question_widget.html)7
-rw-r--r--askbot/skins/default/templates/embed/widget_form.html23
-rw-r--r--askbot/skins/default/templates/embed/widgets.html36
-rw-r--r--askbot/skins/default/templates/groups.html20
-rw-r--r--askbot/skins/default/templates/macros.html45
-rw-r--r--askbot/skins/default/templates/meta/html_head_stylesheets.html3
-rw-r--r--askbot/skins/default/templates/question.html1
-rw-r--r--askbot/skins/default/templates/question/javascript.html1
-rw-r--r--askbot/skins/default/templates/question/new_answer_form.html1
-rw-r--r--askbot/skins/default/templates/question/sidebar.html77
-rw-r--r--askbot/skins/default/templates/widget_base.html1
-rw-r--r--askbot/skins/default/templates/widgets/groups_list.html4
-rw-r--r--askbot/skins/default/templates/widgets/meta_nav.html22
-rw-r--r--askbot/skins/default/templates/widgets/user_list.html8
-rw-r--r--askbot/skins/default/templates/widgets/user_navigation.html1
-rw-r--r--askbot/skins/loaders.py12
-rw-r--r--askbot/startup_procedures.py62
-rw-r--r--askbot/tests/__init__.py1
-rw-r--r--askbot/tests/badge_tests.py2
-rw-r--r--askbot/tests/form_tests.py7
-rw-r--r--askbot/tests/page_load_tests.py21
-rw-r--r--askbot/tests/utils.py25
-rw-r--r--askbot/tests/utils_tests.py17
-rw-r--r--askbot/tests/widget_tests.py116
-rw-r--r--askbot/urls.py66
-rw-r--r--askbot/utils/forms.py55
-rw-r--r--askbot/utils/url_utils.py33
-rw-r--r--askbot/views/commands.py45
-rw-r--r--askbot/views/meta.py16
-rw-r--r--askbot/views/readers.py22
-rw-r--r--askbot/views/users.py14
-rw-r--r--askbot/views/widgets.py217
76 files changed, 3579 insertions, 733 deletions
diff --git a/askbot/api.py b/askbot/api.py
index 57d5c1aa..9f37995e 100644
--- a/askbot/api.py
+++ b/askbot/api.py
@@ -9,7 +9,7 @@ from askbot import models
from askbot import const
def get_info_on_moderation_items(user):
- """returns a dictionary with
+ """returns a dictionary with
counts of new and seen moderation items for a given user
if user is not a moderator or admin, returns None
"""
@@ -48,7 +48,7 @@ def get_admin(seed_user_id = None):
if the user is not found, or there are no moderators/admins
User.DoesNotExist will be raised
-
+
The reason this function is here and not on a manager of
the user object is because we still patch the django-auth User table
and it's probably better not to patch the manager
diff --git a/askbot/conf/__init__.py b/askbot/conf/__init__.py
index 7ccd19bd..3e80877c 100644
--- a/askbot/conf/__init__.py
+++ b/askbot/conf/__init__.py
@@ -26,7 +26,6 @@ import askbot.conf.badges
import askbot.conf.login_providers
import askbot.conf.access_control
import askbot.conf.site_modes
-import askbot.conf.widgets
#import main settings object
from askbot.conf.settings_wrapper import settings
diff --git a/askbot/conf/access_control.py b/askbot/conf/access_control.py
index cd2364b5..5da88936 100644
--- a/askbot/conf/access_control.py
+++ b/askbot/conf/access_control.py
@@ -13,9 +13,45 @@ settings.register(
livesettings.BooleanValue(
ACCESS_CONTROL,
'ASKBOT_CLOSED_FORUM_MODE',
- default = False,
+ default=False,
description=_('Allow only registered user to access the forum'),
)
)
+EMAIL_VALIDATION_CASE_CHOICES = (
+ ('nothing', _('nothing - not required')),
+ ('see-content', _('access to content')),
+ #'post-content', _('posting content'),
+)
+
+settings.register(
+ livesettings.StringValue(
+ ACCESS_CONTROL,
+ 'REQUIRE_VALID_EMAIL_FOR',
+ default='nothing',
+ choices=EMAIL_VALIDATION_CASE_CHOICES,
+ description=_(
+ 'Require valid email for'
+ )
+ )
+)
+settings.register(
+ livesettings.LongStringValue(
+ ACCESS_CONTROL,
+ 'ALLOWED_EMAILS',
+ default='',
+ description=_('Allowed email addresses'),
+ help_text=_('Please use space to separate the entries')
+ )
+)
+
+settings.register(
+ livesettings.LongStringValue(
+ ACCESS_CONTROL,
+ 'ALLOWED_EMAIL_DOMAINS',
+ default='',
+ description=_('Allowed email domain names'),
+ help_text=_('Please use space to separate the entries, do not use the @ symbol!')
+ )
+)
diff --git a/askbot/conf/email.py b/askbot/conf/email.py
index 1b81fa96..9330e638 100644
--- a/askbot/conf/email.py
+++ b/askbot/conf/email.py
@@ -66,7 +66,7 @@ settings.register(
livesettings.StringValue(
EMAIL,
'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ASK',
- default='w',
+ default='i',
choices=const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES,
description=_('Default notification frequency questions asked by the user'),
help_text=_(
@@ -80,7 +80,7 @@ settings.register(
livesettings.StringValue(
EMAIL,
'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ANS',
- default='w',
+ default='d',
choices=const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES,
description=_('Default notification frequency questions answered by the user'),
help_text=_(
@@ -94,7 +94,7 @@ settings.register(
livesettings.StringValue(
EMAIL,
'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_SEL',
- default='w',
+ default='i',
choices=const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES,
description=_('Default notification frequency questions individually \
selected by the user'),
@@ -109,7 +109,7 @@ settings.register(
livesettings.StringValue(
EMAIL,
'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_M_AND_C',
- default='w',
+ default='i',
choices=const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES,
description=_('Default notification frequency for mentions \
and comments'),
diff --git a/askbot/conf/user_settings.py b/askbot/conf/user_settings.py
index 84c16628..adbdd5ff 100644
--- a/askbot/conf/user_settings.py
+++ b/askbot/conf/user_settings.py
@@ -16,11 +16,20 @@ USER_SETTINGS = livesettings.ConfigurationGroup(
)
settings.register(
- livesettings.StringValue(
+ livesettings.LongStringValue(
USER_SETTINGS,
'NEW_USER_GREETING',
- default = '',
- description = _('On-screen greeting shown to the new users')
+ default='',
+ description=_('On-screen greeting shown to the new users')
+ )
+)
+
+settings.register(
+ livesettings.BooleanValue(
+ USER_SETTINGS,
+ 'ALLOW_ANONYMOUS_FEEDBACK',
+ default=True,
+ description=_('Allow anonymous users send feedback')
)
)
diff --git a/askbot/conf/widgets.py b/askbot/conf/widgets.py
deleted file mode 100644
index d704ea12..00000000
--- a/askbot/conf/widgets.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-Settings for embeddable widgets
-"""
-from django.utils.translation import ugettext as _
-from django.utils.html import escape
-from askbot.conf.settings_wrapper import settings
-from askbot.deps.livesettings import ConfigurationGroup
-from askbot.deps.livesettings import values
-from askbot.conf.super_groups import CONTENT_AND_UI
-
-EMBEDDABLE_WIDGETS = ConfigurationGroup(
- 'EMBEDDABLE_WIDGETS',
- _('Embeddable widgets'),
- super_group = CONTENT_AND_UI
-)
-
-#we need better capabilities for the settings here
-#
-
-settings.register(
- values.IntegerValue(
- EMBEDDABLE_WIDGETS,
- 'QUESTIONS_WIDGET_MAX_QUESTIONS',
- default = 7,
- description = _('Number of questions to show'),
- help_text = escape(
- _(
- 'To embed the widget, add the following code '
- 'to your site (and fill in correct base url, preferred tags, width and height):'
- '<iframe '
- 'src="{{base_url}}/widgets/questions?tags={{comma-separated-tags}}" '
- 'width="100%" '
- 'height="300"'
- 'scrolling="no">'
- '<p>Your browser does not support iframes.</p>'
- '</iframe>'
- )
- )
- )
-)
-settings.register(
- values.LongStringValue(
- EMBEDDABLE_WIDGETS,
- 'QUESTIONS_WIDGET_CSS',
- default = """
-body {
- overflow: hidden;
-}
-#container {
- width: 200px;
- height: 350px;
-}
-ul {
- list-style: none;
- padding: 5px;
- margin: 5px;
-}
-li {
- border-bottom: #CCC 1px solid;
- padding-bottom: 5px;
- padding-top: 5px;
-}
-li:last-child {
- border: none;
-}
-a {
- text-decoration: none;
- color: #464646;
- font-family: 'Yanone Kaffeesatz', sans-serif;
- font-size: 15px;
-}
-""",
- descripton = _('CSS for the questions widget')
- )
-)
-
-settings.register(
- values.LongStringValue(
- EMBEDDABLE_WIDGETS,
- 'QUESTIONS_WIDGET_HEADER',
- description = _('Header for the questions widget'),
- default = ''
- )
-)
-
-settings.register(
- values.LongStringValue(
- EMBEDDABLE_WIDGETS,
- 'QUESTIONS_WIDGET_FOOTER',
- description = _('Footer for the questions widget'),
- default = """
-<link
- href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700'
- rel='stylesheet'
- type='text/css'
->
-"""
- )
-)
diff --git a/askbot/context.py b/askbot/context.py
index 3422c701..402183ea 100644
--- a/askbot/context.py
+++ b/askbot/context.py
@@ -6,6 +6,7 @@ import sys
from django.conf import settings
import askbot
from askbot import api
+from askbot import models
from askbot import const
from askbot.conf import settings as askbot_settings
from askbot.skins.loaders import get_skin
@@ -46,9 +47,17 @@ def application_settings(request):
my_settings['LOGOUT_REDIRECT_URL'] = url_utils.get_logout_redirect_url()
my_settings['USE_ASKBOT_LOGIN_SYSTEM'] = 'askbot.deps.django_authopenid' \
in settings.INSTALLED_APPS
- return {
+ context = {
'settings': my_settings,
'skin': get_skin(request),
'moderation_items': api.get_info_on_moderation_items(request.user),
'noscript_url': const.DEPENDENCY_URLS['noscript'],
}
+
+ if askbot_settings.GROUPS_ENABLED:
+ context['group_list'] = models.Tag.group_tags.get_all().filter(
+ deleted=False
+ ).exclude(
+ name__startswith='_internal_')
+
+ return context
diff --git a/askbot/db b/askbot/db
deleted file mode 100644
index e69de29b..00000000
--- a/askbot/db
+++ /dev/null
diff --git a/askbot/deps/django_authopenid/urls.py b/askbot/deps/django_authopenid/urls.py
index 9b97d847..1b7d0b01 100644
--- a/askbot/deps/django_authopenid/urls.py
+++ b/askbot/deps/django_authopenid/urls.py
@@ -21,11 +21,6 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views',
),
url(r'^%s$' % _('register/'), 'register', name='user_register'),
url(
- r'^%s$' % _('verify-user-information/'),
- 'verify_user_information',
- name = 'verify_user_information'
- ),
- url(
r'^%s$' % _('signup/'),
'signup_with_password',
name='user_signup_with_password'
@@ -35,7 +30,12 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views',
#but the setting is disabled right now
#url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'send_email_key', name='send_email_key'),
#url(r'^%s%s(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$' % (_('email/'), _('verify/')), 'verifyemail', name='user_verifyemail'),
- url(r'^%s(?P<key>[\dabcdef]{32})?$' % _('recover/'), 'account_recover', name='user_account_recover'),
+ url(r'^%s$' % _('recover/'), 'account_recover', name='user_account_recover'),
+ url(
+ r'^%s$' % _('verify-email/'),
+ 'verify_email_and_register',
+ name='verify_email_and_register'
+ ),
url(
r'^delete_login_method/$',#this method is ajax only
'delete_login_method',
diff --git a/askbot/deps/django_authopenid/util.py b/askbot/deps/django_authopenid/util.py
index 28f6b2dd..9f02050d 100644
--- a/askbot/deps/django_authopenid/util.py
+++ b/askbot/deps/django_authopenid/util.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
import cgi
import urllib
+import urllib2
import functools
import re
+import random
from openid.store.interface import OpenIDStore
from openid.association import Association as OIDAssociation
from openid.extensions import sreg
@@ -412,6 +414,7 @@ def get_enabled_major_login_providers():
token = oauth.Token(data['oauth_token'], data['oauth_token_secret'])
client = oauth.Client(consumer, token=token)
url = 'https://identi.ca/api/account/verify_credentials.json'
+ content = urllib2.urlopen(url).read()
json = simplejson.loads(content)
return json['id']
if askbot_settings.IDENTICA_KEY and askbot_settings.IDENTICA_SECRET:
@@ -848,3 +851,7 @@ def ldap_check_password(username, password):
except ldap.LDAPError, e:
logging.critical(unicode(e))
return False
+
+def generate_random_key():
+ random.seed()
+ return '%032x' % random.getrandbits(128)
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 3132d7ba..ba208b85 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -84,15 +84,70 @@ 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
+def create_authenticated_user_account(
+ username=None, email=None, password=None,
+ user_identifier=None, login_provider_name=None
+):
+ """creates a user account, user association with
+ the login method and the the default email subscriptions
+ """
+
+ user = User.objects.create_user(username, email)
+ user_registered.send(None, user=user)
+
+ logging.debug('creating new openid user association for %s')
+
+ if password:
+ user.set_password(password)
+ user.save()
+ else:
+ UserAssociation(
+ openid_url = user_identifier,
+ user = user,
+ provider_name = login_provider_name,
+ last_used_timestamp = datetime.datetime.now()
+ ).save()
+
+ subscribe_form = askbot_forms.SimpleEmailSubscribeForm({'subscribe': 'y'})
+ subscribe_form.full_clean()
+ logging.debug('saving email feed settings')
+ subscribe_form.save(user)
+
+ logging.debug('logging the user in')
+ user = authenticate(method='force', user_id=user.id)
+ if user is None:
+ error_message = 'please make sure that ' + \
+ 'askbot.deps.django_authopenid.backends.AuthBackend' + \
+ 'is in your settings.AUTHENTICATION_BACKENDS'
+ raise Exception(error_message)
+
+ return user
+
+
+def cleanup_post_register_session(request):
+ """delete keys from session after registration is complete"""
+ keys = (
+ 'user_identifier',
+ 'login_provider_name',
+ 'username',
+ 'email',
+ 'password',
+ 'validation_code'
+ )
+ for key in keys:
+ if key in request.session:
+ del request.session[key]
+
+
#todo: decouple from askbot
-def login(request,user):
+def login(request, user):
from django.contrib.auth import login as _login
# get old session key
session_key = request.session.session_key
# login and get new session key
- _login(request,user)
+ _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))
@@ -282,7 +337,6 @@ def signin(request, template_name='authopenid/signin.html'):
"""
logging.debug('in signin view')
on_failure = signin_failure
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
#we need a special priority on where to redirect on successful login
#here:
@@ -333,6 +387,8 @@ def signin(request, template_name='authopenid/signin.html'):
if askbot_settings.LDAP_AUTOCREATE_USERS:
#create new user or
user = ldap_create_user(user_info).user
+ user = authenticate(method='force', user_id=user.id)
+ assert(user is not None)
login(request, user)
return HttpResponseRedirect(next_url)
else:
@@ -799,53 +855,6 @@ def finalize_generic_signin(
user_identifier=user_identifier
)
-@login_required
-@csrf.csrf_protect
-def verify_user_information(request):
- """this view collects the same information from
- user ase :func:`register`, but requires that user is
- already logged in and does not create a new user record
- or change anything in instances of :class:`UserAssociation`
- """
- register_form = forms.OpenidRegisterForm(
- initial={
- 'next': reverse('index'),
- 'username': request.user.username,
- 'email': request.user.email
- }
- )
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
-
- if request.method == 'POST':
- register_form = forms.OpenidRegisterForm(request.POST)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
- if register_form.is_valid() and email_feeds_form.is_valid():
-
- email_feeds_form.save(request.user)
-
- request.user.username = register_form.cleaned_data['username']
- request.user.email = register_form.cleaned_data['email']
- request.user.save()
-
- if askbot_settings.EMAIL_VALIDATION == True:
- logging.debug('sending email validation')
- send_new_email_key(request.user, nomessage=True)
- output = validation_email_sent(request)
- set_email_validation_message(request.user) #message set after generating view
- return output
-
- logging.debug('success, send user to main page')
- return HttpResponseRedirect(reverse('index'))
-
- logging.debug('printing authopenid/complete.html output')
- data = {
- 'openid_register_form': register_form,
- 'email_feeds_form': email_feeds_form,
- 'default_form_action': request.path #post to this view
- }
- return render_into_skin('authopenid/complete.html', data, request)
-
-
@not_authenticated
@csrf.csrf_protect
def register(request, login_provider_name=None, user_identifier=None):
@@ -868,7 +877,6 @@ def register(request, login_provider_name=None, user_identifier=None):
next_url = get_next_url(request)
user = None
- is_redirect = False
username = request.session.get('username', '')
email = request.session.get('email', '')
logging.debug('request method is %s' % request.method)
@@ -880,7 +888,6 @@ def register(request, login_provider_name=None, user_identifier=None):
'email': request.session.get('email', ''),
}
)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
if request.method == 'GET':
assert(login_provider_name is not None)
@@ -903,14 +910,9 @@ def register(request, login_provider_name=None, user_identifier=None):
logging.debug('trying to create new account associated with openid')
register_form = forms.OpenidRegisterForm(request.POST)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
if not register_form.is_valid():
logging.debug('OpenidRegisterForm is INVALID')
- elif not email_feeds_form.is_valid():
- logging.debug('SimpleEmailSubscribeForm is INVALID')
else:
- logging.debug('OpenidRegisterForm and SimpleEmailSubscribeForm are valid')
- is_redirect = True
username = register_form.cleaned_data['username']
email = register_form.cleaned_data['email']
@@ -922,60 +924,30 @@ def register(request, login_provider_name=None, user_identifier=None):
user_info['email'] = email
user = ldap_create_user(user_info).user
del request.session['ldap_user_info']
- else:
- user = User()
- user.username = username
- user.email = email
- #todo - maybe hide these names per some option
- #user.first_name = request.session.get('first_name', '')
- #user.last_name = request.session.get('last_name', '')
- user.save()
-
- user_registered.send(None, user = user)
-
- logging.debug('creating new openid user association for %s')
-
- UserAssociation(
- openid_url = user_identifier,
- user = user,
- provider_name = login_provider_name,
- last_used_timestamp = datetime.datetime.now()
- ).save()
-
- del request.session['user_identifier']
- del request.session['login_provider_name']
-
- logging.debug('logging the user in')
-
- user = authenticate(method = 'force', user_id = user.id)
- if user is None:
- error_message = 'please make sure that ' + \
- 'askbot.deps.django_authopenid.backends.AuthBackend' + \
- 'is in your settings.AUTHENTICATION_BACKENDS'
- raise Exception(error_message)
+ login(request, user)
+ cleanup_post_register_session(request)
+ return HttpResponseRedirect(next_url)
- login(request, user)
+ elif askbot_settings.REQUIRE_VALID_EMAIL_FOR == 'nothing':
- logging.debug('saving email feed settings')
- email_feeds_form.save(user)
-
- #check if we need to post a question that was added anonymously
- #this needs to be a function call becase this is also done
- #if user just logged in and did not need to create the new account
-
- if user != None:
- if askbot_settings.EMAIL_VALIDATION == True:
- logging.debug('sending email validation')
- send_new_email_key(user, nomessage=True)
- output = validation_email_sent(request)
- set_email_validation_message(user) #message set after generating view
- return output
- if user.is_authenticated():
- logging.debug('success, send user to main page')
- return HttpResponseRedirect(reverse('index'))
+ user = create_authenticated_user_account(
+ username=username,
+ email=email,
+ user_identifier=user_identifier,
+ login_provider_name=login_provider_name,
+ )
+ login(request, user)
+ cleanup_post_register_session(request)
+ return HttpResponseRedirect(next_url)
else:
- logging.debug('have really strange error')
- raise Exception('openid login failed')#should not ever get here
+ request.session['username'] = username
+ request.session['email'] = email
+ key = util.generate_random_key()
+ email = request.session['email']
+ send_email_key(email, key, handler_url_name='verify_email_and_register')
+ request.session['validation_code'] = key
+ redirect_url = reverse('verify_email_and_register') + '?next=' + next_url
+ return HttpResponseRedirect(redirect_url)
providers = {
'yahoo':'<font color="purple">Yahoo!</font>',
@@ -993,7 +965,6 @@ def register(request, login_provider_name=None, user_identifier=None):
logging.debug('printing authopenid/complete.html output')
data = {
'openid_register_form': register_form,
- 'email_feeds_form': email_feeds_form,
'default_form_action': django_settings.LOGIN_URL,
'provider':mark_safe(provider_logo),
'username': username,
@@ -1011,6 +982,58 @@ def signin_failure(request, message):
return show_signin_view(request)
@not_authenticated
+@csrf.csrf_protect
+def verify_email_and_register(request):
+ """for POST request - check the validation code,
+ and if correct - create an account an log in the user
+
+ for GET - give a field to paste the activation code
+ and a button to send another validation email.
+ """
+ presented_code = request.REQUEST.get('validation_code', None)
+ if presented_code:
+ try:
+ #we get here with post if button is pushed
+ #or with "get" if emailed link is clicked
+ expected_code = request.session['validation_code']
+ assert(presented_code == expected_code)
+ #create an account!
+ username = request.session['username']
+ email = request.session['email']
+ password = request.session.get('password', None)
+ user_identifier = request.session.get('user_identifier', None)
+ login_provider_name = request.session.get('login_provider_name', None)
+ if password:
+ user = create_authenticated_user_account(
+ username=username,
+ email=email,
+ password=password,
+ )
+ elif user_identifier and login_provider_name:
+ user = create_authenticated_user_account(
+ username=username,
+ email=email,
+ user_identifier=user_identifier,
+ login_provider_name=login_provider_name,
+ )
+ else:
+ raise NotImplementedError()
+
+ login(request, user)
+ cleanup_post_register_session(request)
+ return HttpResponseRedirect(get_next_url(request))
+ except Exception, e:
+ message = _(
+ 'Sorry, registration failed. '
+ 'Please ask the site administrator for help.'
+ )
+ request.user.message_set.create(message=message)
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ data = {'page_class': 'validate-email-page'}
+ return render_into_skin('authopenid/verify_email.html', data, request)
+
+@not_authenticated
@decorators.valid_password_login_provider_required
@csrf.csrf_protect
@fix_recaptcha_remote_ip
@@ -1020,8 +1043,7 @@ def signup_with_password(request):
"""
logging.debug(get_request_info(request))
- next = get_next_url(request)
- login_form = forms.LoginForm(initial = {'next': next})
+ login_form = forms.LoginForm(initial = {'next': get_next_url(request)})
#this is safe because second decorator cleans this field
provider_name = request.REQUEST['login_provider']
@@ -1033,7 +1055,6 @@ def signup_with_password(request):
logging.debug('request method was %s' % request.method)
if request.method == 'POST':
form = RegisterForm(request.POST)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
#validation outside if to remember form values
logging.debug('validating classic register form')
@@ -1042,53 +1063,36 @@ def signup_with_password(request):
logging.debug('classic register form validated')
else:
logging.debug('classic register form is not valid')
- form2_is_valid = email_feeds_form.is_valid()
- if form2_is_valid:
- logging.debug('email feeds form validated')
- else:
- logging.debug('email feeds form is not valid')
- if form1_is_valid and form2_is_valid:
+
+ if form1_is_valid:
logging.debug('both forms are valid')
next = form.cleaned_data['next']
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data['email']
- provider_name = form.cleaned_data['login_provider']
-
- 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')
- user = authenticate(
- username = username,
- password = password,
- provider_name = provider_name,
- method = 'password'
- )
+ if askbot_settings.REQUIRE_VALID_EMAIL_FOR == 'nothing':
+ user = create_authenticated_user_account(
+ username=username,
+ email=email,
+ password=password,
+ )
+ login(request, user)
+ cleanup_post_register_session(request)
+ return HttpResponseRedirect(get_next_url(request))
+ else:
+ request.session['username'] = username
+ request.session['email'] = email
+ request.session['password'] = password
+ #todo: generate a key and save it in the session
+ key = util.generate_random_key()
+ email = request.session['email']
+ send_email_key(email, key, handler_url_name='verify_email_and_register')
+ request.session['validation_code'] = key
+ redirect_url = reverse('verify_email_and_register') + \
+ '?next=' + get_next_url(request)
+ return HttpResponseRedirect(redirect_url)
- login(request, user)
- logging.debug('new user logged in')
- email_feeds_form.save(user)
- logging.debug('email feeds form saved')
-
- # send email
- #subject = _("Welcome email subject line")
- #message_template = get_emplate(
- # 'authopenid/confirm_email.txt'
- #)
- #message_context = Context({
- # 'signup_url': askbot_settings.APP_URL + reverse('user_signin'),
- # 'username': username,
- # 'password': password,
- #})
- #message = message_template.render(message_context)
- #send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL,
- # [user.email])
- #logging.debug('new password acct created, confirmation email sent!')
- return HttpResponseRedirect(next)
else:
#todo: this can be solved with a decorator, maybe
form.initial['login_provider'] = provider_name
@@ -1097,11 +1101,10 @@ def signup_with_password(request):
#todo: here we have duplication of get_password_login_provider...
form = RegisterForm(
initial={
- 'next':next,
+ 'next': get_next_url(request),
'login_provider': provider_name
}
)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
logging.debug('printing legacy signup form')
major_login_providers = util.get_enabled_major_login_providers()
@@ -1110,7 +1113,6 @@ def signup_with_password(request):
context_data = {
'form': form,
'page_class': 'openid-signin',
- 'email_feeds_form': email_feeds_form,
'major_login_providers': major_login_providers.values(),
'minor_login_providers': minor_login_providers.values(),
'login_form': login_form
@@ -1158,89 +1160,35 @@ def xrdf(request):
return_to = "%s%s" % (url_host, reverse('user_complete_signin'))
return HttpResponse(XRDF_TEMPLATE % {'return_to': return_to})
-def find_email_validation_messages(user):
- msg_text = _('your email needs to be validated see %(details_url)s') \
- % {'details_url':reverse('faq') + '#validate'}
- return user.message_set.filter(message__exact=msg_text)
-
-def set_email_validation_message(user):
- messages = find_email_validation_messages(user)
- msg_text = _('your email needs to be validated see %(details_url)s') \
- % {'details_url':reverse('faq') + '#validate'}
- if len(messages) == 0:
- user.message_set.create(message=msg_text)
-
-def clear_email_validation_message(user):
- messages = find_email_validation_messages(user)
- messages.delete()
-
-def set_new_email(user, new_email, nomessage=False):
+def set_new_email(user, new_email):
if new_email != user.email:
user.email = new_email
user.email_isvalid = False
user.save()
- if askbot_settings.EMAIL_VALIDATION == True:
- send_new_email_key(user,nomessage=nomessage)
-def _send_email_key(user):
+def send_email_key(email, key, handler_url_name='user_account_recover'):
"""private function. sends email containing validation key
to user's email address
"""
- subject = _("Recover your %(site)s account") % {'site': askbot_settings.APP_SHORT_NAME}
+ subject = _("Recover your %(site)s account") % \
+ {'site': askbot_settings.APP_SHORT_NAME}
url = urlparse(askbot_settings.APP_URL)
data = {
'validation_link': url.scheme + '://' + url.netloc + \
- reverse(
- 'user_account_recover',
- kwargs={'key':user.email_key}
- )
+ reverse(handler_url_name) +\
+ '?validation_code=' + key
}
template = get_template('authopenid/email_validation.txt')
message = template.render(data)
- send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL, [user.email])
+ send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL, [email])
-def send_new_email_key(user,nomessage=False):
- import random
- random.seed()
- user.email_key = '%032x' % random.getrandbits(128)
+def send_user_new_email_key(user):
+ user.email_key = util.generate_random_key()
user.save()
- _send_email_key(user)
- if nomessage==False:
- set_email_validation_message(user)
-
-@login_required
-@csrf.csrf_protect
-def send_email_key(request):
- """
- url = /email/sendkey/
-
- view that is shown right after sending email key
- email sending is called internally
+ send_email_key(user.email, user.email_key)
- raises 404 if email validation is off
- if current email is valid shows 'key_not_sent' view of
- authopenid/changeemail.html template
- """
- if askbot_settings.EMAIL_VALIDATION == True:
- if request.user.email_isvalid:
- data = {
- 'email': request.user.email,
- 'action_type': 'key_not_sent',
- 'change_link': reverse('user_changeemail')
- }
- return render_into_skin(
- 'authopenid/changeemail.html',
- data,
- request
- )
- else:
- send_new_email_key(request.user)
- return validation_email_sent(request)
- else:
- raise Http404
-
-def account_recover(request, key = None):
+def account_recover(request):
"""view similar to send_email_key, except
it allows user to recover an account by entering
his/her email address
@@ -1256,7 +1204,7 @@ def account_recover(request, key = None):
form = forms.AccountRecoveryForm(request.POST)
if form.is_valid():
user = form.cleaned_data['user']
- send_new_email_key(user, nomessage = True)
+ send_user_new_email_key(user)
message = _(
'Please check your email and visit the enclosed link.'
)
@@ -1271,6 +1219,7 @@ def account_recover(request, key = None):
account_recovery_form = form
)
else:
+ key = request.GET.get('validation_code', None)
if key is None:
return HttpResponseRedirect(reverse('user_signin'))
@@ -1304,26 +1253,3 @@ def validation_email_sent(request):
'action_type': 'validate'
}
return render_into_skin('authopenid/changeemail.html', data, request)
-
-def verifyemail(request,id=None,key=None):
- """
- view that is shown when user clicks email validation link
- url = /email/verify/{{user.id}}/{{user.email_key}}/
- """
- logging.debug('')
- if askbot_settings.EMAIL_VALIDATION == True:
- user = User.objects.get(id=id)
- if user:
- if user.email_key == key:
- user.email_isvalid = True
- clear_email_validation_message(user)
- user.save()
- data = {'action_type': 'validation_complete'}
- return render_into_skin(
- 'authopenid/changeemail.html',
- data,
- request
- )
- else:
- logging.error('hmm, no user found for email validation message - foul play?')
- raise Http404
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index b312d2f7..eb45eed2 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -8,6 +8,10 @@ Development version
* Editable optional three level category selector for the tags (Evgeny)
* Tag editor adding tags as they are typed (Evgeny)
* Added optional support for unicode slugs (Evgeny)
+* Option to disable feedback form for the anonymos users (Evgeny)
+* Optional restriction to have confirmed email address to join forum (Evgeny)
+* Optional list of allowed email addresses and email domain name for the new users (Evgeny)
+* Optional support for unicode slugs (Evgeny)
* Optionally allow limiting one answer per question per person (Evgeny)
* Added management command `build_livesettings_cache` (Adolfo)
* Administrators can post under fictional user accounts without logging out (jtrain, Evgeny)
diff --git a/askbot/forms.py b/askbot/forms.py
index 7030643a..0011210d 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -357,7 +357,8 @@ class TagNamesField(forms.CharField):
def __init__(self, *args, **kwargs):
super(TagNamesField, self).__init__(*args, **kwargs)
- self.required = askbot_settings.TAGS_ARE_REQUIRED
+ self.required = kwargs.get('required',
+ askbot_settings.TAGS_ARE_REQUIRED)
self.widget = forms.TextInput(
attrs={'size': 50, 'autocomplete': 'off'}
)
@@ -784,7 +785,7 @@ class PostAsSomeoneForm(forms.Form):
'Can create new accounts.'
),
required=False,
- widget=forms.TextInput(attrs={'class': 'tipped-input'})
+ widget=forms.TextInput()
)
post_author_email = forms.CharField(
initial=_('Email address:'),
@@ -899,6 +900,7 @@ class AskWidgetForm(forms.Form, FormWithHideableFields):
'''Simple form with just the title to ask a question'''
title = TitleField()
+ text = EditorField()
ask_anonymously = forms.BooleanField(
label=_('ask anonymously'),
help_text=_(
@@ -908,12 +910,18 @@ class AskWidgetForm(forms.Form, FormWithHideableFields):
required=False,
)
- def __init__(self, *args, **kwargs):
+ def __init__(self, include_text=True, *args, **kwargs):
super(AskWidgetForm, self).__init__(*args, **kwargs)
#hide ask_anonymously field
- if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
+ if not askbot_settings.ALLOW_ASK_ANONYMOUSLY:
self.hide_field('ask_anonymously')
+ if not include_text:
+ self.hide_field('text')
+ self.fields['text'].required=False
+ else:
+ self.fields['text'].min_length = askbot_settings.MIN_QUESTION_BODY_LENGTH
+
class AskByEmailForm(forms.Form):
""":class:`~askbot.forms.AskByEmailForm`
validates question data, where question was posted
diff --git a/askbot/mail/__init__.py b/askbot/mail/__init__.py
index e653a0e3..01ffb6f0 100644
--- a/askbot/mail/__init__.py
+++ b/askbot/mail/__init__.py
@@ -325,7 +325,8 @@ def process_parts(parts, reply_code = None):
def process_emailed_question(
- from_address, subject, body_text, stored_files, tags = None
+ from_address, subject, body_text, stored_files,
+ tags=None, group_id=None
):
"""posts question received by email or bounces the message"""
#a bunch of imports here, to avoid potential circular import issues
@@ -375,11 +376,12 @@ def process_emailed_question(
user.post_question(
- title = title,
- tags = tagnames.strip(),
- body_text = stripped_body_text,
- by_email = True,
- email_address = from_address
+ title=title,
+ tags=tagnames.strip(),
+ body_text=stripped_body_text,
+ by_email=True,
+ email_address=from_address,
+ group_id=group_id
)
else:
raise ValidationError()
diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py
index 8de3bd71..6bafbc3f 100644
--- a/askbot/mail/lamson_handlers.py
+++ b/askbot/mail/lamson_handlers.py
@@ -205,7 +205,7 @@ def ASK(message, host = None, addr = None):
)
mail.process_emailed_question(
from_address, subject, body_text, stored_files,
- tags = [group_tag.name, ]
+ group_id = group_tag.id
)
except Tag.DoesNotExist:
#do nothing because this handler will match all emails
diff --git a/askbot/migrations/0133_apply_global_group_to_posts_and_users.py b/askbot/migrations/0133_apply_global_group_to_posts_and_users.py
index 733c4c73..8c14b55d 100644
--- a/askbot/migrations/0133_apply_global_group_to_posts_and_users.py
+++ b/askbot/migrations/0133_apply_global_group_to_posts_and_users.py
@@ -82,7 +82,8 @@ class Migration(DataMigration):
thread.groups.add(group)
done_count += 1
- print 'Added global group to %d threads.\n' % done_count
+ if items.count():
+ print 'Added global group to %d threads.\n' % done_count
post_types = ('question', 'answer')
posts = orm['askbot.Post'].objects.filter(post_type__in=post_types)
@@ -93,7 +94,8 @@ class Migration(DataMigration):
post.groups.add(group)
done_count += 1
- print 'Added global group to %d posts.\n' % done_count
+ if posts.count():
+ print 'Added global group to %d posts.\n' % done_count
comments = orm['askbot.Post'].objects.filter(post_type='comment')
message = 'Copying group information from answers ' +\
@@ -105,7 +107,8 @@ class Migration(DataMigration):
comment.groups.add(*parent_post_groups)
done_count += 1
- print 'Added global group to %d comments.\n' % done_count
+ if comments.count():
+ print 'Added global group to %d comments.\n' % done_count
users = orm['auth.User'].objects.all()
message = 'Adding all users to the global group'
@@ -117,7 +120,8 @@ class Migration(DataMigration):
membership.save()
done_count += 1
- print 'Added global group to %d users.' % done_count
+ if users.count():
+ print 'Added global group to %d users.' % done_count
def backwards(self, orm):
diff --git a/askbot/migrations/0135_auto__add_questionwidget__add_askwidget.py b/askbot/migrations/0135_auto__add_questionwidget__add_askwidget.py
new file mode 100644
index 00000000..19ceb582
--- /dev/null
+++ b/askbot/migrations/0135_auto__add_questionwidget__add_askwidget.py
@@ -0,0 +1,393 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'QuestionWidget'
+ db.create_table('askbot_questionwidget', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=100)),
+ ('question_number', self.gf('django.db.models.fields.PositiveIntegerField')(default=7)),
+ ('tagnames', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['askbot.Tag'], null=True, blank=True)),
+ ('search_query', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True)),
+ ('order_by', self.gf('django.db.models.fields.CharField')(default='-added_at', max_length=18)),
+ ('style', self.gf('django.db.models.fields.TextField')(default='', blank=True, null=True)),
+ ))
+ db.send_create_signal('askbot', ['QuestionWidget'])
+
+ # Adding model 'AskWidget'
+ db.create_table('askbot_askwidget', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=100)),
+ ('group', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='groups', null=True, to=orm['askbot.Tag'])),
+ ('tag', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['askbot.Tag'], null=True, blank=True)),
+ ('include_text_field', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('inner_style', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+ ('outer_style', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+ ))
+ db.send_create_signal('askbot', ['AskWidget'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'QuestionWidget'
+ db.delete_table('askbot_questionwidget')
+
+ # Deleting model 'AskWidget'
+ db.delete_table('askbot_askwidget')
+
+
+ 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.askwidget': {
+ 'Meta': {'object_name': 'AskWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'inner_style': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'outer_style': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ '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', 'db_index': 'True'})
+ },
+ 'askbot.draftanswer': {
+ 'Meta': {'object_name': 'DraftAnswer'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"})
+ },
+ 'askbot.draftquestion': {
+ 'Meta': {'object_name': 'DraftQuestion'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'})
+ },
+ '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']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Tag']"}),
+ 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'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', [], {}),
+ '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.posttogroup': {
+ 'Meta': {'unique_together': "(('post', 'tag'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']"})
+ },
+ '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.questionwidget': {
+ 'Meta': {'object_name': 'QuestionWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}),
+ 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}),
+ 'search_query': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'style': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ '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'", 'null': 'True', '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'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ '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']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}),
+ '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_signature': ('django.db.models.fields.TextField', [], {'blank': '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_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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 dc499d70..549f3277 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -1,6 +1,14 @@
from askbot import startup_procedures
startup_procedures.run()
+from django.contrib.auth.models import User
+#set up a possibility for the users to follow others
+try:
+ import followit
+ followit.register(User)
+except ImportError:
+ pass
+
import collections
import datetime
import hashlib
@@ -11,7 +19,6 @@ from django.db.models import signals as django_signals
from django.template import Context
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
-from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.db import models
@@ -45,6 +52,7 @@ from askbot.models.reply_by_email import ReplyAddress
from askbot.models import signals
from askbot.models.badges import award_badges_signal, get_badge, BadgeData
from askbot.models.repute import Award, Repute, Vote
+from askbot.models.widgets import AskWidget, QuestionWidget
from askbot import auth
from askbot.utils.decorators import auto_now_timestamp
from askbot.utils.slug import slugify
@@ -306,7 +314,7 @@ def user_get_marked_tag_names(self, reason):
attr_name = MARKED_TAG_PROPERTY_MAP[reason]
wildcard_tags = getattr(self, attr_name).split()
tag_names.extend(wildcard_tags)
-
+
return tag_names
def user_has_affinity_to_question(self, question = None, affinity_type = None):
@@ -377,7 +385,7 @@ def user_can_have_strong_url(self):
return (self.reputation >= askbot_settings.MIN_REP_TO_HAVE_STRONG_URL)
def user_can_post_by_email(self):
- """True, if reply by email is enabled
+ """True, if reply by email is enabled
and user has sufficient reputatiton"""
return askbot_settings.REPLY_BY_EMAIL and \
self.reputation > askbot_settings.MIN_REP_TO_POST_BY_EMAIL
@@ -2252,7 +2260,7 @@ def user_get_groups_membership_info(self, groups):
info[group.id]['can_join'] = group.group_profile.can_accept_user(self)
return info
-
+
def user_get_karma_summary(self):
@@ -2397,7 +2405,7 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
auth.onDownVotedCanceled(vote, post, user, timestamp)
else:
auth.onDownVoted(vote, post, user, timestamp)
-
+
post.thread.invalidate_cached_data()
if post.post_type == 'question':
@@ -2489,12 +2497,12 @@ def flag_post(user, post, timestamp=None, cancel=False, cancel_all = False, forc
content_type = post_content_type, object_id=post.id
)
for flag in all_flags:
- auth.onUnFlaggedItem(post, flag.user, timestamp=timestamp)
+ auth.onUnFlaggedItem(post, flag.user, timestamp=timestamp)
elif cancel:#todo: can't unflag?
if force == False:
user.assert_can_remove_flag_offensive(post = post)
- auth.onUnFlaggedItem(post, user, timestamp=timestamp)
+ auth.onUnFlaggedItem(post, user, timestamp=timestamp)
else:
if force == False:
@@ -2853,17 +2861,17 @@ def format_instant_notification_email(
if update_type.endswith('update'):
user_action = _('%(user)s edited a %(post_link)s.')
else:
- user_action = _('%(user)s posted a %(post_link)s')
+ user_action = _('%(user)s posted a %(post_link)s')
elif post.is_answer():
if update_type.endswith('update'):
user_action = _('%(user)s edited an %(post_link)s.')
else:
- user_action = _('%(user)s posted an %(post_link)s.')
+ user_action = _('%(user)s posted an %(post_link)s.')
elif post.is_question():
if update_type.endswith('update'):
user_action = _('%(user)s edited a %(post_link)s.')
else:
- user_action = _('%(user)s posted a %(post_link)s.')
+ user_action = _('%(user)s posted a %(post_link)s.')
else:
raise ValueError('unrecognized post type')
@@ -2891,7 +2899,7 @@ def format_instant_notification_email(
reply_separator += '</p>'
else:
reply_separator = user_action
-
+
update_data = {
'update_author_name': from_user.username,
'receiving_user_name': to_user.username,
@@ -2920,7 +2928,7 @@ def get_reply_to_addresses(user, post):
the first address - always a real email address,
the second address is not ``None`` only for "question" posts.
- When the user is notified of a new question -
+ When the user is notified of a new question -
i.e. `post` is a "quesiton", he/she
will need to choose - whether to give a question or a comment,
thus we return the second address - for the comment reply.
@@ -3006,7 +3014,7 @@ def send_instant_notifications_about_activity_in_post(
update_type = update_type,
template = get_template('instant_notification.html')
)
-
+
headers['Reply-To'] = reply_address
mail.send_mail(
subject_line = subject_line,
@@ -3027,7 +3035,7 @@ def notify_author_of_published_revision(
if revision.should_notify_author_about_publishing(was_approved):
from askbot.tasks import notify_author_of_published_revision_celery_task
notify_author_of_published_revision_celery_task.delay(revision)
-
+
#todo: move to utils
def calculate_gravatar_hash(instance, **kwargs):
@@ -3442,7 +3450,7 @@ def update_user_avatar_type_flag(instance, **kwargs):
def make_admin_if_first_user(instance, **kwargs):
"""first user automatically becomes an administrator
the function is run only once in the interpreter session
- """
+ """
import sys
#have to check sys.argv to satisfy the test runner
#which fails with the cache-based skipping
@@ -3492,13 +3500,6 @@ signals.post_updated.connect(record_post_update_activity)
signals.post_revision_published.connect(notify_author_of_published_revision)
signals.site_visited.connect(record_user_visit)
-#set up a possibility for the users to follow others
-try:
- import followit
- followit.register(User)
-except ImportError:
- pass
-
__all__ = [
'signals',
diff --git a/askbot/models/question.py b/askbot/models/question.py
index a700352b..e8ee7a7b 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -503,6 +503,50 @@ class Thread(models.Model):
else:
return self.get_answers(user).count()
+ def get_sharing_info(self, visitor=None):
+ """returns a dictionary with abbreviated thread sharing info:
+ * users - up to a certain number of users, excluding the visitor
+ * groups - up to a certain number of groups
+ * more_users_count - remaining count of shared-with users
+ * more_groups_count - remaining count of shared-with groups
+ """
+ shared_users = self.get_users_shared_with(
+ max_count=2,#"visitor" is implicit
+ exclude_user=visitor
+ )
+ groups = self.groups
+ ugroups = groups.filter(name__startswith='_internal_')
+ ggroups = groups.exclude(name__startswith='_internal_')
+
+ sharing_info = {
+ 'users': shared_users,
+ 'groups': self.get_groups_shared_with(max_count=3),
+ 'more_users_count': max(0, ugroups.count() - 3),
+ 'more_groups_count': max(0, ggroups.count() - 3)
+ }
+ return sharing_info
+
+ def get_users_shared_with(self, max_count=None, exclude_user=None):
+ """returns query set of users with whom
+ this thread is shared
+ """
+ groups = self.groups.filter(name__startswith='_internal_')
+
+ if exclude_user:
+ groups = groups.exclude(created_by__id=exclude_user.id)
+
+ user_ids = groups.values_list('created_by__id', flat=True)
+ if max_count:
+ user_ids = user_ids[:max_count]
+ return User.objects.filter(id__in=user_ids)
+
+ def get_groups_shared_with(self, max_count=None):
+ """returns query set of groups with whom thread is shared"""
+ groups = self.groups.exclude(name__startswith='_internal_')
+ if max_count:
+ groups = groups[:max_count]
+ return groups
+
def update_favorite_count(self):
self.favourite_count = FavoriteQuestion.objects.filter(thread=self).count()
self.save()
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 7f5126c8..7c5d8d97 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -190,6 +190,14 @@ class TagManager(BaseQuerySetManager):
def get_query_set(self):
return TagQuerySet(self.model)
+ def get_content_tags(self):
+ """temporary function that filters out the group tags"""
+ return self.annotate(
+ member_count = models.Count('user_memberships')
+ ).filter(
+ member_count = 0
+ )
+
def create(self, name = None, created_by = None, **kwargs):
"""Creates a new tag"""
if created_by.can_create_tags() or is_preapproved_tag_name(name):
diff --git a/askbot/models/user.py b/askbot/models/user.py
index 14c1d189..583deef2 100644
--- a/askbot/models/user.py
+++ b/askbot/models/user.py
@@ -15,6 +15,7 @@ from askbot.conf import settings as askbot_settings
from askbot.utils import functions
from askbot.models.tag import Tag
from askbot.forms import DomainNameField
+from askbot.utils.forms import email_is_allowed
class ResponseAndMentionActivityManager(models.Manager):
def get_query_set(self):
@@ -392,15 +393,11 @@ class GroupProfile(models.Model):
return True
#relying on a specific method of storage
- if self.preapproved_emails:
- email_match_re = re.compile(r'\s%s\s' % user.email)
- if email_match_re.search(self.preapproved_emails):
- return True
-
- if self.preapproved_email_domains:
- email_domain = user.email.split('@')[1]
- domain_match_re = re.compile(r'\s%s\s' % email_domain)
- return domain_match_re.search(self.preapproved_email_domains)
+ return email_is_allowed(
+ user.email,
+ allowed_emails=self.preapproved_emails,
+ allowed_email_domains=self.preapproved_email_domains
+ )
return False
diff --git a/askbot/models/widgets.py b/askbot/models/widgets.py
new file mode 100644
index 00000000..04056614
--- /dev/null
+++ b/askbot/models/widgets.py
@@ -0,0 +1,123 @@
+from django.db import models
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+from askbot.conf import settings as askbot_settings
+from askbot.models import Tag
+from askbot.models.tag import get_groups
+from askbot.forms import FormWithHideableFields, TagNamesField
+from askbot.conf import settings as askbot_settings
+from django import forms
+
+DEFAULT_INNER_STYLE = ''
+
+DEFAULT_OUTER_STYLE = ''
+
+DEFAULT_QUESTION_STYLE = '''
+@import url('http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700');
+body {
+ overflow: hidden;
+}
+
+#container {
+ width: 200px;
+ height: 350px;
+}
+ul {
+ list-style: none;
+ padding: 5px;
+ margin: 5px;
+}
+li {
+ border-bottom: #CCC 1px solid;
+ padding-bottom: 5px;
+ padding-top: 5px;
+}
+li:last-child {
+ border: none;
+}
+a {
+ text-decoration: none;
+ color: #464646;
+ font-family: 'Yanone Kaffeesatz', sans-serif;
+ font-size: 15px;
+}
+'''
+
+
+class AskWidget(models.Model):
+ '''stores widgets styles and options'''
+ title = models.CharField(max_length=100)
+ group = models.ForeignKey(Tag, null=True, blank=True,
+ related_name='groups')
+ tag = models.ForeignKey(Tag, null=True, blank=True)
+
+ include_text_field = models.BooleanField(default=False, blank=True)
+
+ inner_style = models.TextField(default=DEFAULT_INNER_STYLE, blank=True)
+ outer_style= models.TextField(default=DEFAULT_OUTER_STYLE, blank=True)
+
+ class Meta:
+ app_label = 'askbot'
+
+ def __unicode__(self):
+ return "Widget: %s" % self.title
+
+SEARCH_ORDER_BY = (
+ ('-added_at', _('date descendant')),
+ ('added_at', _('date ascendant')),
+ ('-last_activity_at', _('activity descendant')),
+ ('last_activity_at', _('activity ascendant')),
+ ('-answer_count', _('answers descendant')),
+ ('answer_count', _('answers ascendant')),
+ ('-score', _('votes descendant')),
+ ('score', _('votes ascendant')),
+ )
+
+class QuestionWidget(models.Model):
+ title = models.CharField(max_length=100)
+ question_number = models.PositiveIntegerField(default=7)
+ tagnames = models.CharField(_('tags'), max_length=50)
+ group = models.ForeignKey(Tag, null=True, blank=True)
+ search_query = models.CharField(max_length=50,
+ null=True, blank=True)
+ order_by = models.CharField(max_length=18,
+ choices=SEARCH_ORDER_BY, default='-added_at')
+ style = models.TextField(_('css for the widget'),
+ default=DEFAULT_QUESTION_STYLE, blank=True)
+
+ class Meta:
+ app_label = 'askbot'
+
+#FORMS
+class CreateAskWidgetForm(forms.ModelForm, FormWithHideableFields):
+ inner_style = forms.CharField(
+ widget=forms.Textarea,
+ required=False,
+ initial=DEFAULT_INNER_STYLE
+ )
+ outer_style = forms.CharField(
+ widget=forms.Textarea,
+ required=False,
+ initial=DEFAULT_OUTER_STYLE
+ )
+
+ group = forms.ModelChoiceField(queryset=get_groups().exclude(name__startswith='_internal'),
+ required=False)
+ tag = forms.ModelChoiceField(queryset=Tag.objects.get_content_tags(),
+ required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(CreateAskWidgetForm, self).__init__(*args, **kwargs)
+ if not askbot_settings.GROUPS_ENABLED:
+ self.hide_field('group')
+
+ class Meta:
+ model = AskWidget
+
+class CreateQuestionWidgetForm(forms.ModelForm, FormWithHideableFields):
+ tagnames = TagNamesField()
+ group = forms.ModelChoiceField(queryset=get_groups().exclude(name__startswith='_internal'),
+ required=False)
+
+ class Meta:
+ model = QuestionWidget
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 7e65c833..71f49505 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -248,7 +248,7 @@ TINYMCE_DEFAULT_CONFIG = {
'plugins': 'askbot_imageuploader,askbot_attachment',
'theme_advanced_toolbar_location' : 'top',
'theme_advanced_toolbar_align': 'left',
- 'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment'
+ 'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment',
'theme_advanced_buttons2': '',
'theme_advanced_buttons3' : '',
'theme_advanced_path': False,
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index 56b15da0..e9d9245e 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -249,7 +249,7 @@ TINYMCE_DEFAULT_CONFIG = {
'plugins': 'askbot_imageuploader,askbot_attachment',
'theme_advanced_toolbar_location' : 'top',
'theme_advanced_toolbar_align': 'left',
- 'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment'
+ 'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment',
'theme_advanced_buttons2': '',
'theme_advanced_buttons3' : '',
'theme_advanced_path': False,
diff --git a/askbot/setup_templates/tinymce_sample_config.py b/askbot/setup_templates/tinymce_sample_config.py
new file mode 100644
index 00000000..c75170b0
--- /dev/null
+++ b/askbot/setup_templates/tinymce_sample_config.py
@@ -0,0 +1,26 @@
+TINYMCE_COMPRESSOR = True
+TINYMCE_SPELLCHECKER = False
+TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'common/media/js/tinymce/')
+TINYMCE_URL = STATIC_URL + 'common/media/js/tinymce/'
+TINYMCE_DEFAULT_CONFIG = {
+ 'plugins': 'askbot_imageuploader,askbot_attachment',
+ 'theme': 'advanced',
+ 'content_css': STATIC_URL + 'default/media/style/tinymce/content.css',
+ 'force_br_newlines': True,
+ 'force_p_newlines': False,
+ 'forced_root_block': '',
+ 'mode' : 'textareas',
+ 'oninit': "function(){ tinyMCE.activeEditor.setContent(askbot['data']['editorContent'] || ''); }",
+ 'plugins': 'askbot_imageuploader,askbot_attachment',
+ 'theme_advanced_toolbar_location' : 'top',
+ 'theme_advanced_toolbar_align': 'left',
+ 'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment',
+ 'theme_advanced_buttons2': '',
+ 'theme_advanced_buttons3' : '',
+ 'theme_advanced_path': False,
+ 'theme_advanced_resizing': True,
+ 'theme_advanced_resize_horizontal': False,
+ 'theme_advanced_statusbar_location': 'bottom',
+ 'width': '723',
+ 'height': '250'
+}
diff --git a/askbot/skins/common/media/images/sprites.png b/askbot/skins/common/media/images/sprites.png
index e7244673..c372f9fa 100644
--- a/askbot/skins/common/media/images/sprites.png
+++ b/askbot/skins/common/media/images/sprites.png
Binary files differ
diff --git a/askbot/skins/common/media/js/autocompleter.js b/askbot/skins/common/media/js/autocompleter.js
index a7c54315..8121d2ea 100644
--- a/askbot/skins/common/media/js/autocompleter.js
+++ b/askbot/skins/common/media/js/autocompleter.js
@@ -10,6 +10,7 @@ var AutoCompleter = function(options) {
* Default options for autocomplete plugin
*/
var defaults = {
+ promptText: '',
autocompleteMultiple: true,
multipleSeparator: ' ',//a single character
inputClass: 'acInput',
@@ -147,6 +148,11 @@ AutoCompleter.prototype.decorate = function(element){
this._element.attr('autocomplete', 'off');
/**
+ * Set prompt text
+ */
+ this.setPrompt();
+
+ /**
* Create DOM element to hold results
*/
this._results = $('<div></div>').hide();
@@ -161,6 +167,21 @@ AutoCompleter.prototype.decorate = function(element){
this.setEventHandlers();
};
+AutoCompleter.prototype.setPrompt = function() {
+ this._element.val(this.options['promptText']);
+ this._element.addClass('prompt');
+};
+
+AutoCompleter.prototype.removePrompt = function() {
+ if (this._element.hasClass('prompt')) {
+ this._element.removeClass('prompt');
+ var val = this._element.val();
+ if (val === this.options['promptText']) {
+ this._element.val('');
+ }
+ }
+};
+
AutoCompleter.prototype.setEventHandlers = function(){
/**
* Shortcut to self
@@ -171,6 +192,9 @@ AutoCompleter.prototype.setEventHandlers = function(){
* Attach keyboard monitoring to $elem
*/
self._element.keydown(function(e) {
+
+ self.removePrompt();
+
self.lastKeyPressed_ = e.keyCode;
switch(self.lastKeyPressed_) {
@@ -204,6 +228,10 @@ AutoCompleter.prototype.setEventHandlers = function(){
break;
case 27: // escape
+ if ($.trim(self._element.val()) === '') {
+ self.setPrompt();
+ return false;
+ }
if (self.active_) {
e.preventDefault();
self.finish();
diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js
index f74a6ce1..7739e716 100644
--- a/askbot/skins/common/media/js/post.js
+++ b/askbot/skins/common/media/js/post.js
@@ -207,6 +207,65 @@ var CPValidator = function(){
/**
* @constructor
*/
+var ThreadUsersDialog = function() {
+ SimpleControl.call(this);
+ this._heading_text = 'Add heading with the setHeadingText()';
+};
+inherits(ThreadUsersDialog, SimpleControl);
+
+ThreadUsersDialog.prototype.setHeadingText = function(text) {
+ this._heading_text = text;
+};
+
+ThreadUsersDialog.prototype.showUsers = function(html) {
+ this._dialog.setContent(html);
+ this._dialog.show();
+};
+
+ThreadUsersDialog.prototype.startShowingUsers = function() {
+ var me = this;
+ var threadId = this._threadId;
+ var url = this._url;
+ $.ajax({
+ type: 'GET',
+ data: {'thread_id': threadId},
+ dataType: 'json',
+ url: url,
+ cache: false,
+ success: function(data){
+ if (data['success'] == true){
+ me.showUsers(data['html']);
+ } else {
+ showMessage(me.getElement(), data['message'], 'after');
+ }
+ }
+ });
+};
+
+ThreadUsersDialog.prototype.decorate = function(element) {
+ this._element = element;
+ ThreadUsersDialog.superClass_.decorate.call(this, element);
+ this._threadId = element.data('threadId');
+ this._url = element.data('url');
+ var dialog = new ModalDialog();
+ dialog.setRejectButtonText('');
+ dialog.setAcceptButtonText(gettext('Back to the question'));
+ dialog.setHeadingText(this._heading_text);
+ dialog.setAcceptHandler(function(){ dialog.hide(); });
+ var dialog_element = dialog.getElement();
+ $(dialog_element).find('.modal-footer').css('text-align', 'center');
+ $(document).append(dialog_element);
+ this._dialog = dialog;
+ var me = this;
+ this.setHandler(function(){
+ me.startShowingUsers();
+ });
+};
+
+
+/**
+ * @constructor
+ */
var DraftPost = function() {
WrappedElement.call(this);
};
@@ -3824,8 +3883,6 @@ $(document).ready(function() {
var proxyUserNameInput = $('#id_post_author_username');
var proxyUserEmailInput = $('#id_post_author_email');
if (proxyUserNameInput.length === 1) {
- var tip = new TippedInput();
- tip.decorate(proxyUserNameInput);
var userSelectHandler = function(data) {
proxyUserEmailInput.val(data['data'][0]);
@@ -3834,6 +3891,7 @@ $(document).ready(function() {
var fakeUserAc = new AutoCompleter({
url: '/get-users-info/',//askbot['urls']['get_users_info'],
preloadData: true,
+ promptText: gettext('User name:'),
minChars: 1,
useCache: true,
matchInside: true,
@@ -3841,11 +3899,13 @@ $(document).ready(function() {
delay: 10,
onItemSelect: userSelectHandler
});
+
fakeUserAc.decorate(proxyUserNameInput);
- }
- if (proxyUserEmailInput.length === 1) {
- var tip = new TippedInput();
- tip.decorate(proxyUserEmailInput);
+ if (proxyUserEmailInput.length === 1) {
+ var tip = new TippedInput();
+ tip.decorate(proxyUserEmailInput);
+ }
+
}
//if groups are enabled - activate share functions
var groupsInput = $('#share_group_name');
@@ -3853,6 +3913,7 @@ $(document).ready(function() {
var groupsAc = new AutoCompleter({
url: askbot['urls']['getGroupsList'],
preloadData: true,
+ promptText: gettext('Group name:'),
minChars: 1,
useCache: false,
matchInside: true,
@@ -3866,6 +3927,7 @@ $(document).ready(function() {
var usersAc = new AutoCompleter({
url: '/get-users-info/',
preloadData: true,
+ promptText: gettext('User name:'),
minChars: 1,
useCache: false,
matchInside: true,
@@ -3874,6 +3936,19 @@ $(document).ready(function() {
});
usersAc.decorate(usersInput);
}
+
+ var showSharedUsers = $('.see-related-users');
+ if (showSharedUsers.length) {
+ var usersPopup = new ThreadUsersDialog();
+ usersPopup.setHeadingText(gettext('Shared with the following users:'));
+ usersPopup.decorate(showSharedUsers);
+ }
+ var showSharedGroups = $('.see-related-groups');
+ if (showSharedGroups.length) {
+ var groupsPopup = new ThreadUsersDialog();
+ groupsPopup.setHeadingText(gettext('Shared with the following groups:'));
+ groupsPopup.decorate(showSharedGroups);
+ }
});
diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js
index 97ed7bf3..244960ce 100644
--- a/askbot/skins/common/media/js/utils.js
+++ b/askbot/skins/common/media/js/utils.js
@@ -249,20 +249,65 @@ var inherits = function(childCtor, parentCtor) {
childCtor.prototype.constructor = childCtor;
};
-/* wrapper around jQuery object */
+/** wrapper around jQuery object
+ * @constructor
+ * the top level "class" for other elements
+ * I.e. all other things must inherit this class.
+ * For an example of the inheritance pattern,
+ * please see the "TippedInput" below.
+ */
var WrappedElement = function(){
this._element = null;
this._in_document = false;
};
+/* note that we do not call inherits() here
+ * See TippedInput as an example of a subclass
+ */
+
+/**
+ * notice that we use ObjCls.prototype.someMethod = function()
+ * notation - as we use Javascript's prototypal inheritance
+ * explicitly. The point of this is to be able to eventually
+ * use the Closure Compiler
+ */
WrappedElement.prototype.setElement = function(element){
this._element = element;
};
+
+/**
+ * this function must be overridden for any object
+ * what will use "DOM generation" pattern
+ *
+ * Inside this function two things can happen:
+ * 1) dom structure creation
+ * 2) event handlers attached to the dom structure
+ */
WrappedElement.prototype.createDom = function(){
+ /* inside at the very least you must assign
+ * a jQuery object to a parameter called _element
+ */
this._element = $('<div></div>');
};
+
+/**
+ * @param {object} element, a jQuery object wrapping a single
+ * DOM element.
+ *
+ * This function must be overridden in the subclasses
+ * that are used in the "decoration" pattern
+ */
WrappedElement.prototype.decorate = function(element){
this._element = element;
};
+
+/**
+ * This method should not be overridden
+ * Normally you call this method to generate the dom
+ * structure, if applicable, or just obtain the
+ * jQuery object encapsulating the dom.
+ *
+ * @return {object} jQuery
+ */
WrappedElement.prototype.getElement = function(){
if (this._element === null){
this.createDom();
@@ -278,10 +323,23 @@ WrappedElement.prototype.enterDocument = function(){
WrappedElement.prototype.hasElement = function(){
return (this._element !== null);
};
+/**
+ * A utility method, returning a new jQuery object for
+ * some HTML tag
+ *
+ * Example:
+ * var ageInput = this.makeElement('input');
+ */
WrappedElement.prototype.makeElement = function(html_tag){
//makes jQuery element with tags
return $('<' + html_tag + '></' + html_tag + '>');
};
+/**
+ * Removes object's DOM element from the DOM tree
+ * should be overridden to remove the event handlers
+ * and properly destroy the dom structure
+ * as well as any other included sub-elements
+ */
WrappedElement.prototype.dispose = function(){
this._element.remove();
this._in_document = false;
@@ -320,14 +378,29 @@ Widget.prototype.makeButton = function(label, handler) {
* perhaps empty text, the instruction is restored.
* When instruction is shown, class "blank" is present
* in the input/textare element.
+ *
+ * For the usage examples - search for "new TippedInput"
+ * there is at least one example for both - decoration and
+ * the "dom creation" patterns.
+ *
+ * Also - in the FileUploadDialog the TippedInput is used
+ * as a sub-element - the widget composition use case.
*/
var TippedInput = function(){
+ /* the call below is part 1 of the inheritance pattern */
WrappedElement.call(this);
this._instruction = null;
this._attrs = {};
//this._is_one_shot = false;//if true on starting typing effect is gone
};
inherits(TippedInput, WrappedElement);
+/* the line above is part 2 of the inheritance pattern
+ see definition of the function "inherits" for more details
+*/
+
+/* Below are all the custom methods of the
+ TippedInput class, as well as some required functions
+*/
TippedInput.prototype.reset = function(){
$(this._element).val(this._instruction);
@@ -364,22 +437,48 @@ TippedInput.prototype.setVal = function(value){
}
}
};
-
+/**
+ * Creates the DOM of tipped input from scratch
+ * Notice that there is also a "decorate" method.
+ * At least one - createDom or decorate is required,
+ * depending on the usage.
+ */
TippedInput.prototype.createDom = function() {
this._element = this.makeElement('input');
var element = this._element;
element.val(this._instruction);
+
+ //here we re-use the decorate call (next method)
+ //to avoid copy-pasting code
this.decorate(element);
};
+/**
+ * Attaches the TippedInput behavior to
+ * a pre-existing <input> element
+ *
+ * decorate() method normally does not create
+ * new dom elements, but it might add some missing elements,
+ * if necessary.
+ *
+ * for example the decorate might be composing inside
+ * a more complex widget, in which case other elements
+ * can be added via a "composition" pattern, or
+ * just "naked dom elements" added to the current widget's element
+ *
+ */
TippedInput.prototype.decorate = function(element){
- this._element = element;
+ this._element = element;//mandatory line
+
+ //part 1 - initialize some values and create dom
element.attr(this._attrs);
var instruction_text = this.getVal();
this._instruction = instruction_text;
this.reset();
var me = this;
+
+ //part 2 - attach event handlers
$(element).focus(function(){
if (me.isBlank()){
$(element)
@@ -395,7 +494,7 @@ TippedInput.prototype.decorate = function(element){
.addClass('blank');
}
});
- makeKeyHandler(13, function(){
+ makeKeyHandler(27, function(){
$(element).blur();
});
};
@@ -618,6 +717,9 @@ ModalDialog.prototype.hide = function() {
ModalDialog.prototype.setContent = function(content) {
this._initial_content = content;
+ if (this._content_element) {
+ this._content_element.html(content);
+ }
};
ModalDialog.prototype.prependContent = function(content) {
@@ -697,14 +799,18 @@ ModalDialog.prototype.createDom = function() {
accept_btn.html(this._accept_button_text);
footer.append(accept_btn);
- var reject_btn = this.makeElement('button');
- reject_btn.addClass('btn cancel');
- reject_btn.html(this._reject_button_text);
- footer.append(reject_btn);
+ if (this._reject_button_text) {
+ var reject_btn = this.makeElement('button');
+ reject_btn.addClass('btn cancel');
+ reject_btn.html(this._reject_button_text);
+ footer.append(reject_btn);
+ }
//4) attach event handlers to the buttons
setupButtonEventHandlers(accept_btn, this._accept_handler);
- setupButtonEventHandlers(reject_btn, this._reject_handler);
+ if (this._reject_button_text) {
+ setupButtonEventHandlers(reject_btn, this._reject_handler);
+ }
setupButtonEventHandlers(close_link, this._reject_handler);
this.hide();
@@ -1610,8 +1716,807 @@ Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hi
if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());
//jquery fieldselection
(function(){var a={getSelection:function(){var b=this.jquery?this[0]:this;return(("selectionStart" in b&&function(){var c=b.selectionEnd-b.selectionStart;return{start:b.selectionStart,end:b.selectionEnd,length:c,text:b.value.substr(b.selectionStart,c)}})||(document.selection&&function(){b.focus();var d=document.selection.createRange();if(d==null){return{start:0,end:b.value.length,length:0}}var c=b.createTextRange();var e=c.duplicate();c.moveToBookmark(d.getBookmark());e.setEndPoint("EndToStart",c);return{start:e.text.length,end:e.text.length+d.text.length,length:d.text.length,text:d.text}})||function(){return{start:0,end:b.value.length,length:0}})()},replaceSelection:function(){var b=this.jquery?this[0]:this;var c=arguments[0]||"";return(("selectionStart" in b&&function(){b.value=b.value.substr(0,b.selectionStart)+c+b.value.substr(b.selectionEnd,b.value.length);return this})||(document.selection&&function(){b.focus();document.selection.createRange().text=c;return this})||function(){b.value+=c;return this})()}};jQuery.each(a,function(b){jQuery.fn[b]=this})})();
-//our custom autocompleter
-var AutoCompleter=function(a){var b={autocompleteMultiple:true,multipleSeparator:" ",inputClass:"acInput",loadingClass:"acLoading",resultsClass:"acResults",selectClass:"acSelect",queryParamName:"q",limitParamName:"limit",extraParams:{},lineSeparator:"\n",cellSeparator:"|",minChars:2,maxItemsToShow:10,delay:400,useCache:true,maxCacheLength:10,matchSubset:true,matchCase:false,matchInside:true,mustMatch:false,preloadData:false,selectFirst:false,stopCharRegex:/\s+/,selectOnly:false,formatItem:null,onItemSelect:false,autoFill:false,filterResults:true,sortResults:true,sortFunction:false,onNoMatch:false};this.options=$.extend({},b,a);this.cacheData_={};this.cacheLength_=0;this.selectClass_="jquery-autocomplete-selected-item";this.keyTimeout_=null;this.lastKeyPressed_=null;this.lastProcessedValue_=null;this.lastSelectedValue_=null;this.active_=false;this.finishOnBlur_=true;this.options.minChars=parseInt(this.options.minChars,10);if(isNaN(this.options.minChars)||this.options.minChars<1){this.options.minChars=2}this.options.maxItemsToShow=parseInt(this.options.maxItemsToShow,10);if(isNaN(this.options.maxItemsToShow)||this.options.maxItemsToShow<1){this.options.maxItemsToShow=10}this.options.maxCacheLength=parseInt(this.options.maxCacheLength,10);if(isNaN(this.options.maxCacheLength)||this.options.maxCacheLength<1){this.options.maxCacheLength=10}if(this.options.preloadData===true){this.fetchRemoteData("",function(){})}};inherits(AutoCompleter,WrappedElement);AutoCompleter.prototype.decorate=function(a){this._element=a;this._element.attr("autocomplete","off");this._results=$("<div></div>").hide();if(this.options.resultsClass){this._results.addClass(this.options.resultsClass)}this._results.css({position:"absolute"});$("body").append(this._results);this.setEventHandlers()};AutoCompleter.prototype.setEventHandlers=function(){var a=this;a._element.keydown(function(b){a.lastKeyPressed_=b.keyCode;switch(a.lastKeyPressed_){case 38:b.preventDefault();if(a.active_){a.focusPrev()}else{a.activate()}return false;break;case 40:b.preventDefault();if(a.active_){a.focusNext()}else{a.activate()}return false;break;case 9:case 13:if(a.active_){b.preventDefault();a.selectCurrent();return false}break;case 27:if(a.active_){b.preventDefault();a.finish();return false}break;default:a.activate()}});a._element.blur(function(){if(a.finishOnBlur_){setTimeout(function(){a.finish()},200)}})};AutoCompleter.prototype.position=function(){var a=this._element.offset();this._results.css({top:a.top+this._element.outerHeight(),left:a.left})};AutoCompleter.prototype.cacheRead=function(d){var f,c,b,a,e;if(this.options.useCache){d=String(d);f=d.length;if(this.options.matchSubset){c=1}else{c=f}while(c<=f){if(this.options.matchInside){a=f-c}else{a=0}e=0;while(e<=a){b=d.substr(0,c);if(this.cacheData_[b]!==undefined){return this.cacheData_[b]}e++}c++}}return false};AutoCompleter.prototype.cacheWrite=function(a,b){if(this.options.useCache){if(this.cacheLength_>=this.options.maxCacheLength){this.cacheFlush()}a=String(a);if(this.cacheData_[a]!==undefined){this.cacheLength_++}return this.cacheData_[a]=b}return false};AutoCompleter.prototype.cacheFlush=function(){this.cacheData_={};this.cacheLength_=0};AutoCompleter.prototype.callHook=function(c,b){var a=this.options[c];if(a&&$.isFunction(a)){return a(b,this)}return false};AutoCompleter.prototype.activate=function(){var b=this;var a=function(){b.activateNow()};var c=parseInt(this.options.delay,10);if(isNaN(c)||c<=0){c=250}if(this.keyTimeout_){clearTimeout(this.keyTimeout_)}this.keyTimeout_=setTimeout(a,c)};AutoCompleter.prototype.activateNow=function(){var a=this.getValue();if(a!==this.lastProcessedValue_&&a!==this.lastSelectedValue_){if(a.length>=this.options.minChars){this.active_=true;this.lastProcessedValue_=a;this.fetchData(a)}}};AutoCompleter.prototype.fetchData=function(b){if(this.options.data){this.filterAndShowResults(this.options.data,b)}else{var a=this;this.fetchRemoteData(b,function(c){a.filterAndShowResults(c,b)})}};AutoCompleter.prototype.fetchRemoteData=function(c,e){var d=this.cacheRead(c);if(d){e(d)}else{var a=this;if(this._element){this._element.addClass(this.options.loadingClass)}var b=function(g){var f=false;if(g!==false){f=a.parseRemoteData(g);a.options.data=f;a.cacheWrite(c,f)}if(a._element){a._element.removeClass(a.options.loadingClass)}e(f)};$.ajax({url:this.makeUrl(c),success:b,error:function(){b(false)}})}};AutoCompleter.prototype.setOption=function(a,b){this.options[a]=b};AutoCompleter.prototype.setExtraParam=function(b,c){var a=$.trim(String(b));if(a){if(!this.options.extraParams){this.options.extraParams={}}if(this.options.extraParams[a]!==c){this.options.extraParams[a]=c;this.cacheFlush()}}};AutoCompleter.prototype.makeUrl=function(e){var a=this;var b=this.options.url;var d=$.extend({},this.options.extraParams);if(this.options.queryParamName===false){b+=encodeURIComponent(e)}else{d[this.options.queryParamName]=e}if(this.options.limitParamName&&this.options.maxItemsToShow){d[this.options.limitParamName]=this.options.maxItemsToShow}var c=[];$.each(d,function(f,g){c.push(a.makeUrlParam(f,g))});if(c.length){b+=b.indexOf("?")==-1?"?":"&";b+=c.join("&")}return b};AutoCompleter.prototype.makeUrlParam=function(a,b){return String(a)+"="+encodeURIComponent(b)};AutoCompleter.prototype.splitText=function(a){return String(a).replace(/(\r\n|\r|\n)/g,"\n").split(this.options.lineSeparator)};AutoCompleter.prototype.parseRemoteData=function(c){var h,b,f,d,g;var e=[];var b=this.splitText(c);for(f=0;f<b.length;f++){var a=b[f].split(this.options.cellSeparator);g=[];for(d=0;d<a.length;d++){g.push(unescape(a[d]))}h=g.shift();e.push({value:unescape(h),data:g})}return e};AutoCompleter.prototype.filterAndShowResults=function(a,b){this.showResults(this.filterResults(a,b),b)};AutoCompleter.prototype.filterResults=function(d,b){var f=[];var l,c,e,m,j,a;var k,h,g;for(e=0;e<d.length;e++){m=d[e];j=typeof m;if(j==="string"){l=m;c={}}else{if($.isArray(m)){l=m[0];c=m.slice(1)}else{if(j==="object"){l=m.value;c=m.data}}}l=String(l);if(l>""){if(typeof c!=="object"){c={}}if(this.options.filterResults){h=String(b);g=String(l);if(!this.options.matchCase){h=h.toLowerCase();g=g.toLowerCase()}a=g.indexOf(h);if(this.options.matchInside){a=a>-1}else{a=a===0}}else{a=true}if(a){f.push({value:l,data:c})}}}if(this.options.sortResults){f=this.sortResults(f,b)}if(this.options.maxItemsToShow>0&&this.options.maxItemsToShow<f.length){f.length=this.options.maxItemsToShow}return f};AutoCompleter.prototype.sortResults=function(c,d){var b=this;var a=this.options.sortFunction;if(!$.isFunction(a)){a=function(g,e,h){return b.sortValueAlpha(g,e,h)}}c.sort(function(f,e){return a(f,e,d)});return c};AutoCompleter.prototype.sortValueAlpha=function(d,c,e){d=String(d.value);c=String(c.value);if(!this.options.matchCase){d=d.toLowerCase();c=c.toLowerCase()}if(d>c){return 1}if(d<c){return -1}return 0};AutoCompleter.prototype.showResults=function(e,b){var k=this;var g=$("<ul></ul>");var f,l,j,a,h=false,d=false;var c=e.length;for(f=0;f<c;f++){l=e[f];j=$("<li>"+this.showResult(l.value,l.data)+"</li>");j.data("value",l.value);j.data("data",l.data);j.click(function(){var i=$(this);k.selectItem(i)}).mousedown(function(){k.finishOnBlur_=false}).mouseup(function(){k.finishOnBlur_=true});g.append(j);if(h===false){h=String(l.value);d=j;j.addClass(this.options.firstItemClass)}if(f==c-1){j.addClass(this.options.lastItemClass)}}this.position();this._results.html(g).show();a=this._results.outerWidth()-this._results.width();this._results.width(this._element.outerWidth()-a);$("li",this._results).hover(function(){k.focusItem(this)},function(){});if(this.autoFill(h,b)){this.focusItem(d)}};AutoCompleter.prototype.showResult=function(b,a){if($.isFunction(this.options.showResult)){return this.options.showResult(b,a)}else{return b}};AutoCompleter.prototype.autoFill=function(e,c){var b,a,d,f;if(this.options.autoFill&&this.lastKeyPressed_!=8){b=String(e).toLowerCase();a=String(c).toLowerCase();d=e.length;f=c.length;if(b.substr(0,f)===a){this._element.val(e);this.selectRange(f,d);return true}}return false};AutoCompleter.prototype.focusNext=function(){this.focusMove(+1)};AutoCompleter.prototype.focusPrev=function(){this.focusMove(-1)};AutoCompleter.prototype.focusMove=function(a){var b,c=$("li",this._results);a=parseInt(a,10);for(var b=0;b<c.length;b++){if($(c[b]).hasClass(this.selectClass_)){this.focusItem(b+a);return}}this.focusItem(0)};AutoCompleter.prototype.focusItem=function(b){var a,c=$("li",this._results);if(c.length){c.removeClass(this.selectClass_).removeClass(this.options.selectClass);if(typeof b==="number"){b=parseInt(b,10);if(b<0){b=0}else{if(b>=c.length){b=c.length-1}}a=$(c[b])}else{a=$(b)}if(a){a.addClass(this.selectClass_).addClass(this.options.selectClass)}}};AutoCompleter.prototype.selectCurrent=function(){var a=$("li."+this.selectClass_,this._results);if(a.length==1){this.selectItem(a)}else{this.finish()}};AutoCompleter.prototype.selectItem=function(d){var c=d.data("value");var b=d.data("data");var a=this.displayValue(c,b);this.lastProcessedValue_=a;this.lastSelectedValue_=a;this.setValue(a);this.setCaret(a.length);this.callHook("onItemSelect",{value:c,data:b});this.finish()};AutoCompleter.prototype.isContentChar=function(a){if(a.match(this.options.stopCharRegex)){return false}else{if(a===this.options.multipleSeparator){return false}else{return true}}};AutoCompleter.prototype.getValue=function(){var c=this._element.getSelection();var d=this._element.val();var f=c.start;var e=f;for(cpos=f;cpos>=0;cpos=cpos-1){if(cpos===d.length){continue}var b=d.charAt(cpos);if(!this.isContentChar(b)){break}e=cpos}var a=f;for(cpos=f;cpos<d.length;cpos=cpos+1){if(cpos===0){continue}var b=d.charAt(cpos);if(!this.isContentChar(b)){break}a=cpos}this._selection_start=e;this._selection_end=a;return d.substring(e,a)};AutoCompleter.prototype.setValue=function(b){var a=this._element.val().substring(0,this._selection_start);var c=this._element.val().substring(this._selection_end+1);this._element.val(a+b+c)};AutoCompleter.prototype.displayValue=function(b,a){if($.isFunction(this.options.displayValue)){return this.options.displayValue(b,a)}else{return b}};AutoCompleter.prototype.finish=function(){if(this.keyTimeout_){clearTimeout(this.keyTimeout_)}if(this._element.val()!==this.lastSelectedValue_){if(this.options.mustMatch){this._element.val("")}this.callHook("onNoMatch")}this._results.hide();this.lastKeyPressed_=null;this.lastProcessedValue_=null;if(this.active_){this.callHook("onFinish")}this.active_=false};AutoCompleter.prototype.selectRange=function(d,a){var c=this._element.get(0);if(c.setSelectionRange){c.focus();c.setSelectionRange(d,a)}else{if(this.createTextRange){var b=this.createTextRange();b.collapse(true);b.moveEnd("character",a);b.moveStart("character",d);b.select()}}};AutoCompleter.prototype.setCaret=function(a){this.selectRange(a,a)};
+/**
+ * AutoCompleter Object, refactored closure style from
+ * jQuery autocomplete plugin
+ * @param {Object=} options Settings
+ * @constructor
+ */
+var AutoCompleter = function(options) {
+
+ /**
+ * Default options for autocomplete plugin
+ */
+ var defaults = {
+ promptText: '',
+ autocompleteMultiple: true,
+ multipleSeparator: ' ',//a single character
+ inputClass: 'acInput',
+ loadingClass: 'acLoading',
+ resultsClass: 'acResults',
+ selectClass: 'acSelect',
+ queryParamName: 'q',
+ limitParamName: 'limit',
+ extraParams: {},
+ lineSeparator: '\n',
+ cellSeparator: '|',
+ minChars: 2,
+ maxItemsToShow: 10,
+ delay: 400,
+ useCache: true,
+ maxCacheLength: 10,
+ matchSubset: true,
+ matchCase: false,
+ matchInside: true,
+ mustMatch: false,
+ preloadData: false,
+ selectFirst: false,
+ stopCharRegex: /\s+/,
+ selectOnly: false,
+ formatItem: null, // TBD
+ onItemSelect: false,
+ autoFill: false,
+ filterResults: true,
+ sortResults: true,
+ sortFunction: false,
+ onNoMatch: false
+ };
+
+ /**
+ * Options dictionary
+ * @type Object
+ * @private
+ */
+ this.options = $.extend({}, defaults, options);
+
+ /**
+ * Cached data
+ * @type Object
+ * @private
+ */
+ this.cacheData_ = {};
+
+ /**
+ * Number of cached data items
+ * @type number
+ * @private
+ */
+ this.cacheLength_ = 0;
+
+ /**
+ * Class name to mark selected item
+ * @type string
+ * @private
+ */
+ this.selectClass_ = 'jquery-autocomplete-selected-item';
+
+ /**
+ * Handler to activation timeout
+ * @type ?number
+ * @private
+ */
+ this.keyTimeout_ = null;
+
+ /**
+ * Last key pressed in the input field (store for behavior)
+ * @type ?number
+ * @private
+ */
+ this.lastKeyPressed_ = null;
+
+ /**
+ * Last value processed by the autocompleter
+ * @type ?string
+ * @private
+ */
+ this.lastProcessedValue_ = null;
+
+ /**
+ * Last value selected by the user
+ * @type ?string
+ * @private
+ */
+ this.lastSelectedValue_ = null;
+
+ /**
+ * Is this autocompleter active?
+ * @type boolean
+ * @private
+ */
+ this.active_ = false;
+
+ /**
+ * Is it OK to finish on blur?
+ * @type boolean
+ * @private
+ */
+ this.finishOnBlur_ = true;
+
+ this.options.minChars = parseInt(this.options.minChars, 10);
+ if (isNaN(this.options.minChars) || this.options.minChars < 1) {
+ this.options.minChars = 2;
+ }
+
+ this.options.maxItemsToShow = parseInt(this.options.maxItemsToShow, 10);
+ if (isNaN(this.options.maxItemsToShow) || this.options.maxItemsToShow < 1) {
+ this.options.maxItemsToShow = 10;
+ }
+
+ this.options.maxCacheLength = parseInt(this.options.maxCacheLength, 10);
+ if (isNaN(this.options.maxCacheLength) || this.options.maxCacheLength < 1) {
+ this.options.maxCacheLength = 10;
+ }
+
+ if (this.options['preloadData'] === true){
+ this.fetchRemoteData('', function(){});
+ }
+};
+inherits(AutoCompleter, WrappedElement);
+
+AutoCompleter.prototype.decorate = function(element){
+
+ /**
+ * Init DOM elements repository
+ */
+ this._element = element;
+
+ /**
+ * Switch off the native autocomplete
+ */
+ this._element.attr('autocomplete', 'off');
+
+ /**
+ * Set prompt text
+ */
+ this.setPrompt();
+
+ /**
+ * Create DOM element to hold results
+ */
+ this._results = $('<div></div>').hide();
+ if (this.options.resultsClass) {
+ this._results.addClass(this.options.resultsClass);
+ }
+ this._results.css({
+ position: 'absolute'
+ });
+ $('body').append(this._results);
+
+ this.setEventHandlers();
+};
+
+AutoCompleter.prototype.setPrompt = function() {
+ this._element.val(this.options['promptText']);
+ this._element.addClass('prompt');
+};
+
+AutoCompleter.prototype.removePrompt = function() {
+ if (this._element.hasClass('prompt')) {
+ this._element.removeClass('prompt');
+ var val = this._element.val();
+ if (val === this.options['promptText']) {
+ this._element.val('');
+ }
+ }
+};
+
+AutoCompleter.prototype.setEventHandlers = function(){
+ /**
+ * Shortcut to self
+ */
+ var self = this;
+
+ /**
+ * Attach keyboard monitoring to $elem
+ */
+ self._element.keydown(function(e) {
+
+ self.removePrompt();
+
+ self.lastKeyPressed_ = e.keyCode;
+ switch(self.lastKeyPressed_) {
+
+ case 38: // up
+ e.preventDefault();
+ if (self.active_) {
+ self.focusPrev();
+ } else {
+ self.activate();
+ }
+ return false;
+ break;
+
+ case 40: // down
+ e.preventDefault();
+ if (self.active_) {
+ self.focusNext();
+ } else {
+ self.activate();
+ }
+ return false;
+ break;
+
+ case 9: // tab
+ case 13: // return
+ if (self.active_) {
+ e.preventDefault();
+ self.selectCurrent();
+ return false;
+ }
+ break;
+
+ case 27: // escape
+ if ($.trim(self._element.val()) === '') {
+ self._element.blur();
+ return false;
+ }
+ if (self.active_) {
+ e.preventDefault();
+ self.finish();
+ return false;
+ }
+ break;
+
+ default:
+ self.activate();
+
+ }
+ });
+ self._element.focus(function() {
+ self.removePrompt();
+ });
+ self._element.blur(function() {
+ if ($.trim(self._element.val()) === '') {
+ self.setPrompt();
+ return true;
+ }
+ if (self.finishOnBlur_) {
+ setTimeout(function() { self.finish(); }, 200);
+ }
+ });
+};
+
+AutoCompleter.prototype.position = function() {
+ var offset = this._element.offset();
+ this._results.css({
+ top: offset.top + this._element.outerHeight(),
+ left: offset.left
+ });
+};
+
+AutoCompleter.prototype.cacheRead = function(filter) {
+ var filterLength, searchLength, search, maxPos, pos;
+ if (this.options.useCache) {
+ filter = String(filter);
+ filterLength = filter.length;
+ if (this.options.matchSubset) {
+ searchLength = 1;
+ } else {
+ searchLength = filterLength;
+ }
+ while (searchLength <= filterLength) {
+ if (this.options.matchInside) {
+ maxPos = filterLength - searchLength;
+ } else {
+ maxPos = 0;
+ }
+ pos = 0;
+ while (pos <= maxPos) {
+ search = filter.substr(0, searchLength);
+ if (this.cacheData_[search] !== undefined) {
+ return this.cacheData_[search];
+ }
+ pos++;
+ }
+ searchLength++;
+ }
+ }
+ return false;
+};
+
+AutoCompleter.prototype.cacheWrite = function(filter, data) {
+ if (this.options.useCache) {
+ if (this.cacheLength_ >= this.options.maxCacheLength) {
+ this.cacheFlush();
+ }
+ filter = String(filter);
+ if (this.cacheData_[filter] !== undefined) {
+ this.cacheLength_++;
+ }
+ return this.cacheData_[filter] = data;
+ }
+ return false;
+};
+
+AutoCompleter.prototype.cacheFlush = function() {
+ this.cacheData_ = {};
+ this.cacheLength_ = 0;
+};
+
+AutoCompleter.prototype.callHook = function(hook, data) {
+ var f = this.options[hook];
+ if (f && $.isFunction(f)) {
+ return f(data, this);
+ }
+ return false;
+};
+
+AutoCompleter.prototype.activate = function() {
+ var self = this;
+ var activateNow = function() {
+ self.activateNow();
+ };
+ var delay = parseInt(this.options.delay, 10);
+ if (isNaN(delay) || delay <= 0) {
+ delay = 250;
+ }
+ if (this.keyTimeout_) {
+ clearTimeout(this.keyTimeout_);
+ }
+ this.keyTimeout_ = setTimeout(activateNow, delay);
+};
+
+AutoCompleter.prototype.activateNow = function() {
+ var value = this.getValue();
+ if (value !== this.lastProcessedValue_ && value !== this.lastSelectedValue_) {
+ if (value.length >= this.options.minChars) {
+ this.active_ = true;
+ this.lastProcessedValue_ = value;
+ this.fetchData(value);
+ }
+ }
+};
+
+AutoCompleter.prototype.fetchData = function(value) {
+ if (this.options.data) {
+ this.filterAndShowResults(this.options.data, value);
+ } else {
+ var self = this;
+ this.fetchRemoteData(value, function(remoteData) {
+ self.filterAndShowResults(remoteData, value);
+ });
+ }
+};
+
+AutoCompleter.prototype.fetchRemoteData = function(filter, callback) {
+ var data = this.cacheRead(filter);
+ if (data) {
+ callback(data);
+ } else {
+ var self = this;
+ if (this._element){
+ this._element.addClass(this.options.loadingClass);
+ }
+ var ajaxCallback = function(data) {
+ var parsed = false;
+ if (data !== false) {
+ parsed = self.parseRemoteData(data);
+ self.options.data = parsed;//cache data forever - E.F.
+ self.cacheWrite(filter, parsed);
+ }
+ if (self._element){
+ self._element.removeClass(self.options.loadingClass);
+ }
+ callback(parsed);
+ };
+ $.ajax({
+ url: this.makeUrl(filter),
+ success: ajaxCallback,
+ error: function() {
+ ajaxCallback(false);
+ }
+ });
+ }
+};
+
+AutoCompleter.prototype.setOption = function(name, value){
+ this.options[name] = value;
+};
+
+AutoCompleter.prototype.setExtraParam = function(name, value) {
+ var index = $.trim(String(name));
+ if (index) {
+ if (!this.options.extraParams) {
+ this.options.extraParams = {};
+ }
+ if (this.options.extraParams[index] !== value) {
+ this.options.extraParams[index] = value;
+ this.cacheFlush();
+ }
+ }
+};
+
+AutoCompleter.prototype.makeUrl = function(param) {
+ var self = this;
+ var url = this.options.url;
+ var params = $.extend({}, this.options.extraParams);
+ // If options.queryParamName === false, append query to url
+ // instead of using a GET parameter
+ if (this.options.queryParamName === false) {
+ url += encodeURIComponent(param);
+ } else {
+ params[this.options.queryParamName] = param;
+ }
+
+ if (this.options.limitParamName && this.options.maxItemsToShow) {
+ params[this.options.limitParamName] = this.options.maxItemsToShow;
+ }
+
+ var urlAppend = [];
+ $.each(params, function(index, value) {
+ urlAppend.push(self.makeUrlParam(index, value));
+ });
+ if (urlAppend.length) {
+ url += url.indexOf('?') == -1 ? '?' : '&';
+ url += urlAppend.join('&');
+ }
+ return url;
+};
+
+AutoCompleter.prototype.makeUrlParam = function(name, value) {
+ return String(name) + '=' + encodeURIComponent(value);
+};
+
+/**
+ * Sanitize CR and LF, then split into lines
+ */
+AutoCompleter.prototype.splitText = function(text) {
+ return String(text).replace(/(\r\n|\r|\n)/g, '\n').split(this.options.lineSeparator);
+};
+
+AutoCompleter.prototype.parseRemoteData = function(remoteData) {
+ var value, lines, i, j, data;
+ var results = [];
+ var lines = this.splitText(remoteData);
+ for (i = 0; i < lines.length; i++) {
+ var line = lines[i].split(this.options.cellSeparator);
+ data = [];
+ for (j = 0; j < line.length; j++) {
+ data.push(unescape(line[j]));
+ }
+ value = data.shift();
+ results.push({ value: unescape(value), data: data });
+ }
+ return results;
+};
+
+AutoCompleter.prototype.filterAndShowResults = function(results, filter) {
+ this.showResults(this.filterResults(results, filter), filter);
+};
+
+AutoCompleter.prototype.filterResults = function(results, filter) {
+
+ var filtered = [];
+ var value, data, i, result, type, include;
+ var regex, pattern, testValue;
+
+ for (i = 0; i < results.length; i++) {
+ result = results[i];
+ type = typeof result;
+ if (type === 'string') {
+ value = result;
+ data = {};
+ } else if ($.isArray(result)) {
+ value = result[0];
+ data = result.slice(1);
+ } else if (type === 'object') {
+ value = result.value;
+ data = result.data;
+ }
+ value = String(value);
+ if (value > '') {
+ if (typeof data !== 'object') {
+ data = {};
+ }
+ if (this.options.filterResults) {
+ pattern = String(filter);
+ testValue = String(value);
+ if (!this.options.matchCase) {
+ pattern = pattern.toLowerCase();
+ testValue = testValue.toLowerCase();
+ }
+ include = testValue.indexOf(pattern);
+ if (this.options.matchInside) {
+ include = include > -1;
+ } else {
+ include = include === 0;
+ }
+ } else {
+ include = true;
+ }
+ if (include) {
+ filtered.push({ value: value, data: data });
+ }
+ }
+ }
+
+ if (this.options.sortResults) {
+ filtered = this.sortResults(filtered, filter);
+ }
+
+ if (this.options.maxItemsToShow > 0 && this.options.maxItemsToShow < filtered.length) {
+ filtered.length = this.options.maxItemsToShow;
+ }
+
+ return filtered;
+
+};
+
+AutoCompleter.prototype.sortResults = function(results, filter) {
+ var self = this;
+ var sortFunction = this.options.sortFunction;
+ if (!$.isFunction(sortFunction)) {
+ sortFunction = function(a, b, f) {
+ return self.sortValueAlpha(a, b, f);
+ };
+ }
+ results.sort(function(a, b) {
+ return sortFunction(a, b, filter);
+ });
+ return results;
+};
+
+AutoCompleter.prototype.sortValueAlpha = function(a, b, filter) {
+ a = String(a.value);
+ b = String(b.value);
+ if (!this.options.matchCase) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ }
+ if (a > b) {
+ return 1;
+ }
+ if (a < b) {
+ return -1;
+ }
+ return 0;
+};
+
+AutoCompleter.prototype.showResults = function(results, filter) {
+ var self = this;
+ var $ul = $('<ul></ul>');
+ var i, result, $li, extraWidth, first = false, $first = false;
+ var numResults = results.length;
+ for (i = 0; i < numResults; i++) {
+ result = results[i];
+ $li = $('<li>' + this.showResult(result.value, result.data) + '</li>');
+ $li.data('value', result.value);
+ $li.data('data', result.data);
+ $li.click(function() {
+ var $this = $(this);
+ self.selectItem($this);
+ }).mousedown(function() {
+ self.finishOnBlur_ = false;
+ }).mouseup(function() {
+ self.finishOnBlur_ = true;
+ });
+ $ul.append($li);
+ if (first === false) {
+ first = String(result.value);
+ $first = $li;
+ $li.addClass(this.options.firstItemClass);
+ }
+ if (i == numResults - 1) {
+ $li.addClass(this.options.lastItemClass);
+ }
+ }
+
+ // Alway recalculate position before showing since window size or
+ // input element location may have changed. This fixes #14
+ this.position();
+
+ this._results.html($ul).show();
+ extraWidth = this._results.outerWidth() - this._results.width();
+ this._results.width(this._element.outerWidth() - extraWidth);
+ $('li', this._results).hover(
+ function() { self.focusItem(this); },
+ function() { /* void */ }
+ );
+ if (this.autoFill(first, filter)) {
+ this.focusItem($first);
+ }
+};
+
+AutoCompleter.prototype.showResult = function(value, data) {
+ if ($.isFunction(this.options.showResult)) {
+ return this.options.showResult(value, data);
+ } else {
+ return value;
+ }
+};
+
+AutoCompleter.prototype.autoFill = function(value, filter) {
+ var lcValue, lcFilter, valueLength, filterLength;
+ if (this.options.autoFill && this.lastKeyPressed_ != 8) {
+ lcValue = String(value).toLowerCase();
+ lcFilter = String(filter).toLowerCase();
+ valueLength = value.length;
+ filterLength = filter.length;
+ if (lcValue.substr(0, filterLength) === lcFilter) {
+ this._element.val(value);
+ this.selectRange(filterLength, valueLength);
+ return true;
+ }
+ }
+ return false;
+};
+
+AutoCompleter.prototype.focusNext = function() {
+ this.focusMove(+1);
+};
+
+AutoCompleter.prototype.focusPrev = function() {
+ this.focusMove(-1);
+};
+
+AutoCompleter.prototype.focusMove = function(modifier) {
+ var i, $items = $('li', this._results);
+ modifier = parseInt(modifier, 10);
+ for (var i = 0; i < $items.length; i++) {
+ if ($($items[i]).hasClass(this.selectClass_)) {
+ this.focusItem(i + modifier);
+ return;
+ }
+ }
+ this.focusItem(0);
+};
+
+AutoCompleter.prototype.focusItem = function(item) {
+ var $item, $items = $('li', this._results);
+ if ($items.length) {
+ $items.removeClass(this.selectClass_).removeClass(this.options.selectClass);
+ if (typeof item === 'number') {
+ item = parseInt(item, 10);
+ if (item < 0) {
+ item = 0;
+ } else if (item >= $items.length) {
+ item = $items.length - 1;
+ }
+ $item = $($items[item]);
+ } else {
+ $item = $(item);
+ }
+ if ($item) {
+ $item.addClass(this.selectClass_).addClass(this.options.selectClass);
+ }
+ }
+};
+
+AutoCompleter.prototype.selectCurrent = function() {
+ var $item = $('li.' + this.selectClass_, this._results);
+ if ($item.length == 1) {
+ this.selectItem($item);
+ } else {
+ this.finish();
+ }
+};
+
+AutoCompleter.prototype.selectItem = function($li) {
+ var value = $li.data('value');
+ var data = $li.data('data');
+ var displayValue = this.displayValue(value, data);
+ this.lastProcessedValue_ = displayValue;
+ this.lastSelectedValue_ = displayValue;
+
+ this.setValue(displayValue);
+
+ this.setCaret(displayValue.length);
+ this.callHook('onItemSelect', { value: value, data: data });
+ this.finish();
+};
+
+/**
+ * @return {boolean} true if the symbol matches something that is
+ * considered content and false otherwise
+ * @param {string} symbol - a single char string
+ */
+AutoCompleter.prototype.isContentChar = function(symbol){
+ if (symbol.match(this.options['stopCharRegex'])){
+ return false;
+ } else if (symbol === this.options['multipleSeparator']){
+ return false;
+ } else {
+ return true;
+ }
+};
+
+/**
+ * takes value from the input box
+ * and saves _selection_start and _selection_end coordinates
+ * respects settings autocompleteMultiple and
+ * multipleSeparator
+ * @return {string} the current word in the
+ * autocompletable word
+ */
+AutoCompleter.prototype.getValue = function(){
+ var sel = this._element.getSelection();
+ var text = this._element.val();
+ var pos = sel.start;//estimated start
+ //find real start
+ var start = pos;
+ for (cpos = pos; cpos >= 0; cpos = cpos - 1){
+ if (cpos === text.length){
+ continue;
+ }
+ var symbol = text.charAt(cpos);
+ if (!this.isContentChar(symbol)){
+ break;
+ }
+ start = cpos;
+ }
+ //find real end
+ var end = pos;
+ for (cpos = pos; cpos < text.length; cpos = cpos + 1){
+ if (cpos === 0){
+ continue;
+ }
+ var symbol = text.charAt(cpos);
+ if (!this.isContentChar(symbol)){
+ break;
+ }
+ end = cpos;
+ }
+ this._selection_start = start;
+ this._selection_end = end;
+ return text.substring(start, end);
+}
+
+/**
+ * sets value of the input box
+ * by replacing the previous selection
+ * with the value from the autocompleter
+ */
+AutoCompleter.prototype.setValue = function(val){
+ var prefix = this._element.val().substring(0, this._selection_start);
+ var postfix = this._element.val().substring(this._selection_end + 1);
+ this._element.val(prefix + val + postfix);
+};
+
+AutoCompleter.prototype.displayValue = function(value, data) {
+ if ($.isFunction(this.options.displayValue)) {
+ return this.options.displayValue(value, data);
+ } else {
+ return value;
+ }
+};
+
+AutoCompleter.prototype.finish = function() {
+ if (this.keyTimeout_) {
+ clearTimeout(this.keyTimeout_);
+ }
+ if (this._element.val() !== this.lastSelectedValue_) {
+ if (this.options.mustMatch) {
+ this._element.val('');
+ }
+ this.callHook('onNoMatch');
+ }
+ this._results.hide();
+ this.lastKeyPressed_ = null;
+ this.lastProcessedValue_ = null;
+ if (this.active_) {
+ this.callHook('onFinish');
+ }
+ this.active_ = false;
+};
+
+AutoCompleter.prototype.selectRange = function(start, end) {
+ var input = this._element.get(0);
+ if (input.setSelectionRange) {
+ input.focus();
+ input.setSelectionRange(start, end);
+ } else if (this.createTextRange) {
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', start);
+ range.select();
+ }
+};
+
+AutoCompleter.prototype.setCaret = function(pos) {
+ this.selectRange(pos, pos);
+};
+
(function($){function isRGBACapable(){var $script=$("script:first"),color=$script.css("color"),result=false;if(/^rgba/.test(color)){result=true}else{try{result=(color!=$script.css("color","rgba(0, 0, 0, 0.5)").css("color"));$script.css("color",color)}catch(e){}}return result}$.extend(true,$,{support:{rgba:isRGBACapable()}});var properties=["color","backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","outlineColor"];$.each(properties,function(i,property){$.fx.step[property]=function(fx){if(!fx.init){fx.begin=parseColor($(fx.elem).css(property));fx.end=parseColor(fx.end);fx.init=true}fx.elem.style[property]=calculateColor(fx.begin,fx.end,fx.pos)}});$.fx.step.borderColor=function(fx){if(!fx.init){fx.end=parseColor(fx.end)}var borders=properties.slice(2,6);$.each(borders,function(i,property){if(!fx.init){fx[property]={begin:parseColor($(fx.elem).css(property))}}fx.elem.style[property]=calculateColor(fx[property].begin,fx.end,fx.pos)});fx.init=true};function calculateColor(begin,end,pos){var color="rgb"+($.support.rgba?"a":"")+"("+parseInt((begin[0]+pos*(end[0]-begin[0])),10)+","+parseInt((begin[1]+pos*(end[1]-begin[1])),10)+","+parseInt((begin[2]+pos*(end[2]-begin[2])),10);if($.support.rgba){color+=","+(begin&&end?parseFloat(begin[3]+pos*(end[3]-begin[3])):1)}color+=")";return color}function parseColor(color){var match,triplet;if(match=/#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(color)){triplet=[parseInt(match[1],16),parseInt(match[2],16),parseInt(match[3],16),1]}else{if(match=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(color)){triplet=[parseInt(match[1],16)*17,parseInt(match[2],16)*17,parseInt(match[3],16)*17,1]}else{if(match=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)){triplet=[parseInt(match[1]),parseInt(match[2]),parseInt(match[3]),1]}else{if(match=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(color)){triplet=[parseInt(match[1],10),parseInt(match[2],10),parseInt(match[3],10),parseFloat(match[4])]}else{if(color=="transparent"){triplet=[0,0,0,0]}}}}}return triplet}})(jQuery);
/**
diff --git a/askbot/skins/common/templates/authopenid/complete.html b/askbot/skins/common/templates/authopenid/complete.html
index a84aa646..c9afedee 100644
--- a/askbot/skins/common/templates/authopenid/complete.html
+++ b/askbot/skins/common/templates/authopenid/complete.html
@@ -14,7 +14,6 @@ parameters:
* username (same as screen name or username in the models, and nickname in openid sreg)
* openid_register_form
* openid_verify_form - not clear what this form is supposed to do, not used for legacy
-* email_feeds_form forum.forms.SimpleEmailSubscribeForm
* openid_username_exists
#}
{% block head %}{% endblock %}
@@ -57,13 +56,6 @@ anyone, must be valid</i>)
{% endif %}
{{ openid_register_form.email }}
</div>
- <p>{% trans %}<strong>Receive forum updates by email</strong>{% endtrans %}</p>
- <div class='simple-subscribe-options'>
- {{email_feeds_form.subscribe}}
- {% if email_feeds_form.errors %}
- <p class="error">{% trans %}please select one of the options above{% endtrans %}</p>
- {% endif %}
- </div>
<div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans %}Signup{% endtrans %}"/></div>
</form>
</div>
diff --git a/askbot/skins/common/templates/authopenid/signin.html b/askbot/skins/common/templates/authopenid/signin.html
index 2cce65b1..c5a5c47f 100644
--- a/askbot/skins/common/templates/authopenid/signin.html
+++ b/askbot/skins/common/templates/authopenid/signin.html
@@ -24,25 +24,25 @@
{% endtrans %}
</div>
{% endif %}
+ {% if not (view_subtype == 'default' and have_buttons) %}
<p id='login-intro'>
- {% if view_subtype == 'default' and have_buttons %}
- {% trans %}Choose your favorite service below to sign in using secure OpenID or similar technology. Your external service password always stays confidential and you don't have to rememeber or create another one.{% endtrans %}
- {% elif view_subtype == 'add_openid' and have_buttons %}
- {% if existing_login_methods %}
- {% trans %}It's a good idea to make sure that your existing login methods still work, or add a new one. Please click any of the icons below to check/change or add new login methods.{% endtrans %}
- {% else %}
- {% trans %}Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time.{% endtrans %}
- {% endif %}
- {% elif view_subtype == 'change_openid' and have_buttons %}
- {% if existing_login_methods %}
- {% trans %}Click on one of the icons below to add a new login method or re-validate an existing one.{% endtrans %}
- {% else %}
- {% trans %}You don't have a method to log in right now, please add one or more by clicking any of the icons below.{% endtrans %}
+ {% if view_subtype == 'add_openid' and have_buttons %}
+ {% if existing_login_methods %}
+ {% trans %}It's a good idea to make sure that your existing login methods still work, or add a new one. Please click any of the icons below to check/change or add new login methods.{% endtrans %}
+ {% else %}
+ {% trans %}Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time.{% endtrans %}
+ {% endif %}
+ {% elif view_subtype == 'change_openid' and have_buttons %}
+ {% if existing_login_methods %}
+ {% trans %}Click on one of the icons below to add a new login method or re-validate an existing one.{% endtrans %}
+ {% else %}
+ {% trans %}You don't have a method to log in right now, please add one or more by clicking any of the icons below.{% endtrans %}
+ {% endif %}
+ {% elif view_subtype == 'email_sent' %}
+ {% trans %}Please check your email and visit the enclosed link to re-connect to your account{% endtrans %}
{% endif %}
- {% elif view_subtype == 'email_sent' %}
- {% trans %}Please check your email and visit the enclosed link to re-connect to your account{% endtrans %}
- {% endif %}
</p>
+ {% endif %}
{% if openid_error_message %}
<p class="warning">{{ openid_error_message }}</p>
{% endif %}
diff --git a/askbot/skins/common/templates/authopenid/signup_with_password.html b/askbot/skins/common/templates/authopenid/signup_with_password.html
index e65cd518..e5a8f633 100644
--- a/askbot/skins/common/templates/authopenid/signup_with_password.html
+++ b/askbot/skins/common/templates/authopenid/signup_with_password.html
@@ -37,15 +37,6 @@ your login details with anyone and having to remember yet another password.{% en
<li><label for="password1_id">{{form.password1.label}}</label>{{form.password1}}{{form.password1.errors}}</li>
<li><label for="password2_id">{{form.password2.label}}</label>{{form.password2}}{{form.password2.errors}}</li>
</ul>
- <p style="margin-top: 10px">
- {% trans %}<strong>Receive periodic updates by email</strong>{% endtrans %}
- </p>
- <div class='simple-subscribe-options'>
- {{email_feeds_form.subscribe}}
- {% if email_feeds_form.errors %}
- <p class="error">{% trans %}please select one of the options above{% endtrans %}</p>
- {% endif %}
- </div>
{% if settings.USE_RECAPTCHA %}
<p class="signup_p">{% trans %}Please read and type in the two words below to help us prevent automated account creation.{% endtrans %}</p>
{{form.recaptcha}}
diff --git a/askbot/skins/common/templates/authopenid/verify_email.html b/askbot/skins/common/templates/authopenid/verify_email.html
new file mode 100644
index 00000000..613ca589
--- /dev/null
+++ b/askbot/skins/common/templates/authopenid/verify_email.html
@@ -0,0 +1,14 @@
+{% extends "one_column_body.html" %}
+{% block title %}{% spaceless %}{% trans %}Confirm email address{% endtrans %}{% endspaceless %}{% endblock %}
+{% block content %}
+ <h1 class="section-title">{% trans %}Confirm email address{% endtrans %}</h1>
+ <label for="validation_code">
+ {% trans %}Validation email sent. Please find it and follow the enclosed link.<br/>
+ If the link doesn't work - enter the code below:{% endtrans %}
+ </label>
+ <form method="post">{% csrf_token %}
+ <input id="validation-code" type="text" name="validation_code" />
+ <input type="submit" class="submit" value="{% trans %}Confirm email{% endtrans %}" />
+ </form>
+{% endblock %}
+<!-- end changeemail.html -->
diff --git a/askbot/skins/default/media/bootstrap/css/bootstrap.css b/askbot/skins/default/media/bootstrap/css/bootstrap.css
index b5f04009..e6190005 100644
--- a/askbot/skins/default/media/bootstrap/css/bootstrap.css
+++ b/askbot/skins/default/media/bootstrap/css/bootstrap.css
@@ -919,7 +919,7 @@ input[type="button"],
input[type="reset"],
input[type="submit"] {
width: auto;
- height: auto;
+ /*height: auto;*/
}
select,
input[type="file"] {
diff --git a/askbot/skins/default/media/images/sprites.png b/askbot/skins/default/media/images/sprites.png
index 8c513508..c4e9029c 100644
--- a/askbot/skins/default/media/images/sprites.png
+++ b/askbot/skins/default/media/images/sprites.png
Binary files differ
diff --git a/askbot/skins/default/media/images/sprites_source/sprites.svg b/askbot/skins/default/media/images/sprites_source/sprites.svg
index 585e578f..1c16c89c 100644
--- a/askbot/skins/default/media/images/sprites_source/sprites.svg
+++ b/askbot/skins/default/media/images/sprites_source/sprites.svg
@@ -14,9 +14,9 @@
height="207"
id="svg3448"
version="1.1"
- inkscape:version="0.48.1 r9760"
+ inkscape:version="0.48.3.1 r9886"
sodipodi:docname="sprites.svg"
- inkscape:export-filename="/home/bcorrales/personal/askbot/sprites.png"
+ inkscape:export-filename="/home/fitoria/code/askbot-devel/askbot/skins/default/media/images/sprites.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
@@ -221,16 +221,16 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="1"
- inkscape:cx="-14.220783"
- inkscape:cy="199.46611"
+ inkscape:zoom="1.8095563"
+ inkscape:cx="-20.257428"
+ inkscape:cy="137.9209"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1316"
- inkscape:window-height="744"
- inkscape:window-x="50"
- inkscape:window-y="24"
+ inkscape:window-width="1366"
+ inkscape:window-height="723"
+ inkscape:window-x="-3"
+ inkscape:window-y="-3"
inkscape:window-maximized="1" />
<metadata
id="metadata3453">
@@ -240,7 +240,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
+ <dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
@@ -348,69 +348,81 @@
x="28.158876"
y="953.7583"
style="font-size:14.30124187px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#e90f0f;fill-opacity:1;font-family:Trebuchet MS;-inkscape-font-specification:Trebuchet MS">X</tspan></text>
- <path
- inkscape:export-ydpi="90"
- inkscape:export-xdpi="90"
- inkscape:export-filename="/home/bcorrales/personal/oxfam/arte/disenoindex.png"
- id="path5443"
- d="m 60.268935,856.20911 c -1.0424,-0.85947 -2.66478,-0.61037 -3.61221,0.54899 -0.95263,1.15795 -0.86987,2.79882 0.17873,3.65569 1.0444,0.86116 2.66667,0.61206 3.61631,-0.54959 0.94503,-1.16205 0.86746,-2.79822 -0.17854,-3.65559 z m 5.88263,18.6866 -12.99549,-10.63781 c -0.44703,-0.36346 -0.64755,-0.93014 -0.58638,-1.50242 -0.05,-0.11386 -0.087,-0.2505 -0.10985,-0.41064 l -1.33739,-8.83579 c -0.14424,-0.94964 0.5206,-1.76262 1.48163,-1.8085 l 8.91926,-0.44173 c 0.38495,-0.019 1.49952,0.35486 1.78501,0.58457 l 12.99449,10.6375 c 0.74232,0.60366 0.7989,1.77001 0.12665,2.5924 l -7.71322,9.43427 c -0.66824,0.82178 -1.8207,0.99492 -2.56061,0.38925 z"
- clip-rule="evenodd"
- style="fill:#e7e8a8;fill-opacity:1;fill-rule:evenodd;stroke:none"
- inkscape:connector-curvature="0" />
- <path
- inkscape:connector-curvature="0"
- style="fill:#16160f;fill-opacity:1;fill-rule:evenodd"
- clip-rule="evenodd"
- d="m 60.685375,857.34507 c -0.92055,-0.75811 -2.28652,-0.61896 -3.04183,0.30448 -0.75951,0.92235 -0.62036,2.28942 0.30498,3.04553 0.92224,0.7595 2.28822,0.62046 3.04513,-0.30508 0.75321,-0.92595 0.61866,-2.28902 -0.30479,-3.04533 z m 5.77547,15.77522 -11.47288,-9.38529 c -0.39445,-0.32067 -0.58837,-0.79999 -0.56038,-1.27301 -0.047,-0.097 -0.084,-0.21191 -0.11056,-0.34586 l -1.50572,-7.39855 c -0.16224,-0.79519 0.36786,-1.44284 1.18105,-1.44084 l 7.54948,0.01 c 0.32578,4e-4 1.28701,0.35796 1.53911,0.56048 l 11.47158,9.38508 c 0.65525,0.53279 0.75211,1.50442 0.21612,2.15966 l -6.14833,7.51602 c -0.5325,0.65474 -1.50312,0.75031 -2.15627,0.21591 z"
- id="path5445"
- inkscape:export-filename="/home/bcorrales/personal/oxfam/arte/disenoindex.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90"
- inkscape:transform-center-x="0.5180824"
- inkscape:transform-center-y="-0.28622416" />
- <path
- inkscape:connector-curvature="0"
- id="path5457"
- d="m 143.74707,850.88647 c -5.0386,0.15973 -7.45306,6.82507 -4.44845,10.23363 -5.35784,2.62945 -3.73419,8.64761 -3.90826,13.70497 6.23105,0 12.46221,0 18.69326,0 -0.0342,-5.17704 1.38659,-11.61813 -4.44823,-13.9621 3.5491,-4.27537 -0.52838,-10.73631 -5.88832,-9.9765 z"
- style="fill:#e7e8a8;fill-opacity:1;stroke:none" />
- <path
- sodipodi:type="arc"
- style="fill:#16160f;fill-opacity:1;stroke:none"
- id="path5461"
- sodipodi:cx="-1766"
- sodipodi:cy="1210.3622"
- sodipodi:rx="20.875"
- sodipodi:ry="19.875"
- d="m -1746.6683,1217.8622 c -4.3506,10.1651 -16.5325,15.0477 -27.209,10.9056 -10.6766,-4.1422 -15.8049,-15.7405 -11.4544,-25.9056 4.3506,-10.1651 16.5325,-15.0477 27.209,-10.9056 10.6563,4.1342 15.789,15.6991 11.4755,25.8559"
- sodipodi:start="0.38694167"
- sodipodi:end="6.6674319"
- sodipodi:open="true"
- transform="matrix(0.23246322,0,0,0.23246322,554.98367,575.74893)" />
- <path
- style="fill:#16160f;fill-opacity:1;stroke:none"
- d="m 142.46627,862.02883 c -2.77296,0 -5.03938,2.03973 -5.40307,4.70966 l -0.021,0 0,0.11384 c -0.0243,0.20805 -0.0408,0.42712 -0.0408,0.64178 0,0.21103 0.0177,0.41632 0.0408,0.62106 l 0,5.0718 15.40196,0 0,-5.0718 c 0.0232,-0.20474 0.0408,-0.41003 0.0408,-0.62106 0,-0.21466 -0.0177,-0.43373 -0.0408,-0.64178 l 0,-0.11384 -0.021,0 c -0.3638,-2.66993 -2.63021,-4.70966 -5.40306,-4.70966 l -1.03504,0 -1.35592,2.81554 -1.36629,-2.81554 -0.79699,0 z"
- id="path5463"
- inkscape:connector-curvature="0" />
- <path
- style="opacity:0.95734594;fill:#e7e8a8;fill-opacity:1;stroke:none"
- d="m 230.09729,852.03013 c -1.6542,0.0555 -2.75231,2.08638 -1.97699,3.52883 -0.63135,1.02386 -1.18707,3.28768 -2.84533,2.51282 -1.58254,0.6738 -2.41713,-1.60371 -3.98172,-0.91457 -2.15656,0.50838 -2.50044,3.95528 -0.28646,4.60984 0.66658,0.11836 1.38854,0.19214 1.38577,1.01628 1.32012,1.04282 0.20536,3.35241 -1.27491,3.51052 -2.03524,0.99474 -1.52604,4.44609 0.79456,4.64674 1.48119,0.14147 2.53871,-1.5326 4.09258,-0.89608 1.17551,0.24938 1.76747,1.60242 2.05078,2.54048 -1.0087,1.8897 1.18605,4.16804 3.10405,3.20566 1.39067,-0.4601 1.85188,-2.07121 1.37652,-3.31653 0.41369,-0.83912 1.05271,-1.65965 1.6075,-2.34648 1.24643,-0.35941 2.55425,-0.14896 3.50119,0.71142 2.08378,0.88286 4.38154,-1.75378 2.95628,-3.63073 -0.53953,-1.15942 -2.19299,-0.66667 -2.50358,-1.94916 -0.82635,-1.06372 -0.34544,-2.40575 0.47111,-3.2519 0.51485,-0.51216 1.80066,-0.20231 2.15249,-1.1363 1.70737,-1.91245 -1.15266,-5.07734 -3.21491,-3.54741 -0.69552,1.09312 -1.98439,0.81045 -3.03932,0.8406 -0.98808,-0.56654 -1.66816,-1.89082 -1.84763,-2.90081 0.49774,-1.68564 -0.70875,-3.40169 -2.52198,-3.23322 z"
- id="path5491"
- inkscape:connector-curvature="0" />
- <path
- id="path5404"
- d="m 230.30968,853.20332 c -0.69043,0 -1.25641,0.56589 -1.25641,1.25632 0,0.41517 0.20601,0.77958 0.51734,1.00695 l -2.22628,3.81547 -4.22185,-0.009 c 0,0 0,-0.009 0,-0.009 -0.11993,-0.56552 -0.62747,-0.98854 -1.22877,-0.98854 -0.69034,0 -1.24707,0.55663 -1.24707,1.24716 0,0.69044 0.55673,1.24708 1.24707,1.24708 0.21813,0 0.4221,-0.0582 0.60056,-0.15692 l 2.01389,3.51967 -1.85688,3.19642 c -0.13232,-0.0472 -0.27629,-0.0841 -0.42497,-0.0841 -0.69044,0 -1.24717,0.55674 -1.24717,1.24727 0,0.69052 0.55673,1.25641 1.24717,1.25641 0.54915,0 1.01452,-0.35784 1.18244,-0.84994 l 3.88926,-0.0185 2.19872,3.72291 c -0.28692,0.22904 -0.48036,0.57421 -0.48036,0.96996 0,0.69053 0.55664,1.24726 1.24717,1.24726 0.69044,0 1.25641,-0.55673 1.25641,-1.24726 0,-0.37153 -0.16764,-0.70403 -0.42497,-0.93297 l 2.24487,-3.7599 4.02786,-0.009 c 0.13796,0.54037 0.62682,0.94231 1.21018,0.94231 0.69053,0 1.24717,-0.55673 1.24717,-1.24716 0,-0.69053 -0.55664,-1.25643 -1.24717,-1.25643 -0.18003,0 -0.3534,0.0425 -0.5081,0.11087 l -1.95849,-3.4089 2.05087,-3.53826 c 0.18262,0.10449 0.38447,0.16634 0.60971,0.16634 0.69053,0 1.25641,-0.55663 1.25641,-1.24707 0,-0.69062 -0.56588,-1.24726 -1.25641,-1.24726 -0.63921,0 -1.15415,0.48026 -1.22867,1.09941 l -4.13864,0.0185 -2.32808,-3.83387 c 0.28794,-0.22904 0.48045,-0.57347 0.48045,-0.96996 0,-0.69053 -0.55673,-1.25641 -1.24726,-1.25641 z"
- style="opacity:0.95734594;fill:#16160f;fill-opacity:1;stroke:none"
- inkscape:connector-curvature="0" />
- <path
- transform="matrix(0.05048908,0,0,0.04995915,374.93399,727.37564)"
- d="m -2780.5,2735.8623 c 0,45.5635 -37.3842,82.5 -83.5,82.5 -46.1158,0 -83.5,-36.9365 -83.5,-82.5 0,-45.5635 37.3842,-82.5 83.5,-82.5 46.1158,0 83.5,36.9365 83.5,82.5 z"
- sodipodi:ry="82.5"
- sodipodi:rx="83.5"
- sodipodi:cy="2735.8623"
- sodipodi:cx="-2864"
- id="path5754"
- style="opacity:0.95734594;fill:#e7e8a8;fill-opacity:1;stroke:none"
- sodipodi:type="arc" />
+ <g
+ id="g3045"
+ transform="translate(-46.460961,89.75573)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#e7e8a8;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ clip-rule="evenodd"
+ d="m 60.268935,856.20911 c -1.0424,-0.85947 -2.66478,-0.61037 -3.61221,0.54899 -0.95263,1.15795 -0.86987,2.79882 0.17873,3.65569 1.0444,0.86116 2.66667,0.61206 3.61631,-0.54959 0.94503,-1.16205 0.86746,-2.79822 -0.17854,-3.65559 z m 5.88263,18.6866 -12.99549,-10.63781 c -0.44703,-0.36346 -0.64755,-0.93014 -0.58638,-1.50242 -0.05,-0.11386 -0.087,-0.2505 -0.10985,-0.41064 l -1.33739,-8.83579 c -0.14424,-0.94964 0.5206,-1.76262 1.48163,-1.8085 l 8.91926,-0.44173 c 0.38495,-0.019 1.49952,0.35486 1.78501,0.58457 l 12.99449,10.6375 c 0.74232,0.60366 0.7989,1.77001 0.12665,2.5924 l -7.71322,9.43427 c -0.66824,0.82178 -1.8207,0.99492 -2.56061,0.38925 z"
+ id="path5443"
+ inkscape:export-filename="/home/bcorrales/personal/oxfam/arte/disenoindex.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ inkscape:transform-center-y="-0.28622416"
+ inkscape:transform-center-x="0.5180824"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ inkscape:export-filename="/home/bcorrales/personal/oxfam/arte/disenoindex.png"
+ id="path5445"
+ d="m 60.685375,857.34507 c -0.92055,-0.75811 -2.28652,-0.61896 -3.04183,0.30448 -0.75951,0.92235 -0.62036,2.28942 0.30498,3.04553 0.92224,0.7595 2.28822,0.62046 3.04513,-0.30508 0.75321,-0.92595 0.61866,-2.28902 -0.30479,-3.04533 z m 5.77547,15.77522 -11.47288,-9.38529 c -0.39445,-0.32067 -0.58837,-0.79999 -0.56038,-1.27301 -0.047,-0.097 -0.084,-0.21191 -0.11056,-0.34586 l -1.50572,-7.39855 c -0.16224,-0.79519 0.36786,-1.44284 1.18105,-1.44084 l 7.54948,0.01 c 0.32578,4e-4 1.28701,0.35796 1.53911,0.56048 l 11.47158,9.38508 c 0.65525,0.53279 0.75211,1.50442 0.21612,2.15966 l -6.14833,7.51602 c -0.5325,0.65474 -1.50312,0.75031 -2.15627,0.21591 z"
+ clip-rule="evenodd"
+ style="fill:#16160f;fill-opacity:1;fill-rule:evenodd"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g3049"
+ transform="translate(-130.64701,127.66187)">
+ <path
+ style="fill:#e7e8a8;fill-opacity:1;stroke:none"
+ d="m 143.74707,850.88647 c -5.0386,0.15973 -7.45306,6.82507 -4.44845,10.23363 -5.35784,2.62945 -3.73419,8.64761 -3.90826,13.70497 6.23105,0 12.46221,0 18.69326,0 -0.0342,-5.17704 1.38659,-11.61813 -4.44823,-13.9621 3.5491,-4.27537 -0.52838,-10.73631 -5.88832,-9.9765 z"
+ id="path5457"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(0.23246322,0,0,0.23246322,554.98367,575.74893)"
+ sodipodi:open="true"
+ sodipodi:end="6.6674319"
+ sodipodi:start="0.38694167"
+ d="m -1746.6683,1217.8622 c -4.3506,10.1651 -16.5325,15.0477 -27.209,10.9056 -10.6766,-4.1422 -15.8049,-15.7405 -11.4544,-25.9056 4.3506,-10.1651 16.5325,-15.0477 27.209,-10.9056 10.6563,4.1342 15.789,15.6991 11.4755,25.8559"
+ sodipodi:ry="19.875"
+ sodipodi:rx="20.875"
+ sodipodi:cy="1210.3622"
+ sodipodi:cx="-1766"
+ id="path5461"
+ style="fill:#16160f;fill-opacity:1;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5463"
+ d="m 142.46627,862.02883 c -2.77296,0 -5.03938,2.03973 -5.40307,4.70966 l -0.021,0 0,0.11384 c -0.0243,0.20805 -0.0408,0.42712 -0.0408,0.64178 0,0.21103 0.0177,0.41632 0.0408,0.62106 l 0,5.0718 15.40196,0 0,-5.0718 c 0.0232,-0.20474 0.0408,-0.41003 0.0408,-0.62106 0,-0.21466 -0.0177,-0.43373 -0.0408,-0.64178 l 0,-0.11384 -0.021,0 c -0.3638,-2.66993 -2.63021,-4.70966 -5.40306,-4.70966 l -1.03504,0 -1.35592,2.81554 -1.36629,-2.81554 -0.79699,0 z"
+ style="fill:#16160f;fill-opacity:1;stroke:none" />
+ </g>
+ <g
+ id="g3054"
+ transform="translate(-214.86075,163.94135)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5491"
+ d="m 230.09729,852.03013 c -1.6542,0.0555 -2.75231,2.08638 -1.97699,3.52883 -0.63135,1.02386 -1.18707,3.28768 -2.84533,2.51282 -1.58254,0.6738 -2.41713,-1.60371 -3.98172,-0.91457 -2.15656,0.50838 -2.50044,3.95528 -0.28646,4.60984 0.66658,0.11836 1.38854,0.19214 1.38577,1.01628 1.32012,1.04282 0.20536,3.35241 -1.27491,3.51052 -2.03524,0.99474 -1.52604,4.44609 0.79456,4.64674 1.48119,0.14147 2.53871,-1.5326 4.09258,-0.89608 1.17551,0.24938 1.76747,1.60242 2.05078,2.54048 -1.0087,1.8897 1.18605,4.16804 3.10405,3.20566 1.39067,-0.4601 1.85188,-2.07121 1.37652,-3.31653 0.41369,-0.83912 1.05271,-1.65965 1.6075,-2.34648 1.24643,-0.35941 2.55425,-0.14896 3.50119,0.71142 2.08378,0.88286 4.38154,-1.75378 2.95628,-3.63073 -0.53953,-1.15942 -2.19299,-0.66667 -2.50358,-1.94916 -0.82635,-1.06372 -0.34544,-2.40575 0.47111,-3.2519 0.51485,-0.51216 1.80066,-0.20231 2.15249,-1.1363 1.70737,-1.91245 -1.15266,-5.07734 -3.21491,-3.54741 -0.69552,1.09312 -1.98439,0.81045 -3.03932,0.8406 -0.98808,-0.56654 -1.66816,-1.89082 -1.84763,-2.90081 0.49774,-1.68564 -0.70875,-3.40169 -2.52198,-3.23322 z"
+ style="opacity:0.95734594;fill:#e7e8a8;fill-opacity:1;stroke:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:0.95734594;fill:#16160f;fill-opacity:1;stroke:none"
+ d="m 230.30968,853.20332 c -0.69043,0 -1.25641,0.56589 -1.25641,1.25632 0,0.41517 0.20601,0.77958 0.51734,1.00695 l -2.22628,3.81547 -4.22185,-0.009 c 0,0 0,-0.009 0,-0.009 -0.11993,-0.56552 -0.62747,-0.98854 -1.22877,-0.98854 -0.69034,0 -1.24707,0.55663 -1.24707,1.24716 0,0.69044 0.55673,1.24708 1.24707,1.24708 0.21813,0 0.4221,-0.0582 0.60056,-0.15692 l 2.01389,3.51967 -1.85688,3.19642 c -0.13232,-0.0472 -0.27629,-0.0841 -0.42497,-0.0841 -0.69044,0 -1.24717,0.55674 -1.24717,1.24727 0,0.69052 0.55673,1.25641 1.24717,1.25641 0.54915,0 1.01452,-0.35784 1.18244,-0.84994 l 3.88926,-0.0185 2.19872,3.72291 c -0.28692,0.22904 -0.48036,0.57421 -0.48036,0.96996 0,0.69053 0.55664,1.24726 1.24717,1.24726 0.69044,0 1.25641,-0.55673 1.25641,-1.24726 0,-0.37153 -0.16764,-0.70403 -0.42497,-0.93297 l 2.24487,-3.7599 4.02786,-0.009 c 0.13796,0.54037 0.62682,0.94231 1.21018,0.94231 0.69053,0 1.24717,-0.55673 1.24717,-1.24716 0,-0.69053 -0.55664,-1.25643 -1.24717,-1.25643 -0.18003,0 -0.3534,0.0425 -0.5081,0.11087 l -1.95849,-3.4089 2.05087,-3.53826 c 0.18262,0.10449 0.38447,0.16634 0.60971,0.16634 0.69053,0 1.25641,-0.55663 1.25641,-1.24707 0,-0.69062 -0.56588,-1.24726 -1.25641,-1.24726 -0.63921,0 -1.15415,0.48026 -1.22867,1.09941 l -4.13864,0.0185 -2.32808,-3.83387 c 0.28794,-0.22904 0.48045,-0.57347 0.48045,-0.96996 0,-0.69053 -0.55673,-1.25641 -1.24726,-1.25641 z"
+ id="path5404" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.95734594;fill:#e7e8a8;fill-opacity:1;stroke:none"
+ id="path5754"
+ sodipodi:cx="-2864"
+ sodipodi:cy="2735.8623"
+ sodipodi:rx="83.5"
+ sodipodi:ry="82.5"
+ d="m -2780.5,2735.8623 c 0,45.5635 -37.3842,82.5 -83.5,82.5 -46.1158,0 -83.5,-36.9365 -83.5,-82.5 0,-45.5635 37.3842,-82.5 83.5,-82.5 46.1158,0 83.5,36.9365 83.5,82.5 z"
+ transform="matrix(0.05048908,0,0,0.04995915,374.93399,727.37564)" />
+ </g>
<path
style="fill:url(#radialGradient5712-8);fill-opacity:1"
d="m 9.7549531,922.45495 c -0.094,-0.046 -0.2146,-0.1465 -0.2675,-0.2235 -0.092,-0.1328 -0.097,-0.507 -0.1104,-7.2852 l -0.014,-7.1451 5.5932999,-4.8985 c 3.0763,-2.6941 5.6262,-4.9108 5.6664,-4.9262 0.044,-0.017 2.291499,1.9154 5.694699,4.8961 l 5.6214,4.9239 -0.014,7.1474 c -0.013,6.7953 -0.019,7.1546 -0.1108,7.2883 -0.2172,0.3162 -0.1184,0.3086 -4.0168,0.3086 l -3.5623,0 -6e-4,-3.0266 c -6e-4,-1.6648 -0.016,-3.1187 -0.035,-3.2313 -0.022,-0.1319 -0.098,-0.2677 -0.2151,-0.3832 l -0.1811,-0.1789 -3.152299,0 -3.1523,0 -0.181,0.1789 c -0.1168,0.1155 -0.1931,0.2513 -0.2152,0.3832 -0.019,0.1126 -0.034,1.5665 -0.035,3.2313 l -5e-4,3.0266 -3.5704,0 c -3.1432,0 -3.5909999,-0.012 -3.7417999,-0.085 z m -4.3178,-15.0177 c -0.4595,-0.5139 -0.8170004,-0.9502 -0.7943004,-0.9695 0.023,-0.019 3.6131004,-3.1609 7.9784003,-6.9811 4.3654,-3.82019 7.9797,-6.94589 8.0317,-6.94589 0.052,0 3.651199,3.11599 7.998399,6.92429 4.3473,3.8085 7.9377,6.949 7.9789,6.9791 0.059,0.043 -0.096,0.24591 -0.764,0.9929 -0.7293,0.8159 -0.8498,0.9293 -0.9245,0.8704 -0.047,-0.038 -3.2709,-2.8571 -7.1641,-6.26639 -3.8931,-3.40961 -7.098499,-6.1989 -7.123099,-6.1989 -0.025,0 -3.2475,2.8051 -7.1619,6.23339 -3.9143999,3.4285 -7.1400999,6.2475 -7.1684999,6.2648 -0.028,0.018 -0.4273,-0.3892 -0.887,-0.9031 z m 3.9274,-9.9518 0,-2.96499 2.0325999,0 2.0326,0 0,0.9707 c 0,1.5539 0.2495,1.16 -2.0161,3.1826 -1.0729,0.95779 -1.9728999,1.74949 -1.9998999,1.75909 -0.03,0.011 -0.048,-1.1677 -0.048,-2.9474 z"
@@ -501,7 +513,7 @@
sodipodi:cy="147.08873"
sodipodi:rx="11.136931"
sodipodi:ry="9.0156116"
- d="m 234.75944,147.08873 a 11.136931,9.0156116 0 1 1 -22.27386,0 11.136931,9.0156116 0 1 1 22.27386,0 z"
+ d="m 234.75944,147.08873 c 0,4.97918 -4.98617,9.01561 -11.13693,9.01561 -6.15075,0 -11.13693,-4.03643 -11.13693,-9.01561 0,-4.97918 4.98618,-9.01561 11.13693,-9.01561 6.15076,0 11.13693,4.03643 11.13693,9.01561 z"
transform="matrix(1.2054549,0,0,1.1341085,-99.371969,741.25595)" />
</g>
</svg>
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index c8d92f60..55f5806f 100644
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -29,7 +29,7 @@ body {
line-height: 150%;
margin: 0;
padding: 0;
- color: #000;
+ color: #666;
font-family: Arial;
}
div {
@@ -67,6 +67,11 @@ select {
font-family: Trebuchet MS, "segoe ui", Helvetica, Tahoma, Verdana, MingLiu, PMingLiu, Arial, sans-serif;
margin-left: 0px;
}
+input[type="text"].prompt,
+input[type="password"].prompt {
+ font-style: italic;
+ color: #707070;
+}
textarea:focus,
input:focus {
outline: none;
@@ -194,10 +199,9 @@ body.user-messages {
padding: 0;
text-align: center;
background-color: #f5dd69;
- border-top: #fff 1px solid;
font-family: 'Open Sans Condensed', Arial, sans-serif;
}
-.notify p.notification {
+.notify .notification {
margin-top: 6px;
margin-bottom: 6px;
font-size: 16px;
@@ -307,16 +311,27 @@ body.user-messages {
text-decoration: underline;
}
#metaNav #navTags {
- background: -50px -5px url(../images/sprites.png) no-repeat;
+ background: 0px -95px url(../images/sprites.png) no-repeat;
}
#metaNav #navUsers {
- background: -125px -5px url(../images/sprites.png) no-repeat;
+ background: 3px -132px url(../images/sprites.png) no-repeat;
}
#metaNav #navGroups {
- background: -125px -5px url(../images/sprites.png) no-repeat;
+ background: 3px -132px url(../images/sprites.png) no-repeat;
+}
+#metaNav a.group-name {
+ padding: 0px;
+}
+#metaNav span.dropdown:hover ul.dropdown-menu {
+ display: block;
+}
+#metaNav .dropdown {
+ float: left;
}
-#metaNav #navBadges {
- background: -210px -5px url(../images/sprites.png) no-repeat;
+#metaNav .dropdown-menu {
+ /*top: 120%;*/
+
+ left: 7%;
}
#header.with-logo #userToolsNav {
position: absolute;
@@ -372,6 +387,22 @@ body.user-messages {
#secondaryHeader #scopeWrapper .ask-message {
font-size: 24px;
}
+.validate-email-page label {
+ color: #707070;
+ line-height: 1.35;
+ display: block;
+ margin: 10px 0;
+}
+.validate-email-page #validation-code {
+ padding-left: 5px;
+ border: #cce6ec 3px solid;
+ height: 25px;
+ font-size: 14px;
+ width: 200px;
+}
+.validate-email-page form {
+ margin-bottom: 30px;
+}
#searchBar {
/* Main search form , check widgets/search_bar.html */
@@ -527,6 +558,7 @@ body.anon #searchBar .searchInputCancelable {
margin-bottom: 4px;
color: #707070;
font-family: 'Open Sans Condensed', Arial, sans-serif;
+ font-size: 14px;
}
.box p.info-box-follow-up-links {
text-align: right;
@@ -557,6 +589,9 @@ body.anon #searchBar .searchInputCancelable {
.box .contributorback {
background: #eceeeb url(../images/contributorsback.png) no-repeat center left;
}
+.box form {
+ margin: 0px;
+}
.box label {
color: #707070;
font-size: 15px;
@@ -600,6 +635,7 @@ body.anon #searchBar .searchInputCancelable {
padding-left: 5px;
border: #c9c9b5 1px solid;
height: 25px;
+ font-size: 14px;
}
.box .inputs #ab-tag-search {
width: 138px;
@@ -762,13 +798,20 @@ body.anon #searchBar .searchInputCancelable {
}
.questions-related p {
line-height: 20px;
- padding: 4px 0px 4px 0px;
+ padding: 4px 0px 9px 0px;
font-size: 16px;
font-weight: normal;
border-bottom: #cccccc 1px solid;
}
+.questions-related p:first-child {
+ margin-top: -4px;
+}
+.questions-related p:last-child {
+ border: none;
+}
.questions-related a {
font-size: 13px;
+ line-height: 1.3;
}
/* tips and markdown help are widgets for ask template */
#tips li {
@@ -1239,10 +1282,68 @@ ul#related-tags li {
.tags a:hover {
color: #1A1A1A;
}
+.users-page th,
+.tags-page th,
+.groups-page th,
+.moderate-tags-page th {
+ padding-bottom: 5px;
+ font-weight: normal;
+}
.users-page h1,
.tags-page h1,
-.groups-page h1 {
+.groups-page h1,
+.moderate-tags-page h1 {
float: left;
+ padding-top: 7px;
+}
+.moderate-tags-page button {
+ line-height: 18px;
+}
+.moderate-tags-page table {
+ border-spacing: 0;
+}
+.moderate-tags-page table.suggested-tags-table {
+ width: 100%;
+}
+.moderate-tags-page th {
+ font-style: italic;
+}
+.moderate-tags-page th,
+.moderate-tags-page tr {
+ vertical-align: top;
+ text-align: left;
+ padding-right: 20px;
+}
+.moderate-tags-page td.per-thread-controls {
+ width: 120px;
+ /* 20px more to compensate for the padding */
+
+ height: 30px;
+}
+.moderate-tags-page td.per-thread-controls button {
+ display: none;
+}
+.moderate-tags-page th.decision-col,
+.moderate-tags-page th.tags-col,
+.moderate-tags-page th.users-col {
+ width: 100px;
+}
+.moderate-tags-page tr.per-tag-controls {
+ height: 30px;
+ text-align: center;
+}
+.moderate-tags-page tr.thread-info a {
+ line-height: 18px;
+}
+.moderate-tags-page tr.thread-info td {
+ padding-bottom: 5px;
+}
+.moderate-tags-page td.tags-col,
+.moderate-tags-page td.users-col {
+ padding-top: 7px;
+}
+.moderate-tags-page td.thread-links-col {
+ padding-top: 5px;
}
.main-page h1 {
margin-right: 5px;
@@ -1358,7 +1459,116 @@ ul#related-tags li {
font-size: 13px;
}
.ask-page #id_tags,
-.edit-question-page #id_tags,
+.edit-question-page #id_tags {
+ border: #cce6ec 3px solid;
+ height: 25px;
+ padding-left: 5px;
+ font-size: 14px;
+ width: 395px;
+}
+.ask-page #id_post_author_username,
+.question-page #id_post_author_username,
+.edit-question-page #id_post_author_username,
+.edit-answer-page #id_post_author_username,
+.ask-page #id_post_author_email,
+.question-page #id_post_author_email,
+.edit-question-page #id_post_author_email,
+.edit-answer-page #id_post_author_email {
+ border: #cce6ec 3px solid;
+ height: 25px;
+ padding-left: 5px;
+ font-size: 14px;
+ width: 186px;
+}
+.ask-page #id_post_author_email,
+.question-page #id_post_author_email,
+.edit-question-page #id_post_author_email,
+.edit-answer-page #id_post_author_email {
+ margin-left: 10px;
+}
+.ask-page table.proxy-user-info,
+.question-page table.proxy-user-info,
+.edit-question-page table.proxy-user-info,
+.edit-answer-page table.proxy-user-info {
+ border-spacing: 0px;
+}
+.ask-page table.proxy-user-info .form-item,
+.question-page table.proxy-user-info .form-item,
+.edit-question-page table.proxy-user-info .form-item,
+.edit-answer-page table.proxy-user-info .form-item {
+ float: left;
+}
+.groups-input,
+.users-input {
+ width: 152px;
+ padding-left: 5px;
+ border: #c9c9b5 1px solid;
+ height: 25px;
+ font-size: 14px;
+}
+.add-groups,
+.add-users {
+ border: 0;
+ font-weight: bold;
+ margin-top: -2px;
+ height: 27px;
+ font-size: 14px;
+ text-align: center;
+ text-decoration: none;
+ cursor: pointer;
+ color: #4a757f;
+ font-family: 'Open Sans Condensed', 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;
+}
+.share-input-col {
+ width: 160px;
+ text-align: center;
+}
+.add-everyone-group {
+ text-align: center;
+ margin: auto;
+ display: block;
+ padding: 0 10px;
+ height: 25px;
+}
+.add-groups: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;
+}
#id_user,
#id_user_author {
border: #cce6ec 3px solid;
@@ -1372,6 +1582,14 @@ ul#related-tags li {
font-size: 13px;
margin-bottom: 5px;
}
+.ask-page .title-desc,
+.question-page .title-desc,
+.ask-page .tags-desc,
+.question-page .tags-desc {
+ color: #707070;
+ font-style: italic;
+ font-size: 16px;
+}
#fmanswer input.submit,
.ask-page input.submit,
.edit-question-page input.submit {
@@ -1428,6 +1646,10 @@ ul#related-tags li {
}
.wmd-container {
border: #cce6ec 3px solid;
+ min-height: 250px;
+}
+.wmd-container textarea {
+ border: none;
}
.users-page .wmd-container {
width: 200px;
@@ -1445,6 +1667,12 @@ ul#related-tags li {
width: 710px;
padding: 6px;
}
+.ask-page .retagger-buttons button,
+.question-page .retagger-buttons button,
+.edit-question-page .retagger-buttons button,
+.edit-answer-page .retagger-buttons button {
+ margin: 8px 10px 5px 0;
+}
#editor {
/* adjustment for editor preview */
@@ -1590,6 +1818,12 @@ ul#related-tags li {
width: 682px;
margin-bottom: 10px;
}
+.question-page .question-content pre,
+.question-page .answer pre,
+.question-page .question-content code,
+.question-page .answer code {
+ clear: both;
+}
.question-page #question-table {
float: left;
border-top: #f0f0f0 1px solid;
@@ -1781,9 +2015,6 @@ ul#related-tags li {
.question-page #fmanswer_button {
margin: 8px 0px;
}
-.question-page #fmanswer_button.answer-own-question {
- width: 150px;
-}
.question-page .question-img-favorite:hover {
background: url(../images/vote-favorite-on.png);
}
@@ -2311,6 +2542,26 @@ ul#related-tags li {
.user-profile-page .cancel:hover {
background: url(../images/small-button-cancel.png) repeat-x bottom !important;
}
+.openid-signin .re,
+.meta .re,
+.users-page .re,
+.user-profile-edit-page .re,
+.user-profile-page .re {
+ float: left;
+ width: 960px;
+}
+.inbox-flags.user-profile-page .re {
+ width: 810px;
+}
+.inbox-flags.user-profile-page .post-moderation-controls {
+ float: left;
+ width: 150px;
+ margin-top: 23px;
+ text-align: right;
+}
+.inbox-flags.user-profile-page .dropdown:hover ul.dropdown-menu {
+ display: block;
+}
.openid-signin form {
margin-bottom: 5px;
}
@@ -2443,7 +2694,6 @@ ul#related-tags li {
}
/* tags page */
.tabBar-tags {
- width: 270px;
margin-bottom: 15px;
}
/* badges page */
@@ -2801,7 +3051,7 @@ ins {
width: 100%;
clear: both;
border-top: 1px solid #000;
- padding: 6px 0 0 0;
+ padding: 16px 0 0 0;
background: #16160f;
font-size: 16px;
font-family: 'Open Sans Condensed', Arial, sans-serif;
@@ -2812,14 +3062,14 @@ ins {
.footer-links {
color: #EEE;
text-align: left;
- width: 500px;
+ width: 450px;
float: left;
}
.footer-links a {
color: #e7e8a8;
}
.powered-link {
- width: 500px;
+ width: 450px;
float: left;
text-align: left;
}
@@ -2828,7 +3078,7 @@ ins {
}
.copyright {
color: #616161;
- width: 450px;
+ width: 500px;
float: right;
text-align: right;
}
@@ -3394,38 +3644,41 @@ a.edit {
padding-left: 3px;
color: #145bff;
}
-.str {
+pre {
+ /* name conflict here with tags */
+
+}
+pre .str {
color: #080;
}
-.kwd {
+pre .kwd {
color: #008;
}
-.com {
+pre .com {
color: #800;
}
-.typ {
+pre .typ {
color: #606;
}
-.lit {
+pre .lit {
color: #066;
}
-.pun {
+pre .pun {
color: #660;
}
-.pln {
+pre .pln {
color: #000;
}
-.tag {
+pre .tag {
color: #008;
}
-/* name conflict here */
-.atn {
+pre .atn {
color: #606;
}
-.atv {
+pre .atv {
color: #080;
}
-.dec {
+pre .dec {
color: #606;
}
pre.prettyprint {
@@ -3434,38 +3687,38 @@ pre.prettyprint {
border: 0px solid #888;
}
@media print {
- .str {
+ pre .str {
color: #060;
}
- .kwd {
+ pre .kwd {
color: #006;
font-weight: bold;
}
- .com {
+ pre .com {
color: #600;
font-style: italic;
}
- .typ {
+ pre .typ {
color: #404;
font-weight: bold;
}
- .lit {
+ pre .lit {
color: #044;
}
- .pun {
+ pre .pun {
color: #440;
}
- .pln {
+ pre .pln {
color: #000;
}
- .tag {
+ pre .tag {
color: #006;
font-weight: bold;
}
- .atn {
+ pre .atn {
color: #404;
}
- .atv {
+ pre .atv {
color: #060;
}
}
@@ -3563,6 +3816,13 @@ textarea.tipped-input {
font-size: 14px;
line-height: 25px;
}
+.select-box li input {
+ margin: 0 0 2px -5px;
+ font-size: 14px;
+ line-height: 14px;
+ vertical-align: middle;
+ color: #707070;
+}
.select-box li.selected,
.select-box li.selected:hover {
background-color: #fcf8e3;
@@ -3572,6 +3832,78 @@ textarea.tipped-input {
background-color: #cecece;
color: white;
}
+/* category selector */
+.category-selector {
+ border-spacing: 0;
+}
+.category-selector ul.select-box {
+ height: 150px;
+ width: 235px;
+ overflow: auto;
+ border: #ccc 3px solid;
+}
+.category-selector td {
+ vertical-align: top;
+}
+.category-selector li {
+ position: relative;
+ color: #707070;
+}
+.category-selector li.tree:after {
+ content: ">>";
+ position: absolute;
+ right: 5px;
+ font-weight: bold;
+}
+.category-selector li.selected.tree:after {
+ color: #C09853;
+}
+.category-selector th {
+ color: #707070;
+ font-style: italic;
+ font-size: 16px;
+ font-weight: normal;
+ padding-top: 5px;
+ text-align: left;
+}
+.question-page .category-selector ul.select-box {
+ width: 217px;
+}
+.question-page .category-selector ul.select-box input {
+ width: 95px;
+}
+.question-page .tag-editor {
+ width: 660px;
+ margin-left: 0;
+}
+.editor-status {
+ float: right;
+ margin: 7px 350px 0 0;
+ font-weight: bold;
+}
+.editor-status span {
+ display: none;
+}
+/* tag editor */
+.tag-editor {
+ height: 64px;
+ border: #ccc 3px solid;
+ padding-left: 8px;
+}
+.tag-editor ul.tags {
+ margin: 0;
+}
+.tag-editor ul.tags li {
+ margin-top: 8px;
+ height: 13px;
+}
+.tag-editor input.new-tags-input {
+ border-style: none;
+ font-size: 15px;
+ font-color: #707070;
+ line-height: 16px;
+ margin-top: 9px;
+}
/* fixes for bootstrap */
.caret {
margin-bottom: 7px;
diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less
index 2b6a45bd..a03780c3 100644
--- a/askbot/skins/default/media/style/style.less
+++ b/askbot/skins/default/media/style/style.less
@@ -39,6 +39,13 @@ input, select {
margin-left:0px;
}
+input[type="text"].prompt,
+input[type="password"].prompt,
+input.tipped-input.blank {
+ font-style: italic;
+ color: @info-text;
+}
+
textarea:focus, input:focus{
outline: none;
}
@@ -306,20 +313,34 @@ body.user-messages {
}
#navTags{
- .sprites(-50px,-5px)
+ .sprites(0px,-95px)
}
#navUsers{
- .sprites(-125px,-5px)
+ .sprites(3px,-132px)
}
#navGroups{
- .sprites(-125px,-5px)
+ .sprites(3px,-132px)
+ }
+
+ a.group-name {
+ padding: 0px;
+ }
+
+ span.dropdown:hover ul.dropdown-menu {
+ display: block;
+ }
+
+ .dropdown {
+ float:left;
}
- #navBadges{
- .sprites(-210px,-5px)
+ .dropdown-menu{
+ /*top: 120%;*/
+ left: 7%;
}
+
}
#header.with-logo #userToolsNav {
@@ -384,6 +405,25 @@ body.user-messages {
}
}
+.validate-email-page {
+ label {
+ color: @info-text;
+ line-height: 1.35;
+ display: block;
+ margin: 10px 0;
+ }
+ #validation-code {
+ padding-left:5px;
+ border:#cce6ec 3px solid;
+ height:25px;
+ font-size: 14px;
+ width: 200px;
+ }
+ form {
+ margin-bottom: 30px;
+ }
+}
+
#searchBar { /* Main search form , check widgets/search_bar.html */
display: inline-block;
background-color: #fff;
@@ -512,6 +552,7 @@ body.anon {
margin-bottom: 4px;
color: @info-text;
font-family:@main-font;
+ font-size: 14px;
}
p.info-box-follow-up-links {
@@ -545,6 +586,10 @@ body.anon {
background: #eceeeb url(../images/contributorsback.png) no-repeat center left;
}
+ form {
+ margin: 0px;
+ }
+
label {
color: @info-text;
font-size:15px;
@@ -1430,6 +1475,7 @@ ul#related-tags li {
}
table.proxy-user-info {
border-spacing: 0px;
+ width: 100%;
.form-item {
float: left;
@@ -1455,6 +1501,50 @@ ul#related-tags li {
.rounded-corners(4px);
}
+.share-input-col {
+ width: 160px;
+ text-align: center;
+}
+
+.add-everyone-group {
+ text-align: center;
+ margin: auto;
+ display: block;
+ padding: 0 10px;
+ height: 25px;
+}
+
+.add-groups:hover {
+ .button-style-hover;
+}
+
+#id_user,
+#id_user_author {
+ border:#cce6ec 3px solid;
+ height:25px;
+ padding-left:5px;
+ width:395px;
+ font-size:14px;
+}
+
+.groups-input,
+.users-input {
+ width:152px;
+ padding-left:5px;
+ border:#c9c9b5 1px solid;
+ height:25px;
+ font-size: 14px;
+}
+
+.add-groups,
+.add-users {
+ border:0;
+ font-weight:bold;
+ margin-top:-2px;
+ .button-style(27px, 14px);
+ .rounded-corners(4px);
+}
+
.add-everyone-group {
text-align: center;
margin: auto;
@@ -3638,6 +3728,15 @@ img.group-logo {
}
}
+.groups-page #groups-list {
+ th, td {
+ padding-right: 20px;
+ }
+ th {
+ font-weight: bold;
+ }
+}
+
#reject-edit-modal {
input, textarea {
width: 514px;
@@ -3764,3 +3863,24 @@ textarea.tipped-input {
box-shadow: none;
}
}
+
+/* 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/templates/embed/ask_by_widget.html b/askbot/skins/default/templates/embed/ask_by_widget.html
new file mode 100644
index 00000000..f5c2a0a2
--- /dev/null
+++ b/askbot/skins/default/templates/embed/ask_by_widget.html
@@ -0,0 +1,40 @@
+{% extends "widget_base.html" %}
+{% import "macros.html" as macros %}
+{% block forestyle %}
+ <style type="text/css" media="screen">
+ #editor {
+ display: block;
+ min-height: 200px;
+ width: 100%;
+ margin: 0;
+ border: #CCE6EC 2px solid;
+ }
+
+ #id_title{
+ width: 100%;
+ font-size: 130%;
+ border: #CCE6EC 3px solid;
+ }
+
+ #submit{
+ float: right;
+ font-size: 130%;
+ }
+ </style>
+{%endblock%}
+
+{%block body%}
+<form action="." method="POST" accept-charset="utf-8">
+ {% csrf_token %}
+ <p>{{form.title}}</p>
+ {{form.text}}
+ {% if form.ask_anonymously %}
+ <p>{{form.ask_anonymously.label_tag()}}: {{form.ask_anonymously}}</p>
+ {%endif%}
+ <input type="submit" value="Ask your question" id="submit" />
+</form>
+{{form.errors}}
+{%endblock%}
+{% block endjs %}
+{% endblock %}
+
diff --git a/askbot/skins/default/templates/embed/ask_widget_complete.html b/askbot/skins/default/templates/embed/ask_widget_complete.html
new file mode 100644
index 00000000..580c1f94
--- /dev/null
+++ b/askbot/skins/default/templates/embed/ask_widget_complete.html
@@ -0,0 +1,8 @@
+{% extends "widget_base.html" %}
+{% block forestyle %}
+{%endblock%}
+
+{%block body%}
+<a href="{{question_url}}" target="_blank" >Question posted</a>
+{%endblock%}
+
diff --git a/askbot/skins/default/templates/embed/askbot_widget.css b/askbot/skins/default/templates/embed/askbot_widget.css
new file mode 100755
index 00000000..573a07fd
--- /dev/null
+++ b/askbot/skins/default/templates/embed/askbot_widget.css
@@ -0,0 +1,25 @@
+#{{variable_name}} {
+ visibility: hidden;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width:100%;
+ height:100%;
+ text-align:center;
+ z-index: 99999;
+}
+
+#{{variable_name}} div{
+ width:600px;
+ height:400px;
+ margin: 100px auto;
+ background-color: #fff;
+ border:1px solid #000;
+ padding:15px;
+ text-align:right;
+}
+
+#{{variable_name}} iframe{
+ width:600px;
+ height:400px;
+}
diff --git a/askbot/skins/default/templates/embed/askbot_widget.js b/askbot/skins/default/templates/embed/askbot_widget.js
new file mode 100755
index 00000000..57cead90
--- /dev/null
+++ b/askbot/skins/default/templates/embed/askbot_widget.js
@@ -0,0 +1,73 @@
+var {{variable_name}} = {
+ element_id: "{{variable_name}}",
+ widgetToggle: function() {
+ element = document.getElementById({{variable_name}}.element_id);
+ element.style.visibility = (element.style.visibility == "visible") ? "hidden" : "visible";
+ },
+ toHtml: function() {
+ var html = {{variable_name}}.createButton();
+ var link = document.createElement('link');
+ link.setAttribute("rel", "stylesheet");
+ //link.setAttribute("href", 'http://{{host}}{{"/style/askbot-modal.css"|media}}');
+ link.setAttribute("href", 'http://{{host}}{%url render_ask_widget_css widget.id%}');
+
+ //creating the div
+ var motherDiv = document.createElement('div');
+ motherDiv.setAttribute("id", {{variable_name}}.element_id);
+ console.log(motherDiv);
+
+ var containerDiv = document.createElement('div');
+ motherDiv.appendChild(containerDiv);
+
+ {%if widget.outer_style %}
+ outerStyle = document.createElement('style');
+ outerStyle.innerText = "{{widget.outer_style}}";
+ motherDiv.appendChild(outerStyle);
+ {%endif%}
+
+ var closeButton = document.createElement('a');
+ closeButton.setAttribute('href', '#');
+ closeButton.setAttribute('id', 'AskbotModalClose');
+ closeButton.setAttribute('onClick', '{{variable_name}}.widgetToggle();');
+ closeButton.innerText = 'Close';
+
+ containerDiv.appendChild(closeButton);
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', 'http://{{host}}{% url ask_by_widget widget.id %}');
+
+ containerDiv.appendChild(iframe);
+
+ var body = document.getElementsByTagName('body')[0];
+ if (body){
+ console.log(body.firstChild);
+ body.insertBefore(motherDiv, body.firstChild);
+ body.insertBefore(link, body.firstChild);
+ }
+ },
+ createButton: function() {
+ var label="{{widget.title}}"; //TODO: add to the model
+ var buttonDiv = document.createElement('div');
+ buttonDiv.setAttribute('id', "AskbotAskButton");
+
+ var closeButton = document.createElement('button');
+ closeButton.setAttribute('onClick', '{{variable_name}}.widgetToggle();');
+ closeButton.innerText = label;
+
+ buttonDiv.appendChild(closeButton);
+
+ return buttonDiv;
+ }
+};
+
+previous_function = window.onload;
+var onload_functions = function(){
+ if (previous_function){
+ previous_function();
+ }
+ {{variable_name}}.toHtml();
+}
+
+console.log(onload_functions);
+window.onload = onload_functions();
+document.write({{variable_name}}.createButton().outerHTML);
diff --git a/askbot/skins/default/templates/embed/delete_widget.html b/askbot/skins/default/templates/embed/delete_widget.html
new file mode 100644
index 00000000..ed80c537
--- /dev/null
+++ b/askbot/skins/default/templates/embed/delete_widget.html
@@ -0,0 +1,14 @@
+{% extends "one_column_body.html" %}
+<!-- create_ask_widget.html -->
+{% block title %}Delete {{widget_name|capitalize}} Widget{% endblock %}
+{% block content %}
+<h1>Are you sure that you cant to delete this {{widget_name|capitalize}}Widget?</h1>
+<br/>
+<strong>Warning: This could break the widgets on sites that currently use this widget please make sure that you don't use the widget in other sites</strong>
+<form action="." method="POST">
+ <p><input type='submit' value='Delete' /> <a href="{% url list_widgets widget_name %}">Go Back</a></p>
+</form>
+{% endblock %}
+{% block endjs %}
+{% endblock %}
+
diff --git a/askbot/skins/default/templates/embed/list_widgets.html b/askbot/skins/default/templates/embed/list_widgets.html
new file mode 100644
index 00000000..83de5871
--- /dev/null
+++ b/askbot/skins/default/templates/embed/list_widgets.html
@@ -0,0 +1,45 @@
+{% extends "two_column_body.html" %}
+<!-- create_ask_widget.html -->
+{% block title %}{{widget_name|capitalize}} widget list{% endblock %}
+{% block content %}
+ <h1>{{widget_name|capitalize}} widget list</h1>
+
+<table border="0">
+ <tr>
+ <th>Widget Title </th>
+ <th>Code</th>
+ <th>Actions</th>
+ </tr>
+ {% if widget_name == 'ask' %}
+ {%for widget in widgets%}
+ <tr>
+ <td>{{widget.title}}</td>
+ <td> &lt;script type="text/javascript" src="http://{{request.get_host()}}{% url render_ask_widget widget.id%}" &gt;&lt;/script&gt;</td>
+ <td><a href="{% url edit_widget widget_name, widget.id %}">Edit</a> | <a href="{% url delete_widget widget_name, widget.id %}">Delete</a></td>
+ </tr>
+ {%endfor%}
+ {%else%}
+ {%for widget in widgets%}
+ <tr>
+ <td>{{widget.title}}</td>
+ <td> &lt;iframe src="http://{{request.get_host()}}{% url question_widget widget.id%}" &gt; &lt;/iframe&gt;</td>
+ <td><a href="{% url edit_widget widget_name, widget.id %}">Edit</a> | <a href="{% url delete_widget widget_name, widget.id %}">Delete</a></td>
+ </tr>
+ {%endfor%}
+
+ {%endif%}
+</table>
+
+{% endblock %}
+{% block endjs %}
+{% endblock %}
+
+{% block sidebar %}
+<div class="box">
+ <h2>{% trans %}How to use?{% endtrans %}</h2>
+ <p>{% trans %}
+ Just copy the &lt;script&gt; tag provided and paste it in the site where you wan to put it.
+ {%endtrans%}
+ </p>
+ </div>
+{% endblock %}
diff --git a/askbot/skins/default/templates/question_widget.html b/askbot/skins/default/templates/embed/question_widget.html
index 9d32294b..92e29aa0 100644
--- a/askbot/skins/default/templates/question_widget.html
+++ b/askbot/skins/default/templates/embed/question_widget.html
@@ -3,19 +3,18 @@
<html>
<head>
<style type="text/css">
- {{settings.QUESTIONS_WIDGET_CSS|safe}}
+ {{widget.style|safe}}
</style>
</head>
<body>
- {{settings.QUESTIONS_WIDGET_HEADER|safe}}
+ {{widget.title|safe}}
<div id="container">
<ul>
{% for thread in threads %}
- <li><a href="{{settings.APP_URL|strip_path}}{{ thread.get_absolute_url() }}">
+ <li><a href="{{settings.APP_URL|strip_path}}{{ thread.get_absolute_url() }}" target="_blank">
{{ thread.title|escape }}</a></li>
{% endfor %}
</ul>
</div>
- {{settings.QUESTIONS_WIDGET_FOOTER|safe}}
</body>
</html>
diff --git a/askbot/skins/default/templates/embed/widget_form.html b/askbot/skins/default/templates/embed/widget_form.html
new file mode 100644
index 00000000..65128d8e
--- /dev/null
+++ b/askbot/skins/default/templates/embed/widget_form.html
@@ -0,0 +1,23 @@
+{% extends "one_column_body.html" %}
+<!-- create_ask_widget.html -->
+{% block title %}{% trans %}{{action}} an {{widget_name}} widget{% endtrans %}{% endblock %}
+{% block content %}
+<h1 class="section-title">{% trans %}{{action}} an {{widget_name}} widget{% endtrans %}</h1>
+{#% if form.non_field_errors() %}
+ {{ form.non_field_errors() }}
+{% endif %#}
+<form method="post">
+ <table>
+ {{ form.as_table() }}
+ <tr>
+ <td colspan="2" style="text-align: center">
+ <input type="submit" class="submit" value={% trans %}Save{% endtrans %} />
+ </td>
+ </tr>
+ </table>
+</form>
+
+{% endblock %}
+{% block endjs %}
+{% endblock %}
+
diff --git a/askbot/skins/default/templates/embed/widgets.html b/askbot/skins/default/templates/embed/widgets.html
new file mode 100644
index 00000000..767ebc2c
--- /dev/null
+++ b/askbot/skins/default/templates/embed/widgets.html
@@ -0,0 +1,36 @@
+{% extends "one_column_body.html" %}
+<!-- template badges.html -->
+{% block title %}{% spaceless %}{% trans %}Widgets{% endtrans %}{% endspaceless %}{% endblock %}
+{% block content %}
+<h1 class="section-title">{% trans %}Widgets{% endtrans %}</h1>
+<p>
+</p>
+<table>
+ <thead>
+ <th colspan="3">
+ {% trans %}Create and embed widgets into your sites, here a list of available widgets.{% endtrans %}
+ </th>
+ </thead>
+ <tbody>
+ <tr>
+ <td>{% trans %}Ask a question{% endtrans %}</td>
+ <td><a href="{% url create_widget 'ask' %}">{% trans %}create{% endtrans %}</a></td>
+ <td>
+ {% if ask_widgets > 0 %}
+ <a href="{% url list_widgets 'ask' %}">{% trans %}view list{% endtrans %}</a>
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <td>{% trans %}List of questions{% endtrans %}</td>
+ <td><a href="{% url create_widget 'question' %}">{% trans %}create{% endtrans %}</a></td>
+ <td>
+ {% if question_widgets > 0 %}
+ <a href="{% url list_widgets 'question' %}">{% trans %}view list{% endtrans %}</a>
+ {% endif %}
+ </td>
+ </tr>
+ </tbody>
+</table>
+{% endblock %}
+<!-- end template badges.html -->
diff --git a/askbot/skins/default/templates/groups.html b/askbot/skins/default/templates/groups.html
index 2499ac9f..9c7dac3c 100644
--- a/askbot/skins/default/templates/groups.html
+++ b/askbot/skins/default/templates/groups.html
@@ -26,11 +26,21 @@
</p>
{% endif %}
<table id="groups-list">
- {% for group in groups %}
- <tr>
- {{ macros.user_group(group, groups_membership_info[group.id]) }}
- </tr>
- {% endfor %}
+ <thead>
+ <th>{% trans %}Group{% endtrans %}</th>
+ <th>{% trans %}Number of members{% endtrans %}</th>
+ <th>{% trans %}Description{% endtrans %}</th>
+ </thead>
+ <tbody>
+ {% for group in groups %}
+ <tr>
+ {{ macros.user_group(
+ group, groups_membership_info[group.id], show_count=True
+ )
+ }}
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
{% endblock %}
{% block endjs %}
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index ef5be144..122d90c8 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -207,25 +207,34 @@ poor design of the data or methods on data objects #}
</ul>
{%- endmacro -%}
-{%- macro user_group(group, membership_info) -%}
+{%- macro user_group_link(group) -%}
+ <a class="group-name"
+ href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}"
+ >{{ group.name|escape }}</a>
+{%- endmacro -%}
+
+{%- macro user_group(group, membership_info, show_count=False) -%}
<td>
- <a class="group-name"
- href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}"
- >{{ group.name|replace('-', ' ')|escape }}</a><br/>
- <span class="group-description">
- {% if group.tag_wiki %}
- {{ group.tag_wiki.summary }}
- {% endif %}
- </span>
- {% if membership_info %}
- <br/>
- {{ group_join_button(
- group_id = group.id,
- can_join = membership_info['can_join'],
- is_member = membership_info['is_member']
- )
- }}
- {% endif %}
+ {{ user_group_link(group) }}
+ </td>
+ {% if show_count %}
+ <td>{{ group.users_count }}</td>
+ {% endif %}
+ <td>
+ <span class="group-description">
+ {% if group.tag_wiki %}
+ {{ group.tag_wiki.summary }}
+ {% endif %}
+ </span>
+ {% if membership_info %}
+ <br/>
+ {{ group_join_button(
+ group_id = group.id,
+ can_join = membership_info['can_join'],
+ is_member = membership_info['is_member']
+ )
+ }}
+ {% endif %}
</td>
{%- endmacro -%}
diff --git a/askbot/skins/default/templates/meta/html_head_stylesheets.html b/askbot/skins/default/templates/meta/html_head_stylesheets.html
index 5f83cea1..cadc69e0 100644
--- a/askbot/skins/default/templates/meta/html_head_stylesheets.html
+++ b/askbot/skins/default/templates/meta/html_head_stylesheets.html
@@ -1,3 +1,6 @@
+{%if settings.GROUPS_ENABLED%}
+<link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" />
+{% endif %}
{% if settings.ASKBOT_CSS_DEVEL == False %}
<link href="{{"/style/style.css"|media }}" rel="stylesheet" type="text/css" />
diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html
index 06e29d8f..28de61e7 100644
--- a/askbot/skins/default/templates/question.html
+++ b/askbot/skins/default/templates/question.html
@@ -8,6 +8,7 @@
{% block forestyle %}
<link rel="canonical" href="{{settings.APP_URL|strip_path}}{{question.get_absolute_url()}}" />
<link rel="stylesheet" type="text/css" href="{{'/js/wmd/wmd.css'|media}}" />
+ <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" />
{% endblock %}
{% block forejs %}
<script type="text/javascript">
diff --git a/askbot/skins/default/templates/question/javascript.html b/askbot/skins/default/templates/question/javascript.html
index 4399c823..bc640623 100644
--- a/askbot/skins/default/templates/question/javascript.html
+++ b/askbot/skins/default/templates/question/javascript.html
@@ -27,6 +27,7 @@
{% endif %}
askbot['settings']['tagSource'] = '{{ settings.TAG_SOURCE }}';
</script>
+<script type="text/javascript" src='{{"/bootstrap/js/bootstrap.js"|media}}'></script>
{% if settings.EDITOR_TYPE == 'markdown' %}
<script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script>
<script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script>
diff --git a/askbot/skins/default/templates/question/new_answer_form.html b/askbot/skins/default/templates/question/new_answer_form.html
index 260ffb95..76772abf 100644
--- a/askbot/skins/default/templates/question/new_answer_form.html
+++ b/askbot/skins/default/templates/question/new_answer_form.html
@@ -1,6 +1,5 @@
<form
id="fmanswer"
- {#% if user == question.author %}style="display:none"{% endif %#}
action="{% url answer question.id %}"
method="post"
>{% csrf_token %}
diff --git a/askbot/skins/default/templates/question/sidebar.html b/askbot/skins/default/templates/question/sidebar.html
index ef99e988..f595634b 100644
--- a/askbot/skins/default/templates/question/sidebar.html
+++ b/askbot/skins/default/templates/question/sidebar.html
@@ -51,36 +51,21 @@
<div class="clearfix"></div>
<div class="box sharing-widget">
{% if thread.is_private() %}
- <h2>{% trans %}Share{% endtrans %}</h2>
- <label for="share_user_name">{% trans %}Share with users{% endtrans %}</label>
- <p>{% trans %}Yourself{% endtrans %}</p>
- {% for group in thread.groups.all() %}
- {% if group.name.startswith('_internal_') %}
- <p>{{ group.created_by.get_profile_link() }}<p>
- {% endif %}
- {% endfor %}
+ <h2>{% trans %}Invite{% endtrans %}</h2>
+ <p style="margin: 16px 0"
+ >Invite others to help answer this question</p>
<form action="{% url share_question_with_user %}" method="post">{% csrf_token %}
<input id="share_user_name" type="text" class="groups-input" name="recipient_name" />
<input type="hidden" name="thread_id" value="{{ thread.id }}"/>
<input type="submit" class="add-groups" value="{% trans %}add{% endtrans %}"/>
</form>
- {#% if thread.groups.count() %}
- <label for="group_name">{% trans %}Shared with groups:{% endtrans %}</label>
- {% else %}
- <label for="group_name">{% trans %}Share with a group{% endtrans %}</label>
- {% endif %#}
- <label for="share_group_name">{% trans %}Share with groups{% endtrans %}</label>
- {% for group in thread.groups.all() %}
- {% if not group.name.startswith('_internal_') %}{# todo: fix this hack #}
- <p>{{ macros.user_group(group) }}</p>
- {% endif %}
- {% endfor %}
+ <p class="share-input-col">{% trans %}- or -{% endtrans %}</p>
<form action="{% url share_question_with_group %}" method="post">{% csrf_token %}
<input id="share_group_name" type="text" class="groups-input" name="recipient_name" />
<input type="hidden" name="thread_id" value="{{ thread.id }}"/>
<input type="submit" class="add-groups" value="{% trans %}add{% endtrans %}"/>
</form>
- <p style="text-align: center">{% trans %}or{% endtrans %}</p>
+ <p class="share-input-col">{% trans %}- or -{% endtrans %}</p>
<form action="{% url share_question_with_group %}" method="post">{% csrf_token %}
<input
type="hidden"
@@ -88,13 +73,65 @@
value="{{ settings.GLOBAL_GROUP_NAME }}"
/>
<input type="hidden" name="thread_id" value="{{ thread.id }}"/>
+ <p class="share-input-col">
<input
type="submit"
class="add-groups add-everyone-group"
value="{% trans %}share with everyone{% endtrans %}"
/>
+ </p>
</form>
+ {% set shared_users_count = sharing_info['users'].count() %}
+ {% set shared_groups_count = sharing_info['groups'].count() %}
+
+ {% if shared_users_count or shared_groups_count %}
+ <p
+ style="margin:16px 0 4px 0"
+ >{% trans %}This question is currently shared only with:{% endtrans %}</p>
+ {% endif %}
+ <h3>{% trans %}Individual users{% endtrans %}</h3>
+ {% set comma = joiner(',') %}
+ {{ comma() }}
+ <p>
+ <a href="{{ request.user.get_profile_url() }}">
+ {% trans %}You{% endtrans -%}
+ </a>{%- if shared_users_count -%}
+ {%- for user in sharing_info['users'] %}{{ comma() }}
+ {{ user.get_profile_link() }}
+ {%- endfor -%}
+ {% endif -%}
+ {%- if sharing_info['more_users_count'] > 0 %}
+ {% trans %}and{% endtrans %}
+ <a
+ class="see-related-users"
+ data-url="{% url get_thread_shared_users %}"
+ data-thread-id="{{ thread.id }}"
+ >{% trans
+ more_count=sharing_info['more_users_count']
+ %}{{ more_count }} more{% endtrans %}
+ </a>
+ {% endif %}
+ </p>
+
+ {% if shared_groups_count %}
+ <h3>{% trans %}Groups{% endtrans %}</h3>
+ <p>
+ {% set comma = joiner(',') %}
+ {%- for group in sharing_info['groups'] -%}{{ comma() }}
+ {{ macros.user_group_link(group) }}
+ {%- endfor -%}
+ {% if sharing_info['more_groups_count'] > 0 %}
+ {% trans %}and{% endtrans %}
+ <a
+ class="see-related-groups"
+ data-url="{% url get_thread_shared_groups %}"
+ data-thread-id="{{ thread.id }}"
+ >{% trans more_count=sharing_info['more_groups_count'] %}{{ more_count }} more{% endtrans %}
+ </a>
+ {% endif %}
+ </p>
+ {% endif %}
{% else %}
<h2>{% trans %}Public thread{% endtrans %}</h2>
<p>{% trans site_name=settings.APP_SHORT_NAME %}This thread is public, all members of {{ site_name }} can read this page.{% endtrans %}</p>
diff --git a/askbot/skins/default/templates/widget_base.html b/askbot/skins/default/templates/widget_base.html
index fd5e739e..d23c07bc 100644
--- a/askbot/skins/default/templates/widget_base.html
+++ b/askbot/skins/default/templates/widget_base.html
@@ -3,6 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
{% spaceless %}
<head>
+ {% include "meta/html_head_javascript.html" %}
{% include "meta/bottom_scripts.html" %}
{% block before_css %}{% endblock %}
{% block forestyle %}{% endblock %}
diff --git a/askbot/skins/default/templates/widgets/groups_list.html b/askbot/skins/default/templates/widgets/groups_list.html
new file mode 100644
index 00000000..0669f34f
--- /dev/null
+++ b/askbot/skins/default/templates/widgets/groups_list.html
@@ -0,0 +1,4 @@
+{% import "macros.html" as macros %}
+{% for group in groups %}
+ <p>{{ macros.user_group(group) }}</p>
+{% endfor %}
diff --git a/askbot/skins/default/templates/widgets/meta_nav.html b/askbot/skins/default/templates/widgets/meta_nav.html
index 1b28c787..659780c4 100644
--- a/askbot/skins/default/templates/widgets/meta_nav.html
+++ b/askbot/skins/default/templates/widgets/meta_nav.html
@@ -1,20 +1,28 @@
+{% import "macros.html" as macros%}
<a
id="navTags"
href="{% url tags %}"
{% if active_tab == 'tags' %}class="on"{% endif %}
>{% trans %}tags{% endtrans %}</a>
+{% if settings.GROUPS_ENABLED %}
+<span class="dropdown">
+<a
+ id="navGroups" class='{% if active_tab == 'groups' %}"on"{% endif %}'
+ href="{% url groups %}" data-target="#" >
+ {% trans %}people & groups{% endtrans %}
+</a>
+<ul id="groups-dropdown" class="dropdown-menu" role="menu" aria-labelledby="navGroups">
+ {%for group in group_list%}
+ <li>{{ macros.user_group_link(group) }}</li>
+ {%endfor%}
+</ul>
+</span>
+{%else%}
<a
id="navUsers"
href="{% url users %}"
{% if active_tab == 'users' %}class="on"{% endif %}
>{% trans %}users{% endtrans %}</a>
-{% if settings.GROUPS_ENABLED %}
-<a
- id="navGroups"
- href="{% url groups %}"
- {% if active_tab == 'groups' %}class="on"{% endif %}
->{% trans %}groups{% endtrans %}
-</a>
{% endif %}
{% if settings.BADGES_MODE == 'public' %}
<a
diff --git a/askbot/skins/default/templates/widgets/user_list.html b/askbot/skins/default/templates/widgets/user_list.html
index 2e78bd0c..52cf8bd4 100644
--- a/askbot/skins/default/templates/widgets/user_list.html
+++ b/askbot/skins/default/templates/widgets/user_list.html
@@ -1,4 +1,4 @@
-{% from "macros.html" import gravatar %}
+{% import "macros.html" as macros %}
<div class="userList">
<table class="list-table">
<tr>
@@ -6,10 +6,10 @@
{% for user in users %}
<div class="user">
<ul>
- <li class="thumb">{{ gravatar(user, 32) }}</li>
- <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username|escape}}</a>{{ user_country_flag(user) }}</li>
+ <li class="thumb">{{ macros.gravatar(user, 32) }}</li>
+ <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username|escape}}</a>{{ macros.user_country_flag(user) }}</li>
<li>{{
- user_score_and_badge_summary(
+ macros.user_score_and_badge_summary(
user,
karma_mode = karma_mode,
badges_mode = badges_mode
diff --git a/askbot/skins/default/templates/widgets/user_navigation.html b/askbot/skins/default/templates/widgets/user_navigation.html
index 717cd7ee..0173e974 100644
--- a/askbot/skins/default/templates/widgets/user_navigation.html
+++ b/askbot/skins/default/templates/widgets/user_navigation.html
@@ -21,5 +21,6 @@
{% endif %}
{% if request.user.is_authenticated() and request.user.is_administrator() %}
<a href="{% url site_settings %}">{% trans %}settings{% endtrans %}</a>
+ <a href="{% url widgets %}">{% trans %}widgets{% endtrans %}</a>
{% endif %}
<a href="{% url "help" %}" title="{% trans %}help{% endtrans %}">{% trans %}help{% endtrans %}</a>
diff --git a/askbot/skins/loaders.py b/askbot/skins/loaders.py
index aa3188e9..c1367fe5 100644
--- a/askbot/skins/loaders.py
+++ b/askbot/skins/loaders.py
@@ -115,14 +115,20 @@ def get_template(template, request = None):
skin.set_language(request.LANGUAGE_CODE)
return skin.get_template(template)
+def render_into_skin_as_string(template, data, request):
+ context = RequestContext(request, data)
+ template = get_template(template, request)
+ return template.render(context)
+
def render_into_skin(template, data, request, mimetype = 'text/html'):
"""in the future this function will be able to
switch skin depending on the site administrator/user selection
right now only admins can switch
"""
- context = RequestContext(request, data)
- template = get_template(template, request)
- return HttpResponse(template.render(context), mimetype = mimetype)
+ return HttpResponse(
+ render_into_skin_as_string(template, data, request),
+ mimetype=mimetype
+ )
def render_text_into_skin(text, data, request):
context = RequestContext(request, data)
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 0fec6d5f..33c33da4 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -18,6 +18,7 @@ from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from askbot.utils.loading import load_module
from askbot.utils.functions import enumerate_string_list
+from askbot.utils.url_utils import urls_equal
from urlparse import urlparse
PREAMBLE = """\n
@@ -512,6 +513,66 @@ def test_custom_user_profile_tab():
footer = 'Please carefully read about adding a custom user profile tab.'
print_errors(errors, header = header, footer = footer)
+def get_tinymce_sample_config():
+ """returns the sample configuration for TinyMCE
+ as string"""
+ askbot_root = askbot.get_install_directory()
+ file_path = os.path.join(
+ askbot_root, 'setup_templates', 'tinymce_sample_config.py'
+ )
+ config_file = open(file_path, 'r')
+ sample_config = config_file.read()
+ config_file.close()
+ return sample_config
+
+def test_tinymce():
+ """tests the tinymce editor setup"""
+ errors = list()
+ if 'tinymce' not in django_settings.INSTALLED_APPS:
+ errors.append("add 'tinymce', to the INSTALLED_APPS")
+
+ required_attrs = (
+ 'TINYMCE_COMPRESSOR',
+ 'TINYMCE_JS_ROOT',
+ 'TINYMCE_URL',
+ 'TINYMCE_DEFAULT_CONFIG'
+ )
+
+ missing_attrs = list()
+ for attr in required_attrs:
+ if not hasattr(django_settings, attr):
+ missing_attrs.append(attr)
+
+ if missing_attrs:
+ errors.append('add missing settings: %s' % ', '.join(missing_attrs))
+
+ #check compressor setting
+ compressor_on = getattr(django_settings, 'TINYMCE_COMPRESSOR', False)
+ if compressor_on is False:
+ errors.append('add line: TINYMCE_COMPRESSOR = True')
+
+ #check js root setting
+ js_root = getattr(django_settings, 'TINYMCE_JS_ROOT', '')
+ relative_js_path = 'common/media/js/tinymce/'
+ expected_js_root = os.path.join(django_settings.STATIC_ROOT, relative_js_path)
+ if os.path.normpath(js_root) != os.path.normpath(expected_js_root):
+ js_root_template = "add line: TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, '%s')"
+ errors.append(js_root_template % relative_js_path)
+
+ #check url setting
+ url = getattr(django_settings, 'TINYMCE_URL', '')
+ expected_url = django_settings.STATIC_URL + relative_js_path
+ if urls_equal(url, expected_url) is False:
+ js_url_template = "add line: TINYMCE_URL = STATIC_URL + '%s'"
+ errors.append(js_url_template % relative_js_path)
+
+ if errors:
+ header = 'Please add the tynymce editor configuration ' + \
+ 'to your settings.py file.'
+ footer = 'You might want to use this sample configuration ' + \
+ 'as template:\n\n' + get_tinymce_sample_config()
+ print_errors(errors, header=header, footer=footer)
+
def run_startup_tests():
"""function that runs
all startup tests, mainly checking settings config so far
@@ -526,6 +587,7 @@ def run_startup_tests():
test_middleware()
test_celery()
#test_csrf_cookie_domain()
+ test_tinymce()
test_staticfiles()
test_avatar()
settings_tester = SettingsTester({
diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py
index 056320f5..6e43940e 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -18,3 +18,4 @@ from askbot.tests.reply_by_email_tests import *
from askbot.tests.widget_tests import *
from askbot.tests.category_tree_tests import CategoryTreeTests
from askbot.tests.user_model_tests import UserModelTests
+from askbot.tests.utils_tests import *
diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py
index b66eadcc..0ed4b343 100644
--- a/askbot/tests/badge_tests.py
+++ b/askbot/tests/badge_tests.py
@@ -318,7 +318,7 @@ class BadgeTests(AskbotTestCase):
def test_guru_badge1(self):
self.assert_guru_badge_works('upvote_answer')
- def test_guru_badge1(self):
+ def test_guru_badge2(self):
self.assert_guru_badge_works('accept_best_answer')
def test_necromancer_badge(self):
diff --git a/askbot/tests/form_tests.py b/askbot/tests/form_tests.py
index be88cf39..90f4f4f2 100644
--- a/askbot/tests/form_tests.py
+++ b/askbot/tests/form_tests.py
@@ -374,11 +374,12 @@ class AskWidgetFormTests(AskbotTestCase):
self.bad_data = {'title': ''}
def test_valid_input(self):
- form_object = self.form(self.good_data)
+ form_object = self.form(include_text=False, data=self.good_data)
+ print form_object.errors
self.assertTrue(form_object.is_valid())
- form_object = self.form(self.good_data_anon)
+ form_object = self.form(include_text=False, data=self.good_data_anon)
self.assertTrue(form_object.is_valid())
def test_invalid_input(self):
- form_object = self.form(self.bad_data)
+ form_object = self.form(False, data=self.bad_data)
self.assertFalse(form_object.is_valid())
diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py
index e3e699a7..f3e10c1e 100644
--- a/askbot/tests/page_load_tests.py
+++ b/askbot/tests/page_load_tests.py
@@ -69,7 +69,7 @@ class PageLoadTestCase(AskbotTestCase):
self.old_cache = cache.cache
#Disable caching (to not interfere with production cache,
#not sure if that's possible but let's not risk it)
- cache.cache = DummyCache('', {})
+ cache.cache = DummyCache('', {})
def tearDown(self):
cache.cache = self.old_cache # Restore caching
@@ -311,12 +311,12 @@ class PageLoadTestCase(AskbotTestCase):
status_code=status_code,
template='users.html'
)
- self.try_url(
- 'widget_questions',
- status_code = status_code,
- data={'tags': 'tag-1-0'},
- template='question_widget.html',
- )
+ #self.try_url(
+ # 'widget_questions',
+ # status_code = status_code,
+ # data={'tags': 'tag-1-0'},
+ # template='question_widget.html',
+ # )
#todo: really odd naming conventions for sort methods
self.try_url(
'users',
@@ -519,6 +519,11 @@ class PageLoadTestCase(AskbotTestCase):
template='user_profile/user_inbox.html',
)
+ def test_user_page_with_groups_enabled(self):
+ askbot_settings.GROUPS_ENABLED = True
+ self.try_url('users', status_code=302)
+ askbot_settings.GROUPS_ENABLED = False
+ self.try_url('users', status_code=200)
class AvatarTests(AskbotTestCase):
@@ -626,4 +631,4 @@ class CommandViewTests(AskbotTestCase):
tag1 = self.create_tag('tag1')
response = self.client.get(reverse('load_tag_wiki_text'))
self.assertEqual(response.status_code, 400)#bad request
-
+
diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py
index dd46e31d..99f43677 100644
--- a/askbot/tests/utils.py
+++ b/askbot/tests/utils.py
@@ -5,8 +5,8 @@ from functools import wraps
from askbot import models
def create_user(
- username = None,
- email = None,
+ username = None,
+ email = None,
notification_schedule = None,
date_joined = None,
status = 'a',
@@ -25,13 +25,13 @@ def create_user(
* 'q_sel' - questions that user decides to follow
* 'm_and_c' - comments and mentions of user anywhere
- and values as keys in
+ and values as keys in
:attr:`~askbot.models.EmailFeedSetting.FEED_TYPES`:
* 'i' - instantly
* 'd' - daily
* 'w' - weekly
- * 'n' - never
+ * 'n' - never
"""
user = models.User.objects.create_user(username, email)
@@ -42,7 +42,7 @@ def create_user(
user.set_status(status)
if notification_schedule == None:
notification_schedule = models.EmailFeedSetting.NO_EMAIL_SCHEDULE
-
+
#a hack, we need to delete these, that will be created automatically
#because just below we will be replacing them with the new values
user.notification_subscriptions.all().delete()
@@ -107,9 +107,16 @@ class AskbotTestCase(TestCase):
args_list.pop(1)#so we can remove an item
self.assertRaises(*args_list, **kwargs)
+ def assertQuerysetEqual(self, qs1, qs2, transform=repr, ordered=True):
+ '''borrowed from django1.4 and modified a bit'''
+ items = map(transform, qs1)
+ values = map(transform, qs2)
+ if not ordered:
+ return self.assertEqual(set(items), set(values))
+ return self.assertEqual(list(items), list(values))
def post_question(
- self,
+ self,
user = None,
title = 'test question title',
body_text = 'test question body text',
@@ -207,7 +214,7 @@ class AskbotTestCase(TestCase):
"""reloads model object from the database
"""
return obj.__class__.objects.get(id = obj.id)
-
+
def post_answer(
self,
user = None,
@@ -258,8 +265,8 @@ class AskbotTestCase(TestCase):
by_email = False,
timestamp = None
):
- """posts and returns a comment to parent post, uses
- now timestamp if not given, dummy body_text
+ """posts and returns a comment to parent post, uses
+ now timestamp if not given, dummy body_text
author is required
"""
if user is None:
diff --git a/askbot/tests/utils_tests.py b/askbot/tests/utils_tests.py
new file mode 100644
index 00000000..7f252b69
--- /dev/null
+++ b/askbot/tests/utils_tests.py
@@ -0,0 +1,17 @@
+from django.test import TestCase
+from askbot.utils.url_utils import urls_equal
+
+class UrlUtilsTests(TestCase):
+
+ def tests_urls_equal(self):
+ e = urls_equal
+ self.assertTrue(e('', ''))
+ self.assertTrue(e('', '/', True))
+ self.assertTrue(e('http://cnn.com', 'http://cnn.com/', True))
+
+ self.assertFalse(e('https://cnn.com', 'http://cnn.com'))
+ self.assertFalse(e('http://cnn.com:80', 'http://cnn.com:8000'))
+
+ self.assertTrue(e('http://cnn.com/path', 'http://cnn.com/path/', True))
+ self.assertFalse(e('http://cnn.com/path', 'http://cnn.com/path/'))
+
diff --git a/askbot/tests/widget_tests.py b/askbot/tests/widget_tests.py
index 40c63e0e..98c5a8aa 100644
--- a/askbot/tests/widget_tests.py
+++ b/askbot/tests/widget_tests.py
@@ -11,6 +11,7 @@ class WidgetViewsTests(AskbotTestCase):
def setUp(self):
self.client = Client()
+ self.widget = models.AskWidget.objects.create(title='foo widget')
self.user = self.create_user('user1')
self.user.set_password('sample')
self.user.save()
@@ -19,12 +20,13 @@ class WidgetViewsTests(AskbotTestCase):
def test_post_with_auth(self):
self.client.login(username='user1', password='sample')
- response = self.client.post(reverse('ask_by_widget'), self.good_data)
+ response = self.client.post(reverse('ask_by_widget', args=(self.widget.id, )), self.good_data)
self.assertEquals(response.status_code, 302)
self.client.logout()
def test_post_without_auth(self):
- response = self.client.post(reverse('ask_by_widget'), self.good_data)
+ #weird issue
+ response = self.client.post(reverse('ask_by_widget', args=(self.widget.id, )), self.good_data)
self.assertEquals(response.status_code, 302)
self.assertTrue('widget_question' in self.client.session)
self.assertEquals(self.client.session['widget_question']['title'],
@@ -45,12 +47,21 @@ class WidgetViewsTests(AskbotTestCase):
session = self.client.session
session['widget_question'] = widget_question_data
session.save()
- response = self.client.get(reverse('ask_by_widget'),
- {'action': 'post-after-login'})
+ response = self.client.get(
+ reverse('ask_by_widget', args=(self.widget.id, )),
+ {'action': 'post-after-login'}
+ )
self.assertFalse('widget_question' in self.client.session)
self.assertEquals(response.status_code, 302)
#verify posting question
+ def test_render_widget_view(self):
+ response = self.client.get(reverse('render_ask_widget', args=(self.widget.id, )))
+ self.assertEquals(200, response.status_code)
+ mimetype = 'text/javascript'
+ self.assertTrue(mimetype in response['Content-Type'])
+
+
class WidgetLoginViewTest(AskbotTestCase):
def test_correct_template_loading(self):
@@ -59,3 +70,100 @@ class WidgetLoginViewTest(AskbotTestCase):
template_name = 'authopenid/widget_signin.html'
templates = [template.name for template in response.templates]
self.assertTrue(template_name in templates)
+
+class WidgetCreatorViewsTests(AskbotTestCase):
+
+ def setUp(self):
+ self.client = Client()
+ self.user = self.create_user('user1')
+ self.user.set_password('testpass')
+ self.user.set_admin_status()
+ self.user.save()
+ self.widget = models.AskWidget.objects.create(title='foo widget')
+
+ def test_list_ask_widget_view(self):
+ self.client.login(username='user1', password='testpass')
+ response = self.client.get(reverse('list_widgets', args=('ask',)))
+ self.assertEquals(response.status_code, 200)
+ self.assertTrue('widgets' in response.context)
+
+ def test_create_ask_widget_get(self):
+ self.client.login(username='user1', password='testpass')
+ response = self.client.get(reverse('create_widget', args=('ask',)))
+ self.assertEquals(response.status_code, 200)
+ self.assertTrue('form' in response.context)
+
+ def test_create_ask_widget_post(self):
+ self.client.login(username='user1', password='testpass')
+ post_data = {'title': 'Test widget'}
+ response = self.client.post(reverse('create_widget', args=('ask',)), post_data)
+ self.assertEquals(response.status_code, 302)
+
+ def test_edit_ask_widget_get(self):
+ self.client.login(username='user1', password='testpass')
+ response = self.client.get(reverse('edit_widget',
+ args=('ask', self.widget.id, )))
+ self.assertEquals(response.status_code, 200)
+ self.assertTrue('form' in response.context)
+
+ def test_edit_ask_widget_post(self):
+ self.client.login(username='user1', password='testpass')
+ post_data = {'title': 'Test lalalla'}
+ response = self.client.post(reverse('edit_widget',
+ args=('ask', self.widget.id, )), post_data)
+ self.assertEquals(response.status_code, 302)
+
+ def test_delete_ask_widget_get(self):
+ self.client.login(username='user1', password='testpass')
+ response = self.client.get(reverse('delete_widget',
+ args=('ask', self.widget.id, )))
+ self.assertEquals(response.status_code, 200)
+ self.assertTrue('widget' in response.context)
+
+ def test_delete_ask_widget_post(self):
+ self.client.login(username='user1', password='testpass')
+ response = self.client.post(reverse('delete_widget',
+ args=('ask', self.widget.id, )))
+ self.assertEquals(response.status_code, 302)
+
+ #this test complains about 404.html template but it's correct
+ #def test_bad_url(self):
+ # self.client.login(username='user1', password='testpass')
+ # response = self.client.get('/widgets/foo/create/')
+ # self.assertEquals(404, response.status_code)
+
+
+class QuestionWidgetViewsTests(AskbotTestCase):
+
+ def setUp(self):
+ self.user = self.create_user('testuser')
+ self.client = Client()
+ self.widget = models.QuestionWidget.objects.create(title="foo",
+ question_number=5, search_query='test',
+ tagnames='test')
+
+ #we post 6 questions!
+ titles = (
+ 'test question 1', 'this is a test',
+ 'without the magic word', 'test test test',
+ 'test just another test', 'no magic word',
+ 'test another', 'I can no believe is a test'
+ )
+
+ tagnames = 'test foo bar'
+ for title in titles:
+ self.post_question(title=title, tags=tagnames)
+
+ def test_valid_response(self):
+ filter_params = {
+ 'title__icontains': self.widget.search_query,
+ 'tags__name__in': self.widget.tagnames.split(' ')
+ }
+
+ threads = models.Thread.objects.filter(**filter_params)[:5]
+
+ response = self.client.get(reverse('question_widget', args=(self.widget.id, )))
+ self.assertEquals(200, response.status_code)
+
+ self.assertQuerysetEqual(threads, response.context['threads'])
+ self.assertEquals(self.widget, response.context['widget'])
diff --git a/askbot/urls.py b/askbot/urls.py
index cee5752a..62ae8b54 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -70,13 +70,21 @@ urlpatterns = patterns('',
views.readers.questions,
name='questions'
),
-
# END main page urls
-
url(
r'^api/get_questions/',
views.commands.api_get_questions,
- name = 'api_get_questions'
+ name='api_get_questions'
+ ),
+ url(
+ r'^get-thread-shared-users/',
+ views.commands.get_thread_shared_users,
+ name='get_thread_shared_users'
+ ),
+ url(
+ r'^get-thread-shared-groups/',
+ views.commands.get_thread_shared_groups,
+ name='get_thread_shared_groups'
),
url(
r'^save-draft-question/',
@@ -144,11 +152,6 @@ urlpatterns = patterns('',
kwargs = {'post_type': 'question'},
name='question_revisions'
),
- url(
- r'^%s%s$' % (_('widgets/'), _('questions/')),
- views.readers.widget_questions,
- name='widget_questions'
- ),
url(#ajax only
r'^comment/upvote/$',
views.commands.upvote_comment,
@@ -382,16 +385,59 @@ urlpatterns = patterns('',
),
#widgets url!
url(
- r'^widgets/ask/$',
+ r'^%s$' % (_('widgets/')),
+ views.widgets.widgets,
+ name = 'widgets'
+ ),
+
+ url(
+ r'^%s%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('ask/')),
views.widgets.ask_widget,
name = 'ask_by_widget'
),
url(
- r'^widgets/ask/complete/$',
+ r'^%s%s(?P<widget_id>\d+).js$' % (_('widgets/'), _('ask/')),
+ views.widgets.render_ask_widget_js,
+ name = 'render_ask_widget'
+ ),
+ url(
+ r'^%s%s(?P<widget_id>\d+).css$' % (_('widgets/'), _('ask/')),
+ views.widgets.render_ask_widget_css,
+ name = 'render_ask_widget_css'
+ ),
+
+ url(
+ r'^%s%s%s$' % (_('widgets/'), _('ask/'), _('complete/')),
views.widgets.ask_widget_complete,
name = 'ask_by_widget_complete'
),
url(
+ r'^%s(?P<model>\w+)/%s$' % (_('widgets/'), _('create/')),
+ views.widgets.create_widget,
+ name = 'create_widget'
+ ),
+ url(
+ r'^%s(?P<model>\w+)/%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('edit/')),
+ views.widgets.edit_widget,
+ name = 'edit_widget'
+ ),
+ url(
+ r'^%s(?P<model>\w+)/%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('delete/')),
+ views.widgets.delete_widget,
+ name = 'delete_widget'
+ ),
+
+ url(
+ r'^%s(?P<model>\w+)/$' % (_('widgets/')),
+ views.widgets.list_widgets,
+ name = 'list_widgets'
+ ),
+ url(
+ r'^widgets/questions/(?P<widget_id>\d+)/$',
+ views.widgets.question_widget,
+ name = 'question_widget'
+ ),
+ url(
r'^feeds/(?P<url>.*)/$',
'django.contrib.syndication.views.feed',
{'feed_dict': feeds},
diff --git a/askbot/utils/forms.py b/askbot/utils/forms.py
index a26fbcd5..f607e62b 100644
--- a/askbot/utils/forms.py
+++ b/askbot/utils/forms.py
@@ -7,6 +7,7 @@ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from askbot.conf import settings as askbot_settings
from askbot.utils.slug import slugify
+from askbot.utils.functions import split_list
from askbot import const
import logging
import urllib
@@ -141,25 +142,63 @@ class UserNameField(StrippedNonEmptyCharField):
logging.debug('error - user with this name already exists')
raise forms.ValidationError(self.error_messages['multiple-taken'])
+
+def email_is_allowed(
+ email, allowed_emails='', allowed_email_domains=''
+):
+ """True, if email address is pre-approved or matches a allowed
+ domain"""
+ if allowed_emails:
+ email_list = split_list(allowed_emails)
+ allowed_emails = ' ' + ' '.join(email_list) + ' '
+ email_match_re = re.compile(r'\s%s\s' % email)
+ if email_match_re.search(allowed_emails):
+ return True
+
+ if allowed_email_domains:
+ email_domain = email.split('@')[1]
+ domain_list = split_list(allowed_email_domains)
+ domain_match_re = re.compile(r'\s%s\s' % email_domain)
+ allowed_email_domains = ' ' + ' '.join(domain_list) + ' '
+ return domain_match_re.search(allowed_email_domains)
+
+ return False
+
class UserEmailField(forms.EmailField):
def __init__(self,skip_clean=False,**kw):
self.skip_clean = skip_clean
- super(UserEmailField,self).__init__(widget=forms.TextInput(attrs=dict(login_form_widget_attrs,
- maxlength=200)), label=mark_safe(_('Your email <i>(never shared)</i>')),
- error_messages={'required':_('email address is required'),
- 'invalid':_('please enter a valid email address'),
- 'taken':_('this email is already used by someone else, please choose another'),
- },
+ super(UserEmailField,self).__init__(
+ widget=forms.TextInput(
+ attrs=dict(login_form_widget_attrs, maxlength=200)
+ ),
+ label=mark_safe(_('Your email <i>(never shared)</i>')),
+ error_messages={
+ 'required':_('email address is required'),
+ 'invalid':_('please enter a valid email address'),
+ 'taken':_('this email is already used by someone else, please choose another'),
+ 'unauthorized':_('this email address is not authorized')
+ },
**kw
- )
+ )
- def clean(self,email):
+ def clean(self, email):
""" validate if email exist in database
from legacy register
return: raise error if it exist """
email = super(UserEmailField,self).clean(email.strip())
if self.skip_clean:
return email
+
+ allowed_domains = askbot_settings.ALLOWED_EMAIL_DOMAINS.strip()
+ allowed_emails = askbot_settings.ALLOWED_EMAILS.strip()
+
+ if allowed_emails or allowed_domains:
+ if not email_is_allowed(
+ email,
+ allowed_emails=allowed_emails,
+ allowed_email_domains=allowed_domains
+ ):
+ raise forms.ValidationError(self.error_messages['unauthorized'])
if askbot_settings.EMAIL_UNIQUE == True:
try:
user = User.objects.get(email = email)
diff --git a/askbot/utils/url_utils.py b/askbot/utils/url_utils.py
index 6027d096..c58239c5 100644
--- a/askbot/utils/url_utils.py
+++ b/askbot/utils/url_utils.py
@@ -1,3 +1,4 @@
+import os
import urlparse
from django.core.urlresolvers import reverse
from django.conf import settings
@@ -13,6 +14,38 @@ def strip_path(url):
)
)
+def append_trailing_slash(urlpath):
+ """if path is empty - returns slash
+ if not and path does not end with the slash
+ appends it
+ """
+ if urlpath == '':
+ return '/'
+ elif not urlpath.endswith('/'):
+ return urlpath + '/'
+ return urlpath
+
+def urls_equal(url1, url2, ignore_trailing_slash=False):
+ """True, if urls are equal"""
+ purl1 = urlparse.urlparse(url1)
+ purl2 = urlparse.urlparse(url2)
+ if purl1.scheme != purl2.scheme:
+ return False
+
+ if purl1.netloc != purl2.netloc:
+ return False
+
+ if ignore_trailing_slash is True:
+ normfunc = append_trailing_slash
+ else:
+ normfunc = lambda v: v
+
+ if normfunc(purl1.path) != normfunc(purl2.path):
+ return False
+
+ #test remaining items in the parsed url
+ return purl1[3:] == purl2[3:]
+
def get_login_url():
"""returns internal login url if
django_authopenid is used, or
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 1b6b21e8..a399e8c8 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -30,6 +30,7 @@ from askbot.utils import decorators
from askbot.utils import url_utils
from askbot import mail
from askbot.skins.loaders import render_into_skin, get_template
+from askbot.skins.loaders import render_into_skin_as_string
from askbot import const
@@ -330,7 +331,7 @@ def vote(request, id):
response_data['count'] = post.offensive_flag_count
response_data['success'] = 1
-
+
elif vote_type in ['7.6', '8.6']:
#flag question or answer
if vote_type == '7.6':
@@ -468,13 +469,47 @@ def get_tags_by_wildcard(request):
wildcard = request.GET.get('wildcard', None)
if wildcard is None:
raise Http404
-
+
matching_tags = models.Tag.objects.get_by_wildcards( [wildcard,] )
count = matching_tags.count()
names = matching_tags.values_list('name', flat = True)[:20]
re_data = simplejson.dumps({'tag_count': count, 'tag_names': list(names)})
return HttpResponse(re_data, mimetype = 'application/json')
+@decorators.get_only
+def get_thread_shared_users(request):
+ """returns snippet of html with users"""
+ thread_id = request.GET['thread_id']
+ thread_id = IntegerField().clean(thread_id)
+ thread = models.Thread.objects.get(id=thread_id)
+ users = thread.get_users_shared_with()
+ data = {
+ 'users': users,
+ }
+ html = render_into_skin_as_string('widgets/user_list.html', data, request)
+ re_data = simplejson.dumps({
+ 'html': html,
+ 'users_count': users.count(),
+ 'success': True
+ })
+ return HttpResponse(re_data, mimetype='application/json')
+
+@decorators.get_only
+def get_thread_shared_groups(request):
+ """returns snippet of html with groups"""
+ thread_id = request.GET['thread_id']
+ thread_id = IntegerField().clean(thread_id)
+ thread = models.Thread.objects.get(id=thread_id)
+ groups = thread.get_groups_shared_with()
+ data = {'groups': groups}
+ html = render_into_skin_as_string('widgets/groups_list.html', data, request)
+ re_data = simplejson.dumps({
+ 'html': html,
+ 'groups_count': groups.count(),
+ 'success': True
+ })
+ return HttpResponse(re_data, mimetype='application/json')
+
@decorators.ajax_only
def get_html_template(request):
"""returns rendered template"""
@@ -882,7 +917,7 @@ def edit_group_membership(request):
@decorators.admins_only
def save_group_logo_url(request):
"""saves urls for the group logo"""
- form = forms.GroupLogoURLForm(request.POST)
+ form = forms.GroupLogoURLForm(request.POST)
if form.is_valid():
group_id = form.cleaned_data['group_id']
image_url = form.cleaned_data['image_url']
@@ -1020,8 +1055,8 @@ def save_post_reject_reason(request):
@decorators.admins_only
def moderate_suggested_tag(request):
"""accepts or rejects a suggested tag
- if thread id is given, then tag is
- applied to or removed from only one thread,
+ if thread id is given, then tag is
+ applied to or removed from only one thread,
otherwise the decision applies to all threads
"""
form = forms.ModerateTagForm(request.POST)
diff --git a/askbot/views/meta.py b/askbot/views/meta.py
index e4209185..7b271219 100644
--- a/askbot/views/meta.py
+++ b/askbot/views/meta.py
@@ -16,6 +16,8 @@ from django.db.models import Max, Count
from askbot import skins
from askbot.conf import settings as askbot_settings
from askbot.forms import FeedbackForm
+from askbot.utils.url_utils import get_login_url
+from askbot.utils.forms import get_next_url
from askbot.mail import mail_moderators
from askbot.models import BadgeData, Award, User, Tag
from askbot.models import badges as badge_data
@@ -84,9 +86,19 @@ def faq(request):
def feedback(request):
data = {'page_class': 'meta'}
form = None
+
+ if askbot_settings.ALLOW_ANONYMOUS_FEEDBACK is False:
+ if request.user.is_anonymous():
+ message = _('Please sign in or register to send your feedback')
+ request.user.message_set.create(message=message)
+ redirect_url = get_login_url() + '?next=' + request.path
+ return HttpResponseRedirect(redirect_url)
+
if request.method == "POST":
- form = FeedbackForm(is_auth = request.user.is_authenticated(),
- data = request.POST)
+ form = FeedbackForm(
+ is_auth=request.user.is_authenticated(),
+ data=request.POST
+ )
if form.is_valid():
if not request.user.is_authenticated():
data['email'] = form.cleaned_data.get('email',None)
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index 8d63ba51..faa6f3ba 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -424,7 +424,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
return HttpResponseRedirect(reverse('index'))
elif show_answer:
- #if the url calls to view a particular answer to
+ #if the url calls to view a particular answer to
#question - we must check whether the question exists
#whether answer is actually corresponding to the current question
#and that the visitor is allowed to see it
@@ -590,6 +590,9 @@ def question(request, id):#refactor - long subroutine. display question body, an
'show_comment': show_comment,
'show_comment_position': show_comment_position,
}
+ #shared with ...
+ if askbot_settings.GROUPS_ENABLED:
+ data['sharing_info'] = thread.get_sharing_info()
data.update(context.get_for_tag_editor())
@@ -631,20 +634,3 @@ def get_comment(request):
comment = models.Post.objects.get(post_type='comment', id=id)
request.user.assert_can_edit_comment(comment)
return {'text': comment.text}
-
-def widget_questions(request):
- """Returns the first x questions based on certain tags.
- @returns template with those questions listed."""
- # make sure this is a GET request with the correct parameters.
- if request.method != 'GET':
- raise Http404
- threads = models.Thread.objects.all()
- tags_input = request.GET.get('tags','').strip()
- if len(tags_input) > 0:
- tags = [tag.strip() for tag in tags_input.split(',')]
- threads = threads.filter(tags__name__in=tags)
- data = {
- 'threads': threads[:askbot_settings.QUESTIONS_WIDGET_MAX_QUESTIONS]
- }
- return render_into_skin('question_widget.html', data, request)
-
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 9a5076dd..c5b95390 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -40,7 +40,6 @@ from askbot.models.badges import award_badges_signal
from askbot.models.tag import get_global_group
from askbot.models.tag import get_groups
from askbot.skins.loaders import render_into_skin
-from askbot.templatetags import extra_tags
from askbot.search.state_manager import SearchState
from askbot.utils import url_utils
from askbot.utils.loading import load_module
@@ -60,6 +59,15 @@ def owner_or_moderator_required(f):
def show_users(request, by_group=False, group_id=None, group_slug=None):
"""Users view, including listing of users by group"""
+
+ if askbot_settings.GROUPS_ENABLED and not by_group:
+ default_group = get_global_group()
+ group_slug = slugify(default_group.name)
+ new_url = reverse('users_by_group',
+ kwargs={'group_id': default_group.id,
+ 'group_slug': group_slug})
+ return HttpResponseRedirect(new_url)
+
users = models.User.objects.exclude(status = 'b')
group = None
group_email_moderation_enabled = False
@@ -398,7 +406,7 @@ def user_stats(request, user, context):
interesting_tag_names = None
ignored_tag_names = None
subscribed_tag_names = None
-
+
# tags = models.Post.objects.filter(author=user).values('id', 'thread', 'thread__tags')
# post_ids = set()
# thread_ids = set()
@@ -1010,7 +1018,7 @@ def groups(request, id = None, slug = None):
)
groups = groups.exclude(name__startswith='_internal_')
-
+ groups = groups.annotate(users_count=Count('user_memberships'))
groups = groups.select_related('group_profile')
user_can_add_groups = request.user.is_authenticated() and \
diff --git a/askbot/views/widgets.py b/askbot/views/widgets.py
index 77eb7eb5..9bd5e669 100644
--- a/askbot/views/widgets.py
+++ b/askbot/views/widgets.py
@@ -1,48 +1,106 @@
from datetime import datetime
from django.core import exceptions
-from django.utils import simplejson
-from django.shortcuts import redirect
+from django.template import Context
+from django.http import HttpResponse, Http404
from django.views.decorators import csrf
-from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
+from django.shortcuts import redirect, get_object_or_404
+from django.views.decorators.cache import cache_page
from django.contrib.auth.decorators import login_required
-from askbot.skins.loaders import render_into_skin
+from askbot.skins.loaders import render_into_skin, get_template
+from askbot.conf import settings as askbot_settings
+from askbot.utils import decorators
from askbot import models
from askbot import forms
+WIDGETS_MODELS = {
+ 'ask': models.AskWidget,
+ 'question': models.QuestionWidget
+ }
+
+WIDGETS_FORMS = {
+ 'ask': models.widgets.CreateAskWidgetForm,
+ 'question': models.widgets.CreateQuestionWidgetForm,
+ }
+
+def _get_model(key):
+ '''like get_object_or_404 but for our models'''
+ try:
+ return WIDGETS_MODELS[key]
+ except KeyError:
+ raise Http404
+
+def _get_form(key):
+ '''like get_object_or_404 but for our forms'''
+ try:
+ return WIDGETS_FORMS[key]
+ except KeyError:
+ raise Http404
+
+@decorators.admins_only
+def widgets(request):
+ data = {
+ 'ask_widgets': models.AskWidget.objects.all().count(),
+ 'question_widgets': models.QuestionWidget.objects.all().count(),
+ 'page_class': 'widgets'
+ }
+ return render_into_skin('embed/widgets.html', data, request)
+
@csrf.csrf_protect
-def ask_widget(request):
+def ask_widget(request, widget_id):
def post_question(data, request):
- thread = models.Thread.objects.create_new(**data_dict)
+ thread = models.Thread.objects.create_new(**data)
question = thread._question_post()
request.session['widget_question_url'] = question.get_absolute_url()
return question
+ widget = get_object_or_404(models.AskWidget, id=widget_id)
if request.method == "POST":
- form = forms.AskWidgetForm(request.POST)
+ form = forms.AskWidgetForm(include_text=widget.include_text_field,
+ data=request.POST)
if form.is_valid():
ask_anonymously = form.cleaned_data['ask_anonymously']
title = form.cleaned_data['title']
+ if widget.include_text_field:
+ text = form.cleaned_data['text']
+ else:
+ text = ' '
+
+
+ if widget.group:
+ group_id = widget.group.id
+ else:
+ group_id = None
+
+ if widget.tag:
+ tagnames = widget.tag.name
+ else:
+ tagnames = ''
+
data_dict = {
- 'title': title,
- 'added_at': datetime.now(),
- 'wiki': False,
- 'text': ' ',
- 'tagnames': '',
- 'is_anonymous': ask_anonymously
- }
+ 'title': title,
+ 'added_at': datetime.now(),
+ 'wiki': False,
+ 'text': text,
+ 'tagnames': tagnames,
+ 'group_id': group_id,
+ 'is_anonymous': ask_anonymously
+ }
if request.user.is_authenticated():
data_dict['author'] = request.user
question = post_question(data_dict, request)
return redirect('ask_by_widget_complete')
else:
request.session['widget_question'] = data_dict
- next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
+ next_url = '%s?next=%s' % (
+ reverse('widget_signin'),
+ reverse('ask_by_widget', args=(widget.id,))
+ )
return redirect(next_url)
else:
if 'widget_question' in request.session and \
@@ -58,17 +116,136 @@ def ask_widget(request):
next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
return redirect(next_url)
- form = forms.AskWidgetForm()
- data = {'form': form}
- return render_into_skin('ask_by_widget.html', data, request)
+ form = forms.AskWidgetForm(include_text=widget.include_text_field)
+
+ data = {'form': form, 'widget': widget}
+ return render_into_skin('embed/ask_by_widget.html', data, request)
@login_required
def ask_widget_complete(request):
question_url = request.session.get('widget_question_url')
+ custom_css = request.session.get('widget_css')
if question_url:
del request.session['widget_question_url']
else:
question_url = '#'
- data = {'question_url': question_url}
- return render_into_skin('ask_widget_complete.html', data, request)
+ if custom_css:
+ del request.session['widget_css']
+
+ data = {'question_url': question_url, 'custom_css': custom_css}
+ return render_into_skin('embed/ask_widget_complete.html', data, request)
+
+
+@decorators.admins_only
+def list_widgets(request, model):
+ model_class = _get_model(model)
+ widgets = model_class.objects.all()
+ data = {
+ 'widgets': widgets,
+ 'widget_name': model
+ }
+ return render_into_skin('embed/list_widgets.html', data, request)
+
+@decorators.admins_only
+def create_widget(request, model):
+ form_class = _get_form(model)
+ if request.method == 'POST':
+ form = form_class(request.POST)
+ if form.is_valid():
+ form.save()
+ return redirect('list_widgets', model=model)
+ else:
+ form = form_class()
+
+ data = {'form': form,
+ 'action': 'edit',
+ 'widget_name': model}
+
+ return render_into_skin('embed/widget_form.html', data, request)
+
+@decorators.admins_only
+def edit_widget(request, model, widget_id):
+ model_class = _get_model(model)
+ form_class = _get_form(model)
+ widget = get_object_or_404(model_class, pk=widget_id)
+ if request.method == 'POST':
+ form = form_class(request.POST,
+ instance=widget)
+ if form.is_valid():
+ form.save()
+ return redirect('list_widgets', model=model)
+ else:
+ form = form_class(instance=widget)
+
+ data = {'form': form,
+ 'action': 'edit',
+ 'widget_name': model}
+ return render_into_skin('embed/widget_form.html', data, request)
+
+@decorators.admins_only
+def delete_widget(request, model, widget_id):
+ model_class = _get_model(model)
+ widget = get_object_or_404(model_class, pk=widget_id)
+ if request.method == "POST":
+ widget.delete()
+ return redirect('list_widgets', model=model)
+ else:
+ return render_into_skin('embed/delete_widget.html',
+ {'widget': widget, 'widget_name': model}, request)
+
+@cache_page(60*30)
+def render_ask_widget_js(request, widget_id):
+ widget = get_object_or_404(models.AskWidget, pk=widget_id)
+ variable_name = "AskbotAskWidget%d" % widget.id
+ content_tpl = get_template('embed/askbot_widget.js', request)
+ context_dict = {'widget': widget,
+ 'host': request.get_host(),
+ 'variable_name': variable_name}
+ content = content_tpl.render(Context(context_dict))
+ return HttpResponse(content, mimetype='text/javascript')
+
+@cache_page(60*30)
+def render_ask_widget_css(request, widget_id):
+ widget = get_object_or_404(models.AskWidget, pk=widget_id)
+ variable_name = "AskbotAskWidget%d" % widget.id
+ content_tpl = get_template('embed/askbot_widget.css', request)
+ context_dict = {'widget': widget,
+ 'host': request.get_host(),
+ 'variable_name': variable_name}
+ content = content_tpl.render(Context(context_dict))
+ return HttpResponse(content, mimetype='text/css')
+
+@cache_page(60*30)
+def question_widget(request, widget_id):
+ """Returns the first x questions based on certain tags.
+ @returns template with those questions listed."""
+ # make sure this is a GET request with the correct parameters.
+ widget = get_object_or_404(models.QuestionWidget, pk=widget_id)
+
+ if request.method != 'GET':
+ raise Http404
+
+ filter_params = {}
+
+ if widget.tagnames:
+ filter_params['tags__name__in'] = widget.tagnames.split(' ')
+
+ if widget.group:
+ filter_params['groups'] = widget.group
+
+ #simple title search for now
+ if widget.search_query:
+ filter_params['title__icontains'] = widget.search_query
+
+ if filter_params:
+ threads = models.Thread.objects.filter(**filter_params).order_by(widget.order_by)[:widget.question_number]
+ else:
+ threads = models.Thread.objects.all().order_by(widget.order_by)[:widget.question_number]
+
+ data = {
+ 'threads': threads,
+ 'widget': widget
+ }
+
+ return render_into_skin('embed/question_widget.html', data, request)