Skip to content
This repository has been archived by the owner on Oct 7, 2021. It is now read-only.

Commit

Permalink
following feature complete
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbe committed Dec 15, 2011
1 parent f12cb37 commit c847b43
Show file tree
Hide file tree
Showing 27 changed files with 1,094 additions and 102 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ settings/local*
*.js.old
TODO
cover/
sample_data.py
210 changes: 210 additions & 0 deletions apps/autocomplete/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla Sheriff Duty.
#
# The Initial Developer of the Original Code is Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Peter Bengtsson, <peterbe@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

import ldap
from django.conf import settings
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils import simplejson as json
from users.utils.ldap_mock import MockLDAP
from mock import Mock
from nose.tools import eq_, ok_


class CitiesTest(TestCase):

def test_cities(self):
url = reverse('autocomplete.cities')
response = self.client.get(url)
eq_(response.status_code, 403)

mortal = User.objects.create(username='mortal')
mortal.set_password('secret')
mortal.save()
assert self.client.login(username='mortal', password='secret')

response = self.client.get(url)
eq_(response.status_code, 200)
ok_(response['content-type'].startswith('application/json'))
struct = json.loads(response.content)
eq_(struct, [])

profile = mortal.get_profile()
profile.city = 'London'
profile.save()

response = self.client.get(url)
eq_(response.status_code, 200)
struct = json.loads(response.content)
eq_(struct, ['London'])

bob = User.objects.create(username='bob')
profile = bob.get_profile()
profile.city = 'Aberdeen'
profile.save()

response = self.client.get(url)
eq_(response.status_code, 200)
struct = json.loads(response.content)
eq_(struct, ['Aberdeen', 'London'])

response = self.client.get(url, {'term': 'LON'})
eq_(response.status_code, 200)
struct = json.loads(response.content)
eq_(struct, ['London'])


class UsersTest(TestCase):

def setUp(self):
super(UsersTest, self).setUp()

ldap.open = Mock('ldap.open')
ldap.open.mock_returns = Mock('ldap_connection')
ldap.set_option = Mock(return_value=None)

def test_users(self):
results = [
('mail=peter@mozilla.com,o=com,dc=mozilla',
{'cn': ['Peter Bengtsson'],
'givenName': ['Pet\xc3\xa3r'], # utf-8 encoded
'mail': ['peterbe@mozilla.com'],
'sn': ['Bengtss\xc2\xa2n'],
'uid': ['pbengtsson']
})
]

ldap.initialize = Mock(return_value=MockLDAP({
'(&(objectClass=inetOrgPerson)(mail=*)(|(mail=peter*)(givenName=peter*)(sn=peter*)))': results
}))

url = reverse('autocomplete.users')
response = self.client.get(url, {'term': ' i '})
eq_(response.status_code, 403)

mortal = User.objects.create(
username='mortal',
first_name='Mortal',
last_name='Joe'
)
mortal.set_password('secret')
mortal.save()
assert self.client.login(username='mortal', password='secret')

response = self.client.get(url, {'term': ' i '})
eq_(response.status_code, 200)
ok_(response['content-type'].startswith('application/json'))

response = self.client.get(url, {'term': 'peter'})
eq_(response.status_code, 200)
ok_(response['content-type'].startswith('application/json'))
struct = json.loads(response.content)
ok_(isinstance(struct, list))
first_item = struct[0]

label = '%s %s <%s>' % (u'Pet\xe3r',
u'Bengtss\xa2n',
'peterbe@mozilla.com')
value = label
eq_(first_item, {
'id': 'pbengtsson',
'label': label,
'value': value,
})

def test_users_knownonly(self):
results = [
('mail=peter@mozilla.com,o=com,dc=mozilla',
{'cn': ['Peter Bengtsson'],
'givenName': ['Pet\xc3\xa3r'], # utf-8 encoded
'mail': ['peterbe@mozilla.com'],
'sn': ['Bengtss\xc2\xa2n'],
'uid': ['pbengtsson']
}),
('mail=peterino@mozilla.com,o=com,dc=mozilla',
{'cn': ['Peterino Gaudy'],
'givenName': ['Pet\xc3\xa3rino'], # utf-8 encoded
'mail': ['peterino@mozilla.com'],
'sn': ['Gaudi'],
'uid': ['peterino']
}),
]

ldap.initialize = Mock(return_value=MockLDAP({
'(&(objectClass=inetOrgPerson)(mail=*)(|(mail=peter*)(givenName=peter*)(sn=peter*)))': results
}))

url = reverse('autocomplete.users_known_only')

mortal = User.objects.create(
username='mortal',
first_name='Mortal',
last_name='Joe'
)
mortal.set_password('secret')
mortal.save()
assert self.client.login(username='mortal', password='secret')

response = self.client.get(url, {'term': 'peter'})
eq_(response.status_code, 200)
ok_(response['content-type'].startswith('application/json'))
struct = json.loads(response.content)
ok_(isinstance(struct, list))
eq_(len(struct), 0)

User.objects.create(
username=results[0][1]['uid'],
email=results[0][1]['mail'].upper(),
first_name=results[0][1]['givenName'],
last_name=results[0][1]['sn'],
)
response = self.client.get(url, {'term': 'peter'})
eq_(response.status_code, 200)
struct = json.loads(response.content)
eq_(len(struct), 1)

first_item = struct[0]

label = '%s %s <%s>' % (u'Pet\xe3r',
u'Bengtss\xa2n',
'peterbe@mozilla.com')
value = label
eq_(first_item, {
'id': 'pbengtsson',
'label': label,
'value': value,
})
5 changes: 5 additions & 0 deletions apps/autocomplete/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Peter Bengtsson, <peterbe@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
Expand All @@ -38,4 +39,8 @@

urlpatterns = patterns('',
url(r'^cities/$', views.cities, name='autocomplete.cities'),
url(r'^users/$', views.users, name='autocomplete.users'),
url(r'^users/knownonly/$', views.users,
{'known_only': True},
name='autocomplete.users_known_only'),
)
35 changes: 34 additions & 1 deletion apps/autocomplete/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Peter Bengtsson, <peterbe@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
Expand All @@ -33,12 +34,17 @@
#
# ***** END LICENSE BLOCK *****

import logging
from django import http
from dates.decorators import json_view
from users.models import UserProfile
from users.models import UserProfile, User
from users.utils import ldap_lookup


@json_view
def cities(request):
if not request.user.is_authenticated():
return http.HttpResponseForbidden('Must be logged in')
data = []
term = request.GET.get('term')
qs = UserProfile.objects.exclude(city='')
Expand All @@ -51,3 +57,30 @@ def cities(request):
city = each['city']
data.append(city)
return data

@json_view
def users(request, known_only=False):
if not request.user.is_authenticated():
return http.HttpResponseForbidden('Must be logged in')
query = request.GET.get('term').strip()
if len(query) < 2:
return []

results = []
# I chose a limit of 30 because there are about 20+ 'peter'
# something in mozilla
for each in ldap_lookup.search_users(query, 30, autocomplete=True):
if not each.get('givenName'):
logging.warn("Skipping LDAP entry %s" % each)
continue
if known_only:
if not User.objects.filter(email__iexact=each['mail']).exists():
continue
full_name_and_email = '%s %s <%s>' % (each['givenName'],
each['sn'],
each['mail'])
result = {'id': each['uid'],
'label': full_name_and_email,
'value': full_name_and_email}
results.append(result)
return results
4 changes: 2 additions & 2 deletions apps/dates/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def entry_is_birthday(context, entry):

@register.function
@jinja2.contextfunction
def full_name_form(context, user):
def full_name_form(context, user, avoid_email=False):
if user is None:
return ''
elif (isinstance(user, dict)
Expand All @@ -118,7 +118,7 @@ def full_name_form(context, user):
email = None
name = user

if name and email:
if name and email and not avoid_email:
return '%s <%s>' % (name, email)
elif name:
return name
Expand Down
54 changes: 54 additions & 0 deletions apps/dates/templates/dates/following.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% extends "base.html" %}

{% block extra_site_css %}
{{ css('jquery_ui') }}
{{ css('dates.following') }}
{% endblock %}


{% block extra_site_js %}
{{ js('jquery_ui') }}
{{ js('dates.following') }}
{% endblock %}

{% block page_title %}People you're following (or not){% endblock %}

{% block content %}
<h2>People you're following (or not)</h2>


<form action="{{ url('dates.save_following') }}" method="post">{{ csrf() }}
<h3>Add more people to follow:</h3>
<input name="search" id="id_search" size="50">
</form>

<div id="observed">
<h3>People you follow:</h3>
<table>
{% for user, reason in observed %}
<tr>
<td>{{ full_name_form(user, avoid_email=True) }}</td>
<td class="reason">{{ reason }}</td>
<td><a href="#" class="remove" data-id="{{ user.pk }}">remove</a></td>
</tr>
{% endfor %}
</table>
</div>

<div id="not-observed">
<h3>People you <em>don't</em> follow:</h3>
<table>
{% for user in not_observed %}
<tr>
<td>{{ full_name_form(user, avoid_email=True) }}</td>
<td><a href="#" class="restore" data-id="{{ user.pk }}">restore</a></td>
</tr>
{% endfor %}
</table>
</div>

<div class="clearer">&nbsp;</div>

<p class="goback"><a href="/">&larr; Go back to Calendar dashboard</a></p>

{% endblock %}
9 changes: 8 additions & 1 deletion apps/dates/templates/dates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ <h2>Who's on PTO right now?</h2>

<dl>
{% for user in right_now_users %}
<dt>{{ full_name_form(user) }}</dt>
<dt>
{% if user == request.user %}
<strong>You are!</strong>
{% else %}
{{ full_name_form(user) }}
{% endif %}

</dt>
{% for days_left, entry in right_nows[user] %}
<dd><a href="{{ entry_to_list_url(entry) }}"
title="{{ entry.details }}"
Expand Down
Loading

0 comments on commit c847b43

Please sign in to comment.