diff options
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/email_verification.py | 55 | ||||
-rw-r--r-- | yaksh/forms.py | 24 | ||||
-rw-r--r-- | yaksh/middleware/user_time_zone.py | 4 | ||||
-rw-r--r-- | yaksh/migrations/0005_auto_20170407_1048.py | 30 | ||||
-rw-r--r-- | yaksh/models.py | 3 | ||||
-rw-r--r-- | yaksh/templates/yaksh/activation_status.html | 47 | ||||
-rw-r--r-- | yaksh/urls.py | 2 | ||||
-rw-r--r-- | yaksh/views.py | 85 |
8 files changed, 231 insertions, 19 deletions
diff --git a/yaksh/email_verification.py b/yaksh/email_verification.py new file mode 100644 index 0000000..4eded1c --- /dev/null +++ b/yaksh/email_verification.py @@ -0,0 +1,55 @@ +# Local imports +try: + from string import letters +except ImportError: + from string import ascii_letters as letters +from string import digits, punctuation +# Local imports +import hashlib +from random import randint +from textwrap import dedent + +# Django imports +from django.utils.crypto import get_random_string +from django.core.mail import send_mail +from django.conf import settings + +def generate_activation_key(username): + """ Generate hashed secret key for email activation """ + chars = letters + digits + punctuation + secret_key = get_random_string(randint(10, 40), chars) + return hashlib.sha256((secret_key + username).encode('utf-8')).hexdigest() + +def send_user_mail(user_mail, key): + """ Send mail to user whose email is to be verified + This function should get two args i.e user_email and secret_key. + The activation url is generated from settings.PRODUCTION_URL and key. + """ + try: + to = user_mail + subject = "Yaksh Email Verification" + msg = dedent("""\ + To activate your account and verify your email address, + please click the following link: + {0}/exam/activate/{1} + If clicking the link above does not work, + copy and paste the URL in a new browser window instead. + For any issue, please write us on {2} + + Regards + Yaksh Team + """.format(settings.PRODUCTION_URL, key, settings.REPLY_EMAIL) + ) + + send_mail(subject, msg, settings.SENDER_EMAIL, [to]) + msg = "An activation link is sent to your registered email.\ + Please activate the link within 20 minutes." + success = True + except Exception as exc_msg: + msg = """Error: {0}. Please check your email address.\ + If email address is correct then + Please contact your \ + instructor/administrator.""".format(exc_msg) + success = False + + return success, msg diff --git a/yaksh/forms.py b/yaksh/forms.py index f7f7a10..ac4e1b0 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -5,7 +5,7 @@ from yaksh.models import get_model_class, Profile, Quiz, Question, TestCase, Cou from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType - +from django.conf import settings from taggit.managers import TaggableManager from taggit.forms import TagField from django.forms.models import inlineformset_factory @@ -18,6 +18,7 @@ except ImportError: from string import punctuation, digits import datetime import pytz +from .email_verification import generate_activation_key languages = ( ("select", "Select Language"), @@ -117,6 +118,12 @@ class UserRegisterForm(forms.Form): return c_pwd + def clean_email(self): + user_email = self.cleaned_data['email'] + if User.objects.filter(email=user_email).exists(): + raise forms.ValidationError("This email already exists") + return user_email + def save(self): u_name = self.cleaned_data["username"] u_name = u_name.lower() @@ -135,9 +142,19 @@ class UserRegisterForm(forms.Form): new_profile.department = cleaned_data["department"] new_profile.position = cleaned_data["position"] new_profile.timezone = cleaned_data["timezone"] + if settings.IS_DEVELOPMENT: + new_profile.is_email_verified = True + new_profile.save() + return u_name, pwd + else: + new_profile.activation_key = generate_activation_key(new_user.username) + new_profile.key_expiry_time = datetime.datetime.strftime( + datetime.datetime.now() + \ + datetime.timedelta(minutes=20), + "%Y-%m-%d %H:%M:%S" + ) new_profile.save() - - return u_name, pwd + return new_user.email, new_profile.activation_key class UserLoginForm(forms.Form): @@ -307,4 +324,3 @@ class QuestionPaperForm(forms.ModelForm): class Meta: model = QuestionPaper fields = ['shuffle_questions'] - diff --git a/yaksh/middleware/user_time_zone.py b/yaksh/middleware/user_time_zone.py index 0bd4831..ff9ec5c 100644 --- a/yaksh/middleware/user_time_zone.py +++ b/yaksh/middleware/user_time_zone.py @@ -5,10 +5,10 @@ from django.utils import timezone class TimezoneMiddleware(object): """ Middleware to get user's timezone and activate timezone - if user timezone is not available default value 'UTC' is activated """ + if user timezone is not available default value 'Asia/Kolkata' is activated """ def process_request(self, request): user = request.user - user_tz = 'UTC' + user_tz = 'Asia/Kolkata' if hasattr(user, 'profile'): if user.profile.timezone: user_tz = user.profile.timezone diff --git a/yaksh/migrations/0005_auto_20170407_1048.py b/yaksh/migrations/0005_auto_20170407_1048.py new file mode 100644 index 0000000..de7e2c2 --- /dev/null +++ b/yaksh/migrations/0005_auto_20170407_1048.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2017-04-07 10:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('yaksh', '0004_auto_20170331_0632'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='activation_key', + field=models.CharField(blank=True, max_length=40, null=True), + ), + migrations.AddField( + model_name='profile', + name='is_email_verified', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='profile', + name='key_expiry_time', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/yaksh/models.py b/yaksh/models.py index 6646615..77b5ec4 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -252,6 +252,9 @@ class Profile(models.Model): default=pytz.utc.zone, choices=[(tz, tz) for tz in pytz.common_timezones] ) + is_email_verified = models.BooleanField(default=False) + activation_key = models.CharField(max_length=40, blank=True, null=True) + key_expiry_time = models.DateTimeField(blank=True, null=True) def get_user_dir(self): """Return the output directory for the user.""" diff --git a/yaksh/templates/yaksh/activation_status.html b/yaksh/templates/yaksh/activation_status.html new file mode 100644 index 0000000..4ffd50e --- /dev/null +++ b/yaksh/templates/yaksh/activation_status.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block pagetitle %} Yaksh Account Activation {% endblock %} +{% block title %} Yaksh Account Activation {% endblock %} +{% block content %} +{% if success %} + <center> + <div class="alert alert-success" role="alert"> + <strong> {{ msg }} </strong> + </div> + <a href="{{URL_ROOT}}/exam/"> Click Here to Login </a> + </center> +{% else %} + {% if msg %} + <center> + <div class="alert alert-warning" role="alert"> + <strong> {{ msg }} </strong> + </div> + </center> + <form action="{{ URL_ROOT }}/exam/new_activation/" method="post"> + {% csrf_token %} + <center> + Enter Email Address for verification: <input type="email" name="email" required><br><br> + <button class="btn" type="submit">Resend Email</button> + <button class="btn" type="button" name="button" + onClick='location.replace("{{URL_ROOT}}/exam/");'>Cancel</button> + </center> + </form> + {% endif %} +{% endif %} +<br/> +{% if new_activation_msg %} + <center> + <div class="alert alert-info" role="alert"> + <strong> {{ new_activation_msg }} </strong> + </div> + <a href="{{URL_ROOT}}/exam/"> Click Here to Login </a></center> +{% endif %} +{% if activation_msg %} + <center> + <div class="alert alert-info" role="alert"> + <strong> {{ activation_msg }} </strong> + </div> + <a href="{{URL_ROOT}}/exam/"> Click Here to Login </a></center> +{% endif %} + +{% endblock content %}
\ No newline at end of file diff --git a/yaksh/urls.py b/yaksh/urls.py index 8ddfe67..fae8204 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -5,6 +5,8 @@ urlpatterns = [ url(r'^$', views.index), url(r'^login/$', views.user_login, name='login'), url(r'^logout/$', views.user_logout), + url(r'^activate/(?P<key>.+)$', views.activate_user), + url(r'^new_activation/$', views.new_activation), url(r'^quizzes/$', views.quizlist_user, name='quizlist_user'), url(r'^quizzes/(?P<enrolled>\w+)/$', views.quizlist_user, name='quizlist_user'), url(r'^results/$', views.results_user), diff --git a/yaksh/views.py b/yaksh/views.py index 44f06b1..fa72008 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1,7 +1,7 @@ import random import string import os -from datetime import datetime +from datetime import datetime, timedelta import collections import csv from django.http import HttpResponse @@ -17,11 +17,13 @@ from django.contrib.auth.models import Group from django.forms.models import inlineformset_factory from django.utils import timezone from django.core.exceptions import MultipleObjectsReturned +from django.conf import settings import pytz from taggit.models import Tag from itertools import chain import json import six +from textwrap import dedent import zipfile try: from StringIO import StringIO as string_io @@ -40,6 +42,7 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ from .settings import URL_ROOT from yaksh.models import AssignmentUpload from .file_utils import extract_files +from .email_verification import send_user_mail, generate_activation_key @@ -78,7 +81,7 @@ def index(request): """ user = request.user if user.is_authenticated(): - if user.groups.filter(name='moderator').count() > 0: + if is_moderator(user): return my_redirect('/exam/manage/') return my_redirect("/exam/quizzes/") @@ -93,15 +96,20 @@ def user_register(request): ci = RequestContext(request) if user.is_authenticated(): return my_redirect("/exam/quizzes/") - + context = {} if request.method == "POST": form = UserRegisterForm(request.POST) if form.is_valid(): data = form.cleaned_data - u_name, pwd = form.save() - new_user = authenticate(username=u_name, password=pwd) - login(request, new_user) - return my_redirect("/exam/quizzes/") + if settings.IS_DEVELOPMENT: + u_name, pwd = form.save() + new_user = authenticate(username=u_name, password=pwd) + login(request, new_user) + return index(request) + user_email, key = form.save() + success, msg = send_user_mail(user_email, key) + context = {'activation_msg': msg} + return my_render_to_response('yaksh/activation_status.html', context) else: return my_render_to_response('yaksh/register.html', {'form': form}, context_instance=ci) @@ -330,19 +338,29 @@ def user_login(request): user = request.user ci = RequestContext(request) + context = {} if user.is_authenticated(): - if user.groups.filter(name='moderator').count() > 0: - return my_redirect('/exam/manage/') - return my_redirect("/exam/quizzes/") + if not settings.IS_DEVELOPMENT: + if not user.profile.is_email_verified: + context['success'] = False + context['msg'] = "Your account is not verified" + return my_render_to_response('yaksh/activation_status.html', + context, context_instance=ci) + return index(request) if request.method == "POST": form = UserLoginForm(request.POST) if form.is_valid(): user = form.cleaned_data + if not settings.IS_DEVELOPMENT: + if not user.profile.is_email_verified: + context['success'] = False + context['msg'] = "Your account is not verified. \ + Please verify your account" + return my_render_to_response('yaksh/activation_status.html', + context, context_instance=ci) login(request, user) - if user.groups.filter(name='moderator').count() > 0: - return my_redirect('/exam/manage/') - return my_redirect('/exam/login/') + return index(request) else: context = {"form": form} return my_render_to_response('yaksh/login.html', context, @@ -1441,6 +1459,47 @@ def download_course_csv(request, course_id): writer.writerow(student) return response +def activate_user(request, key): + ci = RequestContext(request) + profile = get_object_or_404(Profile, activation_key=key) + context = {} + context['success'] = False + if timezone.now() > profile.key_expiry_time: + context['msg'] = dedent(""" + Your activation time expired. + Please try again. + """) + else: + context['success'] = True + profile.is_email_verified = True + profile.save() + context['msg'] = "Your account is activated" + return my_render_to_response('yaksh/activation_status.html', context, + context_instance=ci) + +def new_activation(request): + ci = RequestContext(request) + context = {} + if request.method == "POST": + email = request.POST.get('email') + user = get_object_or_404(User, email=email) + if not user.profile.is_email_verified: + user.profile.activation_key = generate_activation_key(user.username) + user.profile.key_expiry_time = datetime.strftime( + datetime.now() + \ + timedelta(minutes=20), "%Y-%m-%d %H:%M:%S" + ) + user.profile.save() + success, msg = send_user_mail(user.email, user.profile.activation_key) + if success: + context['new_activation_msg'] = msg + else: + context['msg'] = msg + else: + context['new_activation_msg'] = "Your account is already verified" + + return my_render_to_response('yaksh/activation_status.html', context, + context_instance=ci) @login_required def download_assignment_file(request, quiz_id, question_id=None, user_id=None): |