diff options
25 files changed, 528 insertions, 299 deletions
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/user_settings.py b/askbot/conf/user_settings.py index e7dea7c8..a1d5a55c 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/deps/django_authopenid/urls.py b/askbot/deps/django_authopenid/urls.py index 249e709e..1b7d0b01 100644 --- a/askbot/deps/django_authopenid/urls.py +++ b/askbot/deps/django_authopenid/urls.py @@ -30,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 468e494d..786a34ca 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -80,15 +80,73 @@ 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, subscribe=False +): + """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': subscribe} + ) + 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', + 'subscribe', + '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)) @@ -778,7 +836,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) @@ -820,56 +877,33 @@ def register(request, login_provider_name=None, user_identifier=None): 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'] + subscribe = email_feeds_form.cleaned_data['subscribe'] - user = User.objects.create_user(username, email) - 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) + if 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, + subscribe=subscribe + ) + 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 + request.session['subscribe'] = subscribe + 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>', @@ -904,6 +938,61 @@ 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) + subscribe = request.session['subscribe'] + 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, + subscribe=subscribe + ) + 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, + subscribe=subscribe + ) + 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 @@ -913,8 +1002,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'] @@ -946,42 +1034,32 @@ def signup_with_password(request): 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' - ) + subscribe = email_feeds_form.cleaned_data['subscribe'] + + if askbot_settings.REQUIRE_VALID_EMAIL_FOR == 'nothing': + user = create_authenticated_user_account( + username=username, + email=email, + password=password, + subscribe=subscribe + ) + 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 + request.session['subscribe'] = subscribe + #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, 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 @@ -990,7 +1068,7 @@ 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 } ) @@ -1051,89 +1129,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, settings.DEFAULT_FROM_EMAIL, [user.email]) + send_mail(subject, message, 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) + send_email_key(user.email, user.email_key) -@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 - - 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 @@ -1149,7 +1173,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.' ) @@ -1164,6 +1188,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')) @@ -1197,26 +1222,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 5662661a..57b14550 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -351,7 +351,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'} ) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 90d8df14..ecbd64e5 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -45,7 +45,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 +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 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 index 3303eca7..473a2b73 100644 --- a/askbot/models/widgets.py +++ b/askbot/models/widgets.py @@ -4,7 +4,7 @@ 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 +from askbot.forms import FormWithHideableFields, TagNamesField from askbot.conf import settings as askbot_settings from django import forms @@ -30,6 +30,19 @@ class AskWidget(models.Model): def __unicode__(self): return "Widget: %s" % self.title +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) + style = models.TextField('css for the widget', + default=DEFAULT_INNER_STYLE, blank=True) + + class Meta: + app_label = 'askbot' + +#FORMS class CreateAskWidgetForm(forms.ModelForm, FormWithHideableFields): inner_style = forms.CharField( widget=forms.Textarea, @@ -54,3 +67,16 @@ class CreateAskWidgetForm(forms.ModelForm, FormWithHideableFields): class Meta: model = AskWidget + +class CreateQuestionWidgetForm(forms.ModelForm, FormWithHideableFields): + tagnames = TagNamesField() + group = forms.ModelChoiceField(queryset=get_groups().exclude(name__startswith='_internal'), + required=False) + + #def __init__(self, *args, **kwargs): + # super(CreateQuestionWidgetForm, self).__init__(*args, **kwargs) + # if not askbot_settings.GROUPS_ENABLED: + # self.hide_field('group') + + class Meta: + model = QuestionWidget 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/style/style.less b/askbot/skins/default/media/style/style.less index 069c36e4..cc7a4431 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -384,6 +384,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; diff --git a/askbot/skins/default/templates/ask_by_widget.html b/askbot/skins/default/templates/embed/ask_by_widget.html index f5c2a0a2..f5c2a0a2 100644 --- a/askbot/skins/default/templates/ask_by_widget.html +++ b/askbot/skins/default/templates/embed/ask_by_widget.html diff --git a/askbot/skins/default/templates/ask_widget_complete.html b/askbot/skins/default/templates/embed/ask_widget_complete.html index 580c1f94..580c1f94 100644 --- a/askbot/skins/default/templates/ask_widget_complete.html +++ b/askbot/skins/default/templates/embed/ask_widget_complete.html diff --git a/askbot/skins/default/templates/delete_ask_widget.html b/askbot/skins/default/templates/embed/delete_widget.html index 17f48b8c..ed80c537 100644 --- a/askbot/skins/default/templates/delete_ask_widget.html +++ b/askbot/skins/default/templates/embed/delete_widget.html @@ -1,12 +1,12 @@ {% extends "one_column_body.html" %} <!-- create_ask_widget.html --> -{% block title %}Delete Ask Question Widget{% endblock %} +{% block title %}Delete {{widget_name|capitalize}} Widget{% endblock %} {% block content %} -<h1>Are you sure that you cant to delete this Ask Question Widget?</h1> +<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_ask_widgets %}">Go Back</a></p> + <p><input type='submit' value='Delete' /> <a href="{% url list_widgets widget_name %}">Go Back</a></p> </form> {% endblock %} {% block endjs %} diff --git a/askbot/skins/default/templates/list_ask_widget.html b/askbot/skins/default/templates/embed/list_widgets.html index 27b8a20a..fcba7fc4 100644 --- a/askbot/skins/default/templates/list_ask_widget.html +++ b/askbot/skins/default/templates/embed/list_widgets.html @@ -1,8 +1,8 @@ {% extends "two_column_body.html" %} <!-- create_ask_widget.html --> -{% block title %}Create a Widget{% endblock %} +{% block title %}{{widget_name|capitalize}} widget list{% endblock %} {% block content %} -<h1>Ask Question widget list</h1> + <h1>{{widget_name|capitalize}} widget list</h1> <table border="0"> <tr> @@ -14,7 +14,7 @@ <tr> <td>{{widget.title}}</td> <td> <script type="text/javascript" src="http://{{request.get_host()}}{% url render_ask_widget widget.id%}" ></script></td> - <td><a href="{% url edit_ask_widget widget.id %}">Edit</a> | <a href="{% url delete_ask_widget widget.id %}">Delete</a></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%} </table> diff --git a/askbot/skins/default/templates/question_widget.html b/askbot/skins/default/templates/embed/question_widget.html index 9d32294b..9d32294b 100644 --- a/askbot/skins/default/templates/question_widget.html +++ b/askbot/skins/default/templates/embed/question_widget.html diff --git a/askbot/skins/default/templates/ask_widget_form.html b/askbot/skins/default/templates/embed/widget_form.html index e082b175..a658050c 100644 --- a/askbot/skins/default/templates/ask_widget_form.html +++ b/askbot/skins/default/templates/embed/widget_form.html @@ -1,8 +1,8 @@ {% extends "one_column_body.html" %} <!-- create_ask_widget.html --> -{% block title %}{% trans %}Create an Ask a Question widget{% endtrans %}{% endblock %} +{% block title %}{% trans %}Create an {{widget_name}} widget{% endtrans %}{% endblock %} {% block content %} -<h1 class="section-title">{% trans %}Create an ask a questions widget{% endtrans %}</h1> +<h1 class="section-title">{% trans %}Create an {{widget_name}} widget{% endtrans %}</h1> {#% if form.non_field_errors() %} {{ form.non_field_errors() }} {% endif %#} diff --git a/askbot/skins/default/templates/widgets.html b/askbot/skins/default/templates/embed/widgets.html index 4c7a633e..767ebc2c 100644 --- a/askbot/skins/default/templates/widgets.html +++ b/askbot/skins/default/templates/embed/widgets.html @@ -14,10 +14,19 @@ <tbody> <tr> <td>{% trans %}Ask a question{% endtrans %}</td> - <td><a href="{% url create_ask_widget %}">{% trans %}create{% endtrans %}</a></td> + <td><a href="{% url create_widget 'ask' %}">{% trans %}create{% endtrans %}</a></td> <td> - {% if ask_widgets.count() > 0 %} - <a href="{% url list_ask_widgets %}">{% trans %}view list{% endtrans %}</a> + {% 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> diff --git a/askbot/tests/widget_tests.py b/askbot/tests/widget_tests.py index 0ead07f5..dbb6d635 100644 --- a/askbot/tests/widget_tests.py +++ b/askbot/tests/widget_tests.py @@ -81,45 +81,51 @@ class WidgetCreatorViewsTests(AskbotTestCase): def test_list_ask_widget_view(self): self.client.login(username='user1', password='testpass') - response = self.client.get(reverse('list_ask_widgets')) + 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_ask_widget')) + 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_ask_widget'), post_data) + 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_ask_widget', - args=(self.widget.id, ))) + 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_ask_widget', - args=(self.widget.id, )), post_data) + 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_ask_widget', - args=(self.widget.id, ))) + 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_ask_widget', - args=(self.widget.id, ))) + 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) diff --git a/askbot/urls.py b/askbot/urls.py index 4fe999da..c55e552e 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -153,11 +153,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, @@ -391,53 +386,63 @@ urlpatterns = patterns('', ), #widgets url! url( - r'^widgets/$', + r'^%s$' % (_('widgets/')), views.widgets.widgets, name = 'widgets' ), url( - r'^widgets/ask/(?P<widget_id>\d+)/$', + r'^%s%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('ask/')), views.widgets.ask_widget, name = 'ask_by_widget' ), url( - r'^widgets/ask/(?P<widget_id>\d+).js$', + r'^%s%s(?P<widget_id>\d+).js$' % (_('widgets/'), _('ask/')), views.widgets.render_ask_widget_js, name = 'render_ask_widget' ), url( - r'^widgets/ask/(?P<widget_id>\d+).css$', + r'^%s%s(?P<widget_id>\d+).css$' % (_('widgets/'), _('ask/')), views.widgets.render_ask_widget_css, name = 'render_ask_widget_css' ), url( - r'^widgets/ask/complete/$', + r'^%s%s%s$' % (_('widgets/'), _('ask/'), _('complete/')), views.widgets.ask_widget_complete, name = 'ask_by_widget_complete' ), url( - r'^widgets/ask/create/$', - views.widgets.create_ask_widget, - name = 'create_ask_widget' + r'^%s(?P<model>\w+)/%s$' % (_('widgets/'), _('create/')), + views.widgets.create_widget, + name = 'create_widget' ), url( - r'^widgets/ask/edit/(?P<widget_id>\d+)/$', - views.widgets.edit_ask_widget, - name = 'edit_ask_widget' + r'^%s(?P<model>\w+)/%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('edit/')), + views.widgets.edit_widget, + name = 'edit_widget' ), url( - r'^widgets/ask/delete/(?P<widget_id>\d+)/$', - views.widgets.delete_ask_widget, - name = 'delete_ask_widget' + r'^%s(?P<model>\w+)/%s(?P<widget_id>\d+)/$' % (_('widgets/'), _('delete/')), + views.widgets.delete_widget, + name = 'delete_widget' ), url( - r'^widgets/ask/$', - views.widgets.list_ask_widget, - name = 'list_ask_widgets' - ), + r'^%s(?P<model>\w+)/$' % (_('widgets/')), + views.widgets.list_widgets, + name = 'list_widgets' + ), + #url( + # r'^%s%s$' % (_('widgets/'), _('questions/')), + # views.widgets.widget_questions, + # name='widget_questions' + #), + #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', diff --git a/askbot/utils/forms.py b/askbot/utils/forms.py index ee7adf7e..319e9b9d 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 @@ -131,25 +132,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/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 268227eb..3d0f131f 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 @@ -645,20 +645,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/widgets.py b/askbot/views/widgets.py index 1940eedd..8579c7e1 100644 --- a/askbot/views/widgets.py +++ b/askbot/views/widgets.py @@ -1,29 +1,52 @@ from datetime import datetime from django.core import exceptions -from django.utils import simplejson from django.template import Context -from django.http import HttpResponse +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.contrib.auth.decorators import login_required 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(), + 'ask_widgets': models.AskWidget.objects.all().count(), + 'question_widgets': models.QuestionWidget.objects.all().count(), 'page_class': 'widgets' } - return render_into_skin('widgets.html', data, request) + return render_into_skin('embed/widgets.html', data, request) @csrf.csrf_protect def ask_widget(request, widget_id): @@ -93,7 +116,7 @@ def ask_widget(request, widget_id): form = forms.AskWidgetForm(include_text=widget.include_text_field) data = {'form': form, 'widget': widget} - return render_into_skin('ask_by_widget.html', data, request) + return render_into_skin('embed/ask_by_widget.html', data, request) @login_required def ask_widget_complete(request): @@ -108,58 +131,69 @@ def ask_widget_complete(request): del request.session['widget_css'] data = {'question_url': question_url, 'custom_css': custom_css} - return render_into_skin('ask_widget_complete.html', data, request) + return render_into_skin('embed/ask_widget_complete.html', data, request) @decorators.admins_only -def list_ask_widget(request): - widgets = models.AskWidget.objects.all() - data = {'widgets': widgets} - return render_into_skin('list_ask_widget.html', data, request) +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_ask_widget(request): - if request.method=='POST': - form = models.widgets.CreateAskWidgetForm(request.POST) +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_ask_widgets') + return redirect('list_widgets', model=model) else: - form = models.widgets.CreateAskWidgetForm() + form = form_class() + + data = {'form': form, + 'widget_name': model} - data = {'form': form} - return render_into_skin('ask_widget_form.html', data, request) + return render_into_skin('embed/widget_form.html', data, request) @decorators.admins_only -def edit_ask_widget(request, widget_id): - widget = get_object_or_404(models.AskWidget, pk=widget_id) - if request.method=='POST': - form = models.widgets.CreateAskWidgetForm(request.POST, +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_ask_widgets') + return redirect('list_widgets', model=model) else: - form = models.widgets.CreateAskWidgetForm(instance=widget) + form = form_class(instance=widget) - data = {'form': form} - return render_into_skin('ask_widget_form.html', data, request) + data = {'form': form, + 'widget_name': model} + return render_into_skin('embed/widget_form.html', data, request) @decorators.admins_only -def delete_ask_widget(request, widget_id): - widget = get_object_or_404(models.AskWidget, pk=widget_id) - if request.method=="POST": +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_ask_widgets') + return redirect('list_widgets', model=model) else: - return render_into_skin('delete_ask_widget.html', - {'widget': widget}, request) + return render_into_skin('embed/delete_widget.html', + {'widget': widget, 'widget_name': model}, request) #TODO: Add cache 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('widgets/askbot_widget.js', request) + content_tpl = get_template('embed/askbot_widget.js', request) context_dict = {'widget': widget, 'host': request.get_host(), 'variable_name': variable_name} @@ -170,9 +204,30 @@ def render_ask_widget_js(request, widget_id): 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('widgets/askbot_widget.css', request) + 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') + +#search widget +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 + + tags_input = request.GET.get('tags','').strip() + if len(tags_input) > 0: + tags = [tag.strip() for tag in tags_input.split(',')] + threads = models.Thread.objects.filter(tags__name__in=tags)[:askbot_settings.QUESTIONS_WIDGET_MAX_QUESTIONS] + + else: + threads = models.Thread.objects.all()[:askbot_settings.QUESTIONS_WIDGET_MAX_QUESTIONS] + + data = { 'threads': threads } + return render_into_skin('embed/question_widget.html', data, request) |