Add Find My Team feature.

This commit is contained in:
Umair Khan 2016-12-20 14:41:46 +05:00 committed by Tim Abbott
parent 268770489b
commit f208813ea3
12 changed files with 219 additions and 2 deletions

View File

@ -1415,3 +1415,12 @@ label.label-title {
.float-right { .float-right {
float: right; float: right;
} }
#find_my_team .form-control {
height: 30px;
}
#find_my_team .btn {
height: 40px;
border-radius: 5px;
}

View File

@ -0,0 +1,8 @@
Hello {{ user_profile.full_name }},
You can log into Zulip using {{ user_profile.email }} on following organization:
- {{ user_profile.realm.name }} through {{ user_profile.realm.uri }}
Cheers,
The Zulip Team

View File

@ -0,0 +1,9 @@
<p>Hello {{ user_profile.full_name }},<p>
<p>You can log into Zulip using {{ user_profile.email }} on following organization:</p>
<p><a href="{{ user_profile.realm.uri }}">{{ user_profile.realm.name }}</a></p>
<p>Cheers,
<br>
The Zulip Team</p>

View File

@ -0,0 +1,48 @@
{% extends "zerver/portico.html" %}
{% block portico_content %}
<div class="app find-team-page">
<div class="app-main find-team-page-container">
<p class="lead">
<h3 class="find-team-page-header">{{ _("Find your team") }}…</h3>
</p>
{% if emails %}
<div id="results">
<p>
We have checked the following email address(es). Any email address
not associated with Zulip was not sent the sign-in information.
</p>
<ul>
{% for email in emails %}
<li>{{ email }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="find-team-form">
<p>
We will send you an email with the sign-in information against
any email address associated with Zulip.
</p>
<form class="form-inline" id="find_my_team" name="email_form"
action="{{ current_url() }}" method="post">
{{ csrf_input }}
<input type="text" class="form-control required"
placeholder="{{ _("Enter email addresses") }}"
id="emails" name="emails"/>
<button type="submit" class="btn btn-primary">{{ _('Find team') }}</button>
<div><i>{{ form.emails.help_text }}</i></div>
</form>
<div id="errors"></div>
{% if form.emails.errors %}
{% for error in form.emails.errors %}
<div class="alert alert-error">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
{% endif %}
</div>
<div class="footer-padder"></div>
</div>
{% endblock %}

View File

@ -84,6 +84,11 @@
<li><span class="little-bullet">·</span></li> <li><span class="little-bullet">·</span></li>
<li><a href="{{ server_uri }}/create_realm">{{ _('Create new organization') }}</a></li> <li><a href="{{ server_uri }}/create_realm">{{ _('Create new organization') }}</a></li>
{% endif %} {% endif %}
{% if find_team_link_disabled %}
{% else %}
<li><span class="little-bullet">·</span></li>
<li><a href="{{ server_uri }}/find-my-team">{{ _('Find my team') }}</a></li>
{% endif %}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -60,6 +60,7 @@ def add_settings(request):
'github_auth_enabled': github_auth_enabled(realm), 'github_auth_enabled': github_auth_enabled(realm),
'development_environment': settings.DEVELOPMENT, 'development_environment': settings.DEVELOPMENT,
'support_email': settings.ZULIP_ADMINISTRATOR, 'support_email': settings.ZULIP_ADMINISTRATOR,
'find_team_link_disabled': settings.FIND_TEAM_LINK_DISABLED,
} }

View File

@ -6,6 +6,7 @@ from django.contrib.auth.forms import SetPasswordForm, AuthenticationForm, \
PasswordResetForm PasswordResetForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.validators import validate_email
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from jinja2 import Markup as mark_safe from jinja2 import Markup as mark_safe
@ -201,3 +202,31 @@ Please contact %s to reactivate this group.""" % (
(user_profile.email, get_subdomain(self.request))) (user_profile.email, get_subdomain(self.request)))
raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR)) raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))
return email return email
class MultiEmailField(forms.Field):
def to_python(self, emails):
# type: (Text) -> List[Text]
"""Normalize data to a list of strings."""
if not emails:
return []
return [email.strip() for email in emails.split(',')]
def validate(self, emails):
# type: (List[Text]) -> None
"""Check if value consists only of valid emails."""
super(MultiEmailField, self).validate(emails)
for email in emails:
validate_email(email)
class FindMyTeamForm(forms.Form):
emails = MultiEmailField(
help_text="Add up to 10 comma-separated email addresses.")
def clean_emails(self):
# type: () -> List[Text]
emails = self.cleaned_data['emails']
if len(emails) > 10:
raise forms.ValidationError("Please enter at most 10 emails.")
return emails

View File

@ -154,6 +154,7 @@ class TemplateTestCase(ZulipTestCase):
full_name=get_form_value('John Doe'), full_name=get_form_value('John Doe'),
terms=get_form_value(True), terms=get_form_value(True),
email=get_form_value(email), email=get_form_value(email),
emails=get_form_value(email),
), ),
current_url=lambda: 'www.zulip.com', current_url=lambda: 'www.zulip.com',
hubot_lozenges_dict={}, hubot_lozenges_dict={},

View File

@ -2304,3 +2304,47 @@ class TestLoginPage(ZulipTestCase):
# type: () -> None # type: () -> None
result = self.client_get("/login/?subdomain=1") result = self.client_get("/login/?subdomain=1")
self.assertIn(WRONG_SUBDOMAIN_ERROR, result.content.decode('utf8')) self.assertIn(WRONG_SUBDOMAIN_ERROR, result.content.decode('utf8'))
class FindMyTeamTestCase(ZulipTestCase):
def test_template(self):
# type: () -> None
result = self.client_get('/find-my-team/')
self.assertIn("Find your team", result.content.decode('utf8'))
def test_result(self):
# type: () -> None
url = '/find-my-team/?emails=iago@zulip.com,cordelia@zulip.com'
result = self.client_get(url)
content = result.content.decode('utf8')
self.assertIn("We have checked the following email address(es)", content)
self.assertIn("iago@zulip.com", content)
self.assertIn("cordelia@zulip.com", content)
def test_find_team_zero_emails(self):
# type: () -> None
data = {'emails': ''}
result = self.client_post('/find-my-team/', data)
self.assertIn('This field is required', result.content.decode('utf8'))
self.assertEqual(result.status_code, 200)
def test_find_team_one_email(self):
# type: () -> None
data = {'emails': 'hamlet@zulip.com'}
result = self.client_post('/find-my-team/', data)
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, '/find-my-team/?emails=hamlet%40zulip.com')
def test_find_team_multiple_emails(self):
# type: () -> None
data = {'emails': 'hamlet@zulip.com,iago@zulip.com'}
result = self.client_post('/find-my-team/', data)
self.assertEqual(result.status_code, 302)
expected = '/find-my-team/?emails=hamlet%40zulip.com%2Ciago%40zulip.com'
self.assertEqual(result.url, expected)
def test_find_team_more_than_ten_emails(self):
# type: () -> None
data = {'emails': ','.join(['hamlet-{}@zulip.com'.format(i) for i in range(11)])}
result = self.client_post('/find-my-team/', data)
self.assertEqual(result.status_code, 200)
self.assertIn("Please enter at most 10", result.content.decode('utf8'))

View File

@ -12,12 +12,17 @@ from django.shortcuts import redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core import signing from django.core import signing
from typing import Text from django.template import loader
from django.core.validators import validate_email
from django import forms
from django.core.mail import send_mail
from six.moves import urllib from six.moves import urllib
from typing import Any, Dict, Optional, Tuple, Text from typing import Any, Dict, Optional, Tuple, Text
from confirmation.models import Confirmation from confirmation.models import Confirmation
from zerver.forms import HomepageForm, OurAuthenticationForm, WRONG_SUBDOMAIN_ERROR from zerver.forms import HomepageForm, OurAuthenticationForm, \
WRONG_SUBDOMAIN_ERROR, FindMyTeamForm
from zerver.lib.request import REQ, has_request_variables, JsonableError from zerver.lib.request import REQ, has_request_variables, JsonableError
from zerver.lib.response import json_success, json_error from zerver.lib.response import json_success, json_error
from zerver.lib.utils import get_subdomain from zerver.lib.utils import get_subdomain
@ -26,6 +31,7 @@ from zerver.views import create_preregistration_user, get_realm_from_request, \
redirect_and_log_into_subdomain redirect_and_log_into_subdomain
from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled
from zproject.jinja2 import render_to_response from zproject.jinja2 import render_to_response
from zerver.lib.notifications import send_future_email
import hashlib import hashlib
import hmac import hmac
@ -34,6 +40,61 @@ import logging
import requests import requests
import time import time
import ujson import ujson
import datetime
from typing import Text
try:
import mailer
send_mail = mailer.send_mail
except ImportError:
# no mailer app present, stick with default
pass
def send_find_my_team_emails(user_profile):
# type: (UserProfile) -> None
text_template = 'zerver/emails/find_team/find_team_email.txt'
html_template = 'zerver/emails/find_team/find_team_email_html.txt'
context = {'user_profile': user_profile}
text_content = loader.render_to_string(text_template, context)
html_content = loader.render_to_string(html_template, context)
sender = settings.NOREPLY_EMAIL_ADDRESS
recipients = [user_profile.email]
subject = "Your Zulip Team"
send_mail(subject, text_content, sender, recipients, html_message=html_content)
def find_my_team(request):
# type: (HttpRequest) -> HttpResponse
url = reverse('find-my-team')
emails = [] # type: List[Text]
if request.method == 'POST':
form = FindMyTeamForm(request.POST)
if form.is_valid():
emails = form.cleaned_data['emails']
for user_profile in UserProfile.objects.filter(email__in=emails):
send_find_my_team_emails(user_profile)
# Note: Show all the emails in the result otherwise this
# feature can be used to ascertain which email addresses
# are associated with Zulip.
data = urllib.parse.urlencode({'emails': ','.join(emails)})
return redirect(url + "?" + data)
else:
form = FindMyTeamForm()
result = request.GET.get('emails')
if result:
for email in result.split(','):
try:
validate_email(email)
emails.append(email)
except forms.ValidationError:
pass
return render_to_response('zerver/find_my_team.html',
{'form': form, 'current_url': lambda: url,
'emails': emails},
request=request)
def maybe_send_to_registration(request, email, full_name=''): def maybe_send_to_registration(request, email, full_name=''):
# type: (HttpRequest, Text, Text) -> HttpResponse # type: (HttpRequest, Text, Text) -> HttpResponse

View File

@ -157,6 +157,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
'REGISTER_LINK_DISABLED': False, 'REGISTER_LINK_DISABLED': False,
'LOGIN_LINK_DISABLED': False, 'LOGIN_LINK_DISABLED': False,
'ABOUT_LINK_DISABLED': False, 'ABOUT_LINK_DISABLED': False,
'FIND_TEAM_LINK_DISABLED': True,
'CUSTOM_LOGO_URL': None, 'CUSTOM_LOGO_URL': None,
'VERBOSE_SUPPORT_OFFERS': False, 'VERBOSE_SUPPORT_OFFERS': False,
'STATSD_HOST': '', 'STATSD_HOST': '',

View File

@ -131,6 +131,7 @@ i18n_urls = [
name='landing-page'), name='landing-page'),
url(r'^new-user/$', RedirectView.as_view(url='/hello', permanent=True)), url(r'^new-user/$', RedirectView.as_view(url='/hello', permanent=True)),
url(r'^features/$', TemplateView.as_view(template_name='zerver/features.html')), url(r'^features/$', TemplateView.as_view(template_name='zerver/features.html')),
url(r'^find-my-team/$', zerver.views.auth.find_my_team, name='find-my-team'),
] ]
# If a Terms of Service is supplied, add that route # If a Terms of Service is supplied, add that route