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;
}
#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><a href="{{ server_uri }}/create_realm">{{ _('Create new organization') }}</a></li>
{% 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>
</div>
</div>

View File

@ -60,6 +60,7 @@ def add_settings(request):
'github_auth_enabled': github_auth_enabled(realm),
'development_environment': settings.DEVELOPMENT,
'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
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import validate_email
from django.db.models.query import QuerySet
from django.utils.translation import ugettext as _
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)))
raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))
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'),
terms=get_form_value(True),
email=get_form_value(email),
emails=get_form_value(email),
),
current_url=lambda: 'www.zulip.com',
hubot_lozenges_dict={},

View File

@ -2304,3 +2304,47 @@ class TestLoginPage(ZulipTestCase):
# type: () -> None
result = self.client_get("/login/?subdomain=1")
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.utils.translation import ugettext as _
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 typing import Any, Dict, Optional, Tuple, Text
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.response import json_success, json_error
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
from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled
from zproject.jinja2 import render_to_response
from zerver.lib.notifications import send_future_email
import hashlib
import hmac
@ -34,6 +40,61 @@ import logging
import requests
import time
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=''):
# type: (HttpRequest, Text, Text) -> HttpResponse

View File

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

View File

@ -131,6 +131,7 @@ i18n_urls = [
name='landing-page'),
url(r'^new-user/$', RedirectView.as_view(url='/hello', permanent=True)),
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