summaryrefslogtreecommitdiffstats
path: root/forum
diff options
context:
space:
mode:
Diffstat (limited to 'forum')
-rwxr-xr-xforum/authentication/base.py4
-rwxr-xr-xforum/authentication/forms.py46
-rw-r--r--forum/context.py47
-rwxr-xr-xforum/forms.py20
-rwxr-xr-xforum/models/__init__.py11
-rwxr-xr-xforum/models/user.py68
-rwxr-xr-xforum/skins/default/templates/auth/auth_settings.html35
-rwxr-xr-xforum/skins/default/templates/auth/email_validation.html20
-rwxr-xr-xforum/skins/default/templates/auth/signin.html6
-rwxr-xr-xforum/skins/default/templates/auth/temp_login_email.html20
-rwxr-xr-xforum/skins/default/templates/auth/temp_login_request.html28
-rwxr-xr-xforum/skins/default/templates/base.html2
-rwxr-xr-xforum/skins/default/templates/changepw.html18
-rwxr-xr-xforum/skins/default/templates/email_base.html26
-rwxr-xr-xforum/skins/default/templates/user_info.html6
-rwxr-xr-xforum/templatetags/extra_tags.py25
-rwxr-xr-xforum/urls.py11
-rwxr-xr-xforum/user_messages/context_processors.py3
-rwxr-xr-xforum/utils/email.py21
-rwxr-xr-xforum/utils/forms.py4
-rwxr-xr-xforum/utils/time.py4
-rw-r--r--forum/views/auth.py178
-rwxr-xr-xforum/views/users.py33
23 files changed, 523 insertions, 113 deletions
diff --git a/forum/authentication/base.py b/forum/authentication/base.py
index 08534adc..99005866 100755
--- a/forum/authentication/base.py
+++ b/forum/authentication/base.py
@@ -33,6 +33,10 @@ class ConsumerTemplateContext(object):
extra_css = []
show_to_logged_in_user = True
+ @classmethod
+ def readable_key(cls, key):
+ return key.key
+
class InvalidAuthentication(Exception):
def __init__(self, message):
self.message = message
diff --git a/forum/authentication/forms.py b/forum/authentication/forms.py
index 019c85f3..7fa06b01 100755
--- a/forum/authentication/forms.py
+++ b/forum/authentication/forms.py
@@ -1,7 +1,8 @@
-from forum.utils.forms import NextUrlField, UserNameField, UserEmailField
-from forum.models import EmailFeedSetting, Question
+from forum.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
+from forum.models import EmailFeedSetting, Question, User
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
from django import forms
from forum.forms import EditUserEmailFeedsForm
import logging
@@ -11,6 +12,29 @@ class SimpleRegistrationForm(forms.Form):
username = UserNameField()
email = UserEmailField()
+class TemporaryLoginRequestForm(forms.Form):
+ def __init__(self, data=None):
+ super(TemporaryLoginRequestForm, self).__init__(data)
+ self.user_cache = None
+
+ email = forms.EmailField(
+ required=True,
+ label=_("Your account email"),
+ error_messages={
+ 'required': _("You cannot leave this field blank"),
+ 'invalid': _('please enter a valid email address'),
+ }
+ )
+
+ def clean_email(self):
+ try:
+ user = User.objects.get(email=self.cleaned_data['email'])
+ except:
+ raise forms.ValidationError(_("Sorry, but this email is not on our database."))
+
+ self.user_cache = user
+ return self.cleaned_data['email']
+
class SimpleEmailSubscribeForm(forms.Form):
SIMPLE_SUBSCRIBE_CHOICES = (
@@ -29,3 +53,21 @@ class SimpleEmailSubscribeForm(forms.Form):
else:
email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
email_settings_form.save(user,save_unbound=True)
+
+class ChangePasswordForm(SetPasswordForm):
+ """ change password form """
+ oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}),
+ label=mark_safe(_('Current password')))
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangePasswordForm, self).__init__(data, *args, **kwargs)
+ self.user = user
+
+ def clean_oldpw(self):
+ """ test old password """
+ if not self.user.check_password(self.cleaned_data['oldpw']):
+ raise forms.ValidationError(_("Old password is incorrect. \
+ Please enter the correct password."))
+ return self.cleaned_data['oldpw']
diff --git a/forum/context.py b/forum/context.py
new file mode 100644
index 00000000..b33a1760
--- /dev/null
+++ b/forum/context.py
@@ -0,0 +1,47 @@
+from django.conf import settings
+def application_settings(context):
+ my_settings = {
+ 'APP_TITLE' : settings.APP_TITLE,
+ 'APP_SHORT_NAME' : settings.APP_SHORT_NAME,
+ 'APP_URL' : settings.APP_URL,
+ 'APP_KEYWORDS' : settings.APP_KEYWORDS,
+ 'APP_DESCRIPTION' : settings.APP_DESCRIPTION,
+ 'APP_INTRO' : settings.APP_INTRO,
+ 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION,
+ 'FEEDBACK_SITE_URL': settings.FEEDBACK_SITE_URL,
+ 'FORUM_SCRIPT_ALIAS': settings.FORUM_SCRIPT_ALIAS,
+ 'LANGUAGE_CODE': settings.LANGUAGE_CODE,
+ 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE,
+ 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY,
+ 'WIKI_ON':settings.WIKI_ON,
+ 'USE_EXTERNAL_LEGACY_LOGIN':settings.USE_EXTERNAL_LEGACY_LOGIN,
+ 'RESOURCE_REVISION':settings.RESOURCE_REVISION,
+ 'OSQA_SKIN':settings.OSQA_DEFAULT_SKIN,
+ }
+ return {'settings':my_settings}
+
+def auth_processor(request):
+ """
+ Returns context variables required by apps that use Django's authentication
+ system.
+
+ If there is no 'user' attribute in the request, uses AnonymousUser (from
+ django.contrib.auth).
+ """
+ if hasattr(request, 'user'):
+ user = request.user
+ if user.is_authenticated():
+ messages = user.message_set.all()
+ else:
+ messages = None
+ else:
+ from django.contrib.auth.models import AnonymousUser
+ user = AnonymousUser()
+ messages = None
+
+ from django.core.context_processors import PermWrapper
+ return {
+ 'user': user,
+ 'messages': messages,
+ 'perms': PermWrapper(user),
+ }
diff --git a/forum/forms.py b/forum/forms.py
index 5d4fb516..2260bfe5 100755
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -261,25 +261,7 @@ class TagFilterSelectionForm(forms.ModelForm):
if before != after:
return True
return False
-
-
-class ChangePasswordForm(SetPasswordForm):
- """ change password form """
- oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}),
- label=mark_safe(_('Current password')))
-
- def __init__(self, data=None, user=None, *args, **kwargs):
- if user is None:
- raise TypeError("Keyword argument 'user' must be supplied")
- super(ChangePasswordForm, self).__init__(data, *args, **kwargs)
- self.user = user
-
- def clean_oldpw(self):
- """ test old password """
- if not self.user.check_password(self.cleaned_data['oldpw']):
- raise forms.ValidationError(_("Old password is incorrect. \
- Please enter the correct password."))
- return self.cleaned_data['oldpw']
+
class EditUserEmailFeedsForm(forms.Form):
WN = (('w',_('weekly')),('n',_('no email')))
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
index db66a227..fee52447 100755
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
@@ -2,7 +2,7 @@ from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion
from answer import Answer, AnonymousAnswer, AnswerRevision
from tag import Tag, MarkedTag
from meta import Vote, Comment, FlaggedItem
-from user import Activity, AnonymousEmail, EmailFeedSetting, AuthKeyUserAssociation
+from user import Activity, ValidationHash, EmailFeedSetting, AuthKeyUserAssociation
from repute import Badge, Award, Repute
import re
@@ -34,6 +34,9 @@ def user_get_q_sel_email_feed_frequency(self):
#print 'have freq=%s' % feed_setting.frequency
return feed_setting.frequency
+def user_get_absolute_url(self):
+ return "/users/%d/%s/" % (self.id, (self.username))
+
User.add_to_class('is_approved', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
@@ -68,6 +71,7 @@ User.add_to_class('tag_filter_setting',
default='ignored'
)
)
+User.add_to_class('get_absolute_url', user_get_absolute_url)
# custom signal
tags_updated = django.dispatch.Signal(providing_args=["question"])
@@ -294,7 +298,6 @@ def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs)
aa.publish(user)
#signal for User modle save changes
-
pre_save.connect(calculate_gravatar_hash, sender=User)
post_save.connect(record_ask_event, sender=Question)
post_save.connect(record_answer_event, sender=Answer)
@@ -338,7 +341,7 @@ Repute = Repute
Activity = Activity
EmailFeedSetting = EmailFeedSetting
-AnonymousEmail = AnonymousEmail
+ValidationHash = ValidationHash
AuthKeyUserAssociation = AuthKeyUserAssociation
__all__ = [
@@ -364,7 +367,7 @@ __all__ = [
'Activity',
'EmailFeedSetting',
- 'AnonymousEmail',
+ 'ValidationHash',
'AuthKeyUserAssociation',
'User'
diff --git a/forum/models/user.py b/forum/models/user.py
index bed6f55e..ff0af7fa 100755
--- a/forum/models/user.py
+++ b/forum/models/user.py
@@ -1,6 +1,9 @@
from base import *
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
+from hashlib import md5
+import string
+from random import Random
from django.utils.translation import ugettext as _
@@ -57,19 +60,72 @@ class EmailFeedSetting(models.Model):
class Meta:
app_label = 'forum'
-class AnonymousEmail(models.Model):
- #validation key, if used
- key = models.CharField(max_length=32)
- email = models.EmailField(null=False,unique=True)
- isvalid = models.BooleanField(default=False)
+from forum.utils.time import one_day_from_now
+
+class ValidationHashManager(models.Manager):
+ def _generate_md5_hash(self, user, type, hash_data, seed):
+ return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest()
+
+ def create_new(self, user, type, hash_data=[], expiration=None):
+ seed = ''.join(Random().sample(string.letters+string.digits, 12))
+ hash = self._generate_md5_hash(user, type, hash_data, seed)
+
+ obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type)
+
+ if expiration is not None:
+ obj.expiration = expiration
+
+ try:
+ obj.save()
+ except:
+ return None
+
+ return obj
+
+ def validate(self, hash, user, type, hash_data=[]):
+ try:
+ obj = self.get(hash_code=hash)
+ except:
+ return False
+
+ if obj.type != type:
+ return False
+
+ if obj.user != user:
+ return False
+
+ valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed))
+
+ if valid:
+ if obj.expiration < datetime.datetime.now():
+ obj.delete()
+ return False
+ else:
+ obj.delete()
+ return True
+
+ return False
+
+class ValidationHash(models.Model):
+ hash_code = models.CharField(max_length=256,unique=True)
+ seed = models.CharField(max_length=12)
+ expiration = models.DateTimeField(default=one_day_from_now)
+ type = models.CharField(max_length=12)
+ user = models.ForeignKey(User)
+
+ objects = ValidationHashManager()
class Meta:
+ unique_together = ('user', 'type')
app_label = 'forum'
+ def __str__(self):
+ return self.hash_code
+
class AuthKeyUserAssociation(models.Model):
key = models.CharField(max_length=255,null=False,unique=True)
provider = models.CharField(max_length=64)
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, related_name="auth_keys")
added_at = models.DateTimeField(default=datetime.datetime.now)
class Meta:
diff --git a/forum/skins/default/templates/auth/auth_settings.html b/forum/skins/default/templates/auth/auth_settings.html
new file mode 100755
index 00000000..051fb6ba
--- /dev/null
+++ b/forum/skins/default/templates/auth/auth_settings.html
@@ -0,0 +1,35 @@
+{% extends "base.html" %}
+<!-- changepw.html -->
+{% load i18n %}
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Authentication settings" %}{% endspaceless %}{% endblock %}
+{% block content %}
+<div class="headNormal">{% trans "Authentication settings" %}</div>
+{% if auth_keys %}
+ <p class="message">{% blocktrans %}These are the external authentication providers currently associated with your account.{% endblocktrans %}</p>
+ <div>
+ {% for key in auth_keys %}
+ <p>{{ key.name }} (<a href="{% url user_remove_external_provider id=key.id %}">{% trans "remove" %}</a>)</p>
+ {% endfor %}
+ </div>
+{% endif %}
+{% if not auth_keys %}
+ <p class="message">{% blocktrans %}You currently have no external authentication provider associated with your account.{% endblocktrans %}</p>
+{% endif %}
+<input type="button" class="submit" value="{% trans "Add new provider" %}" onclick="window.location='{% url user_add_external_provider %}'" />
+{% if has_password %}
+ <p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
+{% endif %}
+{% if not has_password %}
+ <p class="message">{% blocktrans %}You can set up a password for your account, so you can login using standard username and password!{% endblocktrans %}</p>
+{% endif %}
+<div class="aligned">
+ <form action="" method="post" accept-charset="utf-8">
+ <ul id="changepw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <div class="submit-row"><input type="submit" class="submit" value="{% if has_password %}{% trans "Change password" %}{% endif %}{% if not has_password %}{% trans "Create password" %}{% endif %}" /></div>
+ </form>
+ </div>
+{% endblock %}
+<!-- end changepw.html -->
diff --git a/forum/skins/default/templates/auth/email_validation.html b/forum/skins/default/templates/auth/email_validation.html
new file mode 100755
index 00000000..a4126a69
--- /dev/null
+++ b/forum/skins/default/templates/auth/email_validation.html
@@ -0,0 +1,20 @@
+{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "To make use of the Forum, please follow the link below:" %}</p>
+
+ <a href="{% fullurl auth_validate_email user=user.id,code=validation_code %}">{% fullurl auth_validate_email user=user.id,code=validation_code %}</a>
+
+ <p>{% trans "Following the link above will help us verify your email address." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/signin.html b/forum/skins/default/templates/auth/signin.html
index d4ee9fc1..478de4a8 100755
--- a/forum/skins/default/templates/auth/signin.html
+++ b/forum/skins/default/templates/auth/signin.html
@@ -73,12 +73,16 @@
<input type="hidden" class="validate_email" name="validate_email" value="yes" />
</form>
{% for provider in stackitem_providers %}
- <h3 class="or_label">Or...</h3>
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
<form class="signin_form" method="POST" action="{% url auth_provider_signin provider=provider.id %}">
{% include provider.stack_item_template %}
<input type="hidden" class="validate_email" name="validate_email" value="yes" />
</form>
{% endfor %}
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
+ <fieldset>
+ {% trans 'Click' %} <a href="{% url auth_local_register %}">here</a> {% trans "if you're having troubles signing in." %}
+ </fieldset>
<script type="text/html" id="simple_form_template">
<fieldset id="slot_form">
<p id="provider_name_slot">{% trans 'Enter your ' %}%%YOUR_WHAT%%</p>
diff --git a/forum/skins/default/templates/auth/temp_login_email.html b/forum/skins/default/templates/auth/temp_login_email.html
new file mode 100755
index 00000000..063608fe
--- /dev/null
+++ b/forum/skins/default/templates/auth/temp_login_email.html
@@ -0,0 +1,20 @@
+{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "You're seeing this because someone requested a temporary login link" %}</p>
+
+ <a href="{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}">{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}</a>
+
+ <p>{% trans "Following the link above will give you access to your account." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/temp_login_request.html b/forum/skins/default/templates/auth/temp_login_request.html
new file mode 100755
index 00000000..772f18fb
--- /dev/null
+++ b/forum/skins/default/templates/auth/temp_login_request.html
@@ -0,0 +1,28 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Request temporary login key" %}{% endspaceless %}{% endblock %}
+{% block content %}
+<div class="headNormal">{% trans "Account: request temporary login key" %}</div>
+<p class="message">{% blocktrans %}
+ If you're experiencing problems accessing your account, or if you forgot your password,
+ here you can request a temporary login key. Fill out your account email and we'll send you a temporary access link that
+ will enable you to access your account. This token is valid only once and for a limited period of time.
+ {% endblocktrans %}</p>
+<div class="aligned">
+ {% if form.errors %}
+ <ul class="errorlist">
+ {% for error in form.errors %}
+ <li>{{ error }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ <form action="" method="post" accept-charset="utf-8">
+ <ul id="changepw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "Send link" %}" /></div>
+ </form>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html
index 3a1848ef..884551e3 100755
--- a/forum/skins/default/templates/base.html
+++ b/forum/skins/default/templates/base.html
@@ -3,7 +3,7 @@
{% load extra_filters %}
{% load extra_tags %}
{% load i18n %}
-<html xmlns="http://www.w3.org/1999/xhtml"{% if fb_api_key %} xmlns:fb="http://www.facebook.com/2008/fbml"{% endif %}>
+<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
{% spaceless %}
diff --git a/forum/skins/default/templates/changepw.html b/forum/skins/default/templates/changepw.html
deleted file mode 100755
index 7b4cf801..00000000
--- a/forum/skins/default/templates/changepw.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "base.html" %}
-<!-- changepw.html -->
-{% load i18n %}
-{% block head %}{% endblock %}
-{% block title %}{% spaceless %}{% trans "Change password" %}{% endspaceless %}{% endblock %}
-{% block content %}
-<div class="headNormal">{% trans "Account: change password" %}</div>
-<p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
-<div class="aligned">
- <form action="" method="post" accept-charset="utf-8">
- <ul id="changepw-form" class="form-horizontal-rows">
- {{form.as_ul}}
- </ul>
- <div class="submit-row"><input type="submit" class="submit" value="{% trans "Change password" %}" /></div>
- </form>
- </div>
-{% endblock %}
-<!-- end changepw.html -->
diff --git a/forum/skins/default/templates/email_base.html b/forum/skins/default/templates/email_base.html
new file mode 100755
index 00000000..b74741e3
--- /dev/null
+++ b/forum/skins/default/templates/email_base.html
@@ -0,0 +1,26 @@
+{% load extra_filters %}
+{% load extra_tags %}
+{% load i18n %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link href="{% fullmedia "/media/style/style.css" %}" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <a href="{% fullurl index %}">
+ <img src="{% fullmedia "/media/images/logo.png" %}" title="{% trans "home" %}" alt="{{settings.APP_TITLE}} logo"/>
+ </a>
+ <br />
+ <p>{{ settings.APP_TITLE }}</p>
+ <br /><br />
+ <div id="wrapper">
+ <div id="room">
+ <div id="CALeft">
+ {% block content%}
+ {% endblock%}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/forum/skins/default/templates/user_info.html b/forum/skins/default/templates/user_info.html
index 0ce4e77a..b0fd246a 100755
--- a/forum/skins/default/templates/user_info.html
+++ b/forum/skins/default/templates/user_info.html
@@ -46,12 +46,8 @@
{% endif %}
{% separator %}
{% ifequal request.user view_user %}
- <a href="{% url auth_signin %}">add authentication method</a>
+ <a href="{% url user_authsettings %}">authentication settings</a>
{% endifequal %}
- {% separator %}
- {% if request.user.has_usable_password %}
- <a href="{% url user_changepw %}">change password</a>
- {% endif %}
{% endjoinitems %}
</td>
</tr>
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 26c52b8d..8fa01629 100755
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -13,6 +13,7 @@ from forum.models import Question, Answer, QuestionRevision, AnswerRevision
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings
+from django.template.defaulttags import url as default_url
from forum import skins
register = template.Library()
@@ -355,3 +356,27 @@ def blockmedia(parser,token):
if next.contents == 'endblockmedia':
break
return BlockMediaUrlNode(nodelist)
+
+class FullUrlNode(template.Node):
+ def __init__(self, default_node):
+ self.default_node = default_node
+
+ def render(self, context):
+ domain = settings.APP_URL
+ protocol = getattr(settings, "PROTOCOL", "http")
+ path = self.default_node.render(context)
+ return "%s://%s%s" % (protocol, domain, path)
+
+@register.tag(name='fullurl')
+def fullurl(parser, token):
+ default_node = default_url(parser, token)
+ return FullUrlNode(default_node)
+
+@register.simple_tag
+def fullmedia(url):
+ domain = settings.APP_URL
+ protocol = getattr(settings, "PROTOCOL", "http")
+ path = media(url)
+ return "%s://%s%s" % (protocol, domain, path)
+
+
diff --git a/forum/urls.py b/forum/urls.py
index f81bad69..59693be7 100755
--- a/forum/urls.py
+++ b/forum/urls.py
@@ -76,7 +76,7 @@ urlpatterns = patterns('',
url(r'^%s$' % _('users/'),app.users.users, name='users'),
url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.users.moderate_user, name='moderate_user'),
url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'),
- url(r'^%s(?P<id>\d+)//*' % _('users/'), app.users.user, name='user'),
+ url(r'^%s(?P<id>\d+)/(?P<slug>\w+)/$' % _('users/'), app.users.user, name='user'),
url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'),
url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'),
url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'),
@@ -96,10 +96,15 @@ urlpatterns = patterns('',
url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('signin/')), app.auth.prepare_provider_signin, name='auth_provider_signin'),
url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('done/')), app.auth.process_provider_signin, name='auth_provider_done'),
url(r'^%s%s$' % (_('account/'), _('register/')), app.auth.external_register, name='auth_external_register'),
+ url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('validate/')), app.auth.validate_email, name="auth_validate_email"),
+ url(r'^%s%s$' % (_('account/'), _('tempsignin/')), app.auth.request_temp_login, name="auth_request_tempsignin"),
+ url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('tempsignin/')), app.auth.temp_signin, name="auth_tempsignin"),
+ url(r'^%s%s$' % (_('account/'), _('authsettings/')), app.auth.auth_settings, name='user_authsettings'),
+ url(r'^%s%s(?P<id>\d+)/%s$' % (_('account/'), _('providers/'), _('remove/')), app.auth.remove_external_provider, name='user_remove_external_provider'),
+ url(r'^%s%s%s$' % (_('account/'), _('providers/'), _('add/')), app.auth.signin_page, name='user_add_external_provider'),
- url(r'^%s%s$' % (_('account/'), _('password/')), app.users.changepw, name='user_changepw'),
#url(r'^%s%s%s$' % (_('accounts/'), _('password/'), _('confirm/')), app.user.confirmchangepw, name='user_confirmchangepw'),
- url(r'^%s$' % _('account/'), app.users.account_settings, name='user_account_settings'),
+ #url(r'^%s$' % _('account/'), app.users.account_settings, name='user_account_settings'),
#url(r'^%s$' % _('delete/'), app.users.delete, name='user_delete'),
)
diff --git a/forum/user_messages/context_processors.py b/forum/user_messages/context_processors.py
index 2bf26269..5f7b857c 100755
--- a/forum/user_messages/context_processors.py
+++ b/forum/user_messages/context_processors.py
@@ -17,6 +17,9 @@ def user_messages (request):
#if request.user.is_authenticated():
#else:
# messages = LazyMessages(request)
+ #import inspect
+ #print inspect.stack()[1]
+ #print messages
return { 'user_messages': messages }
class LazyMessages (StrAndUnicode):
diff --git a/forum/utils/email.py b/forum/utils/email.py
new file mode 100755
index 00000000..a6ea1087
--- /dev/null
+++ b/forum/utils/email.py
@@ -0,0 +1,21 @@
+from django.core.mail import send_mail, EmailMultiAlternatives
+from django.conf import settings
+from django.template import loader, Context
+from django.utils.html import strip_tags
+from threading import Thread
+
+def send_email(subject, recipients, template, context={}, sender=settings.DEFAULT_FROM_EMAIL, txt_template=None):
+ context['settings'] = settings
+ html_body = loader.get_template(template).render(Context(context))
+
+ if txt_template is None:
+ txt_body = strip_tags(html_body)
+ else:
+ txt_body = loader.get_template(txt_template).render(Context(context))
+
+ msg = EmailMultiAlternatives(subject, txt_body, sender, recipients)
+ msg.attach_alternative(html_body, "text/html")
+
+ thread = Thread(target=EmailMultiAlternatives.send, args=[msg])
+ thread.setDaemon(True)
+ thread.start()
diff --git a/forum/utils/forms.py b/forum/utils/forms.py
index c54056ca..c8305c7c 100755
--- a/forum/utils/forms.py
+++ b/forum/utils/forms.py
@@ -133,6 +133,10 @@ class SetPasswordForm(forms.Form):
error_messages={'required':_('please, retype your password'),
'nomatch':_('sorry, entered passwords did not match, please try again')},
)
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ super(SetPasswordForm, self).__init__(data, *args, **kwargs)
+
def clean_password2(self):
"""
Validates that the two password inputs match.
diff --git a/forum/utils/time.py b/forum/utils/time.py
new file mode 100755
index 00000000..5fd15e24
--- /dev/null
+++ b/forum/utils/time.py
@@ -0,0 +1,4 @@
+import datetime
+
+def one_day_from_now():
+ return datetime.datetime.now() + datetime.timedelta(days=1)
diff --git a/forum/views/auth.py b/forum/views/auth.py
index d885440b..74f7af4b 100644
--- a/forum/views/auth.py
+++ b/forum/views/auth.py
@@ -1,17 +1,21 @@
-from django.shortcuts import render_to_response
+from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
-from django.http import HttpResponseRedirect
+from django.http import HttpResponseRedirect, Http404
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
+from django.utils.http import urlquote_plus
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, logout
from django.http import get_host
import types
+import datetime
-from forum.models import AuthKeyUserAssociation
-from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm
+from forum.models import AuthKeyUserAssociation, ValidationHash
+from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm, \
+ TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
+from forum.utils.email import send_email
from forum.authentication.base import InvalidAuthentication
from forum.authentication import AUTH_PROVIDERS
@@ -105,7 +109,9 @@ def process_provider_signin(request, provider):
except:
uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
uassoc.save()
- request.session['auth_error'] = _("These new credentials are now associated with your account.")
+ request.user.message_set.create(message=_('The new credentials are now associated with your account'))
+ return HttpResponseRedirect(reverse('user_authsettings'))
+
return HttpResponseRedirect(reverse('auth_signin'))
else:
if isinstance(assoc_key, (type, User)):
@@ -128,12 +134,15 @@ def external_register(request):
email_feeds_form = SimpleEmailSubscribeForm(request.POST)
if (form1.is_valid() and email_feeds_form.is_valid()):
- tmp_pwd = User.objects.make_random_password()
- user_ = User.objects.create_user(form1.cleaned_data['username'],
- form1.cleaned_data['email'], tmp_pwd)
-
+ user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
+ user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
user_.set_unusable_password()
+ user_.save()
+
+ if not user_.email_isvalid:
+ send_validation_email(user_)
+
try:
assoc_key = request.session['assoc_key']
auth_provider = request.session['auth_provider']
@@ -148,7 +157,11 @@ def external_register(request):
del request.session['assoc_key']
del request.session['auth_provider']
- return login_and_forward(request, user_)
+
+ if user_.email_isvalid:
+ return login_and_forward(request, user_)
+ else:
+ return HttpResponseRedirect(reverse('index'))
else:
provider_class = AUTH_PROVIDERS[request.session['auth_provider']].consumer
user_data = provider_class.get_user_data(request.session['assoc_key'])
@@ -159,6 +172,9 @@ def external_register(request):
if not email:
email = request.session.get('auth_email_request', '')
+ if email:
+ request.session['auth_validated_email'] = email
+
form1 = SimpleRegistrationForm(initial={
'next': '/',
'username': username,
@@ -176,6 +192,119 @@ def external_register(request):
'gravatar_faq_url':reverse('faq') + '#gravatar',
}, context_instance=RequestContext(request))
+def request_temp_login(request):
+ if request.method == 'POST':
+ form = TemporaryLoginRequestForm(request.POST)
+
+ if form.is_valid():
+ user = form.user_cache
+
+ try:
+ hash = get_object_or_404(ValidationHash, user=user, type='templogin')
+ if hash.expiration < datetime.datetime.now():
+ hash.delete()
+ return request_temp_login(request)
+ except:
+ hash = ValidationHash.objects.create_new(user, 'templogin', [user.id])
+
+ send_email(_("Temporary login link"), [user.email], "auth/temp_login_email.html", {
+ 'temp_login_code': hash,
+ 'user': user
+ })
+
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ form = TemporaryLoginRequestForm()
+
+ return render_to_response(
+ 'auth/temp_login_request.html', {'form': form},
+ context_instance=RequestContext(request))
+
+def temp_signin(request, user, code):
+ user = get_object_or_404(User, id=user)
+
+ if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
+ print user.get_absolute_url()
+ return login_and_forward(request, user, reverse('user_authsettings'),
+ _("You are logged in with a temporary access key, please take the time to fix your issue with authentication."))
+ else:
+ raise Http404()
+
+def send_validation_email(user):
+ hash = ValidationHash.objects.create_new(user, 'email', [user.email])
+ send_email(_("Email Validation"), [user.email], "auth/email_validation.html", {
+ 'validation_code': hash,
+ 'user': user
+ })
+
+def validate_email(request, user, code):
+ user = get_object_or_404(User, id=user)
+
+ if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
+ user.email_isvalid = True
+ user.save()
+ return login_and_forward(request, user, None, _("Thank you, your email is now validated."))
+ else:
+ raise Http404()
+
+@login_required
+def auth_settings(request):
+ """
+ change password view.
+
+ url : /changepw/
+ template: authopenid/changepw.html
+ """
+ user_ = request.user
+ auth_keys = user_.auth_keys.all()
+
+ if user_.has_usable_password():
+ FormClass = ChangePasswordForm
+ else:
+ FormClass = SetPasswordForm
+
+ if request.POST:
+ form = FormClass(request.POST, user=user_)
+ if form.is_valid():
+ if user_.has_usable_password():
+ request.user.message_set.create(message=_("Your password was changed"))
+ else:
+ request.user.message_set.create(message=_("New password set"))
+
+ user_.set_password(form.cleaned_data['password1'])
+ user_.save()
+ return HttpResponseRedirect(reverse('user_authsettings'))
+ else:
+ form = FormClass(user=user_)
+
+ auth_keys_list = []
+
+ for k in auth_keys:
+ provider = AUTH_PROVIDERS.get(k.provider, None)
+
+ if provider is not None:
+ name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
+ else:
+ from forum.authentication.base import ConsumerTemplateContext
+ "unknown: %s" % ConsumerTemplateContext.readable_key(k)
+
+ auth_keys_list.append({
+ 'name': name,
+ 'id': k.id
+ })
+
+ return render_to_response('auth/auth_settings.html', {
+ 'form': form,
+ 'has_password': user_.has_usable_password(),
+ 'auth_keys': auth_keys_list,
+ }, context_instance=RequestContext(request))
+
+def remove_external_provider(request, id):
+ association = get_object_or_404(AuthKeyUserAssociation, id=id)
+ request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
+ association.delete()
+ return HttpResponseRedirect(reverse('user_authsettings'))
+
def newquestion_signin_action(user):
question = Question.objects.filter(author=user).order_by('-added_at')[0]
return question.get_absolute_url()
@@ -189,7 +318,7 @@ POST_SIGNIN_ACTIONS = {
'newanswer': newanswer_signin_action,
}
-def login_and_forward(request, user):
+def login_and_forward(request, user, forward=None, message=None):
old_session = request.session.session_key
user.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user)
@@ -197,19 +326,24 @@ def login_and_forward(request, user):
from forum.models import user_logged_in
user_logged_in.send(user=user,session_key=old_session,sender=None)
- signin_action = request.session.get('on_signin_action', None)
- if not signin_action:
- redirect = request.session.get('on_signin_url', None)
+ if not forward:
+ signin_action = request.session.get('on_signin_action', None)
+ if not signin_action:
+ forward = request.session.get('on_signin_url', None)
- if not redirect:
- redirect = reverse('index')
- else:
- try:
- redirect = POST_SIGNIN_ACTIONS[signin_action](user)
- except:
- redirect = reverse('index')
+ if not forward:
+ forward = reverse('index')
+ else:
+ try:
+ forward = POST_SIGNIN_ACTIONS[signin_action](user)
+ except:
+ forward = reverse('index')
+
+ if message is None:
+ message = _("Welcome back %s, you are now logged in") % user.username
- return HttpResponseRedirect(redirect)
+ request.user.message_set.create(message=message)
+ return HttpResponseRedirect(forward)
@login_required
def signout(request):
diff --git a/forum/views/users.py b/forum/views/users.py
index baa8090b..ff92803c 100755
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -947,44 +947,13 @@ USER_TEMPLATE_VIEWS = (
)
)
-def user(request, id):
+def user(request, id, slug=None):
sort = request.GET.get('sort', 'stats')
user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
from forum.views import users
func = user_view.view_func
return func(request, id, user_view)
-
-@login_required
-def changepw(request):
- """
- change password view.
-
- url : /changepw/
- template: authopenid/changepw.html
- """
- logging.debug('')
- user_ = request.user
-
- if not user_.has_usable_password():
- raise Http404
-
- if request.POST:
- form = ChangePasswordForm(request.POST, user=user_)
- if form.is_valid():
- user_.set_password(form.cleaned_data['password1'])
- user_.save()
- msg = _("Password changed.")
- redirect = "%s?msg=%s" % (
- reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
- else:
- form = ChangePasswordForm(user=user_)
-
- return render_to_response('changepw.html', {'form': form },
- context_instance=RequestContext(request))
-
@login_required
def account_settings(request):
"""