Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: tallowen/mozillians
...
head fork: tallowen/mozillians
  • 13 commits
  • 11 files changed
  • 0 commit comments
  • 6 contributors
Commits on Apr 12, 2012
David Waddington Advanced search options
* Fix bug681106
* Added profile picture only optin to search
* Added UI for doing customizing search in the front end.
5f41fb9
Commits on Apr 16, 2012
James Socol [Fix bug 745860] Fix CSP reporting and violation. 8e4254d
Matthew Riley MacPherson tofumatt Merge pull request #220 from jsocol/csp
Fix CSP reporting and a CSP violation.
347436e
Commits on Apr 17, 2012
Isac Lagerblad icaaq [bug 745888] Make skip-nav link tab-selectable.
skiplink that has display:none is useless because it will never receive
focus. I also added a visual :focus state.
4f40a35
James Socol [Fix bug 746203] Add gravatar to img-src whitelist. 5cbd32c
Matthew Riley MacPherson tofumatt Merge pull request #221 from jsocol/img-src
[Fix bug 746203] Add gravatar to img-src whitelist.
816c82a
Owen Coutts fixes c51fc61
Owen Coutts more fixes e16f06a
Commits on Apr 18, 2012
Adnane Belmadiaf [fix bug 746608] Use OpenSans on paragraph text.
Follow up to bug 737438, missed some paragraph text.
0d9e15b
James Socol Add mock to requirements/dev dec8bf1
Owen Coutts Merge pull request #222 from jsocol/mock
Add mock to requirements/dev
a829089
James Socol Merge pull request #216 from tallowen/advsearch
[fix bug 681106] Advanced search options. r=me
005e22e
Owen Coutts Allow empty search queries r=jsocol
- Fix bug 746688
- form still needs to be valid to see users. In other words, going to
  search/ with no args shoes the "enter a query" screen
2ecfe12
7 apps/phonebook/forms.py
View
@@ -20,9 +20,12 @@
class SearchForm(happyforms.Form):
- q = forms.CharField(widget=forms.HiddenInput, required=True)
+ q = forms.CharField(widget=forms.HiddenInput, required=False)
limit = forms.CharField(widget=forms.HiddenInput, required=False)
- nonvouched_only = forms.BooleanField(required=False)
+ nonvouched_only = forms.BooleanField(label=_lazy(u'Non Vouched Only'),
+ required=False)
+ picture_only = forms.BooleanField(label=_lazy(u'Only users with photos'),
+ required=False)
def clean_limit(self):
"""Validate that this limit is numeric and greater than 1"""
14 apps/phonebook/templates/phonebook/search.html
View
@@ -25,8 +25,22 @@
value="{% if form.cleaned_data %}{{ form.cleaned_data.q }}{% endif %}">
<input type="hidden" name="limit" id="limit" value="{{ limit }}">
<button type="submit" class="btn primary">
+ <span class="icon-search"></span>
{{ _('Search') }}
</button>
+ <button type="button" id="advanced" class="btn primary">
+ <span class="icon-chevron-down"></span>
+ {{ _('Advanced Options') }}
+ </button>
+ <div class="search-options">
+ {% for f in ['nonvouched_only', 'picture_only'] %}
+ <div id="{{ form[f].name }}-container"
+ class="field {{ form[f].field.widget.attrs['class'] }}">
+ {{ form[f] }}
+ {{ form[f].label_tag() }}
+ </div>
+ {% endfor %}
+ </div>
</form>
<div class="well">
{% if not form.cleaned_data %}
152 apps/phonebook/tests/test_search.py
View
@@ -0,0 +1,152 @@
+import os
+
+from django.conf import settings
+
+from nose.tools import eq_
+from pyquery import PyQuery as pq
+
+from common.tests import ESTestCase
+from elasticutils import get_es
+from funfactory.urlresolvers import reverse
+
+from users.models import UserProfile
+
+
+class TestSearch(ESTestCase):
+ def test_search_with_space(self):
+ """Extra spaces should not impact search queries."""
+ amanda = 'Amanda Younger'
+ amandeep = 'Amandeep McIlrath'
+ url = reverse('search')
+ r = self.mozillian_client.get(url, dict(q='Am'))
+ rs = self.mozillian_client.get(url, dict(q=' Am'))
+
+ eq_(r.status_code, 200)
+ peeps = r.context['people']
+ peeps_ws = rs.context['people']
+ saw_amanda = saw_amandeep = False
+
+ for person in peeps:
+ if person.display_name == amanda:
+ saw_amanda = True
+ elif person.display_name == amandeep:
+ saw_amandeep = True
+ if saw_amanda and saw_amandeep:
+ break
+
+ assert peeps[0].id in (peeps_ws[0].id, peeps_ws[1].id)
+ self.assertTrue(saw_amanda, 'We see first person')
+ self.assertTrue(saw_amandeep, 'We see another person')
+
+ def test_nonvouched_search(self):
+ """Make sure that only non vouched users are returned on search."""
+ amanda = 'Amanda Younger'
+ amandeep = 'Amandeep McIlrath'
+ url = reverse('search')
+ r = self.mozillian_client.get(url, dict(q='Am'))
+ rnv = self.mozillian_client.get(url, dict(q='Am', nonvouched_only=1))
+
+ eq_(r.status_code, 200)
+ peeps = r.context['people']
+ peeps_nv = rnv.context['people']
+
+ saw_amanda = saw_amandeep = False
+
+ for person in peeps:
+ if person.display_name == amandeep:
+ assert person.is_vouched, 'Amanda is a Mozillian'
+ saw_amandeep = True
+ elif person.display_name == amanda:
+ if person.is_vouched:
+ self.fail('Amandeep should have pending status')
+ saw_amanda = True
+ if saw_amanda and saw_amandeep:
+ break
+
+ self.assertEqual(peeps_nv[0].display_name, amanda)
+ self.assertTrue(saw_amanda, 'We see vouched users')
+ self.assertTrue(saw_amandeep, 'We see non-vouched users')
+ assert all(not person.is_vouched for person in peeps_nv)
+
+ def test_profilepic_search(self):
+ """Make sure searching for only users with profile pics works."""
+ with open(os.path.join(os.path.dirname(__file__), 'profile-photo.jpg')) as f:
+ r = self.mozillian_client.post(reverse('profile.edit'),
+ dict(first_name='Aman', last_name='Withapic', photo=f))
+
+ if not settings.ES_DISABLED:
+ get_es().refresh(settings.ES_INDEXES['default'], timesleep=0)
+
+ amanhasapic = 'Aman Withapic'
+ amanda = 'Amanda Younger'
+ url = reverse('search')
+ r = self.mozillian_client.get(url, dict(q='Am'))
+ rpp = self.mozillian_client.get(url, dict(q='Am', picture_only=1))
+
+ eq_(r.status_code, 200)
+ peeps = r.context['people']
+ peeps_pp = rpp.context['people']
+ saw_amanda = False
+
+ # Make sure that every body has a profile picture
+ for person in peeps:
+ if person.display_name == amanda:
+ if bool(person.photo):
+ self.fail('Amanda doesnt have a profile pic')
+ saw_amanda = True
+
+ # Make sure amanda shows up in peeps
+ assert amanda in [p.display_name for p in peeps]
+ # Make sure she doesn't show up in peeps_pp
+ assert amanda not in [p.display_name for p in peeps_pp]
+ self.assertEqual(peeps_pp[0].display_name, amanhasapic)
+ self.assertTrue(saw_amanda, 'We dont see profile picture')
+
+ def test_mozillian_search_pagination(self):
+ """Tests the pagination on search.
+
+ 1. assumes no page is passed, but valid limit is passed
+ 2. assumes invalid page is passed, no limit is passed
+ 3. assumes valid page is passed, no limit is passed
+ 4. assumes valid page is passed, valid limit is passed
+ """
+ url = reverse('search')
+ r = self.mozillian_client.get(url, dict(q='Amand', limit='1'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 1)
+
+ r = self.mozillian_client.get(url, dict(q='Amand', page='test'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 2)
+
+ r = self.mozillian_client.get(url, dict(q='Amand', page='1'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 2)
+
+ r = self.mozillian_client.get(url, dict(q='Amand', page='test',
+ limit='1'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 1)
+
+ r = self.mozillian_client.get(url, dict(q='Amand', page='test',
+ limit='x'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 2)
+
+ r = self.mozillian_client.get(url, dict(q='Amand', page='test',
+ limit='-3'))
+ peeps = r.context['people']
+ self.assertEqual(len(peeps), 2)
+
+ def test_empty_query_search(self):
+ """Make sure the search method works with an empty query"""
+ assert UserProfile.search('').count()
+
+ def test_proper_url_arg_handling(self):
+ search_url = reverse('search')
+ r = self.mozillian_client.get(search_url)
+ assert not pq(r.content)('.result')
+
+ r = self.mozillian_client.get(search_url,
+ dict(q=u'', nonvouched_only=1))
+ assert pq(r.content)('.result')
74 apps/phonebook/tests/test_views.py
View
@@ -8,7 +8,7 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-from common.tests import TestCase, ESTestCase
+from common.tests import TestCase
from funfactory.urlresolvers import set_url_prefix, reverse
@@ -333,78 +333,6 @@ def test_localized_search_plugin(self):
assert '/fr/search' in response.content
-class TestSearch(ESTestCase):
- def test_mozillian_search(self):
- """Test our search."""
- amanda = 'Amanda Younger'
- amandeep = 'Amandeep McIlrath'
- url = reverse('search')
- r = self.mozillian_client.get(url, dict(q='Am'))
- rs = self.mozillian_client.get(url, dict(q=' Am'))
- rnv = self.mozillian_client.get(url, dict(q='Am', nonvouched_only=1))
-
- eq_(r.status_code, 200)
- peeps = r.context['people']
- peeps_ws = rs.context['people']
- peeps_nv = rnv.context['people']
-
- saw_amandeep = saw_amanda = False
-
- for person in peeps:
- if person.display_name == amandeep:
- assert person.is_vouched, 'Amandeep is a Mozillian'
- saw_amandeep = True
- elif person.display_name == amanda:
- if person.is_vouched:
- self.fail('Amanda is pending status')
- saw_amanda = True
- if saw_amandeep and saw_amanda:
- break
-
- assert peeps[0].id in (peeps_ws[0].id, peeps_ws[1].id)
- self.assertEqual(peeps_nv[0].display_name, amanda)
- self.assertTrue(saw_amandeep, 'We see Mozillians')
- self.assertTrue(saw_amanda, 'We see Pending')
-
- assert all(not person.is_vouched for person in peeps_nv)
-
- def test_mozillian_search_pagination(self):
- """Tests the pagination on search.
-
- 1. assumes no page is passed, but valid limit is passed
- 2. assumes invalid page is passed, no limit is passed
- 3. assumes valid page is passed, no limit is passed
- 4. assumes valid page is passed, valid limit is passed
- """
- url = reverse('search')
- r = self.mozillian_client.get(url, dict(q='Amand', limit='1'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 1)
-
- r = self.mozillian_client.get(url, dict(q='Amand', page='test'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 2)
-
- r = self.mozillian_client.get(url, dict(q='Amand', page='1'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 2)
-
- r = self.mozillian_client.get(url, dict(q='Amand', page='test',
- limit='1'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 1)
-
- r = self.mozillian_client.get(url, dict(q='Amand', page='test',
- limit='x'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 2)
-
- r = self.mozillian_client.get(url, dict(q='Amand', page='test',
- limit='-3'))
- peeps = r.context['people']
- self.assertEqual(len(peeps), 2)
-
-
def _logged_in_html(response):
doc = pq(response.content)
return doc('a#logout')
29 apps/phonebook/views.py
View
@@ -136,35 +136,40 @@ def search(request):
num_pages = 0
limit = None
nonvouched_only = False
+ picture_only = False
people = []
show_pagination = False
form = forms.SearchForm(request.GET)
if form.is_valid():
- query = form.cleaned_data.get('q', '')
+ query = form.cleaned_data.get('q', u'')
limit = form.cleaned_data['limit']
vouched = False if form.cleaned_data['nonvouched_only'] else None
+ profilepic = True if form.cleaned_data['picture_only'] else None
page = request.GET.get('page', 1)
- profiles = UserProfile.search(query, vouched=vouched)
+ # if nothing has been entered don't load any searches.
+ if not (not query and vouched is None and profilepic is None):
+ profiles = UserProfile.search(query, vouched=vouched, photo=profilepic)
- paginator = Paginator(profiles, limit)
+ paginator = Paginator(profiles, limit)
- try:
- people = paginator.page(page)
- except PageNotAnInteger:
- people = paginator.page(1)
- except EmptyPage:
- people = paginator.page(paginator.num_pages)
+ try:
+ people = paginator.page(page)
+ except PageNotAnInteger:
+ people = paginator.page(1)
+ except EmptyPage:
+ people = paginator.page(paginator.num_pages)
- if paginator.count > forms.PAGINATION_LIMIT:
- show_pagination = True
- num_pages = len(people.paginator.page_range)
+ if paginator.count > forms.PAGINATION_LIMIT:
+ show_pagination = True
+ num_pages = len(people.paginator.page_range)
d = dict(people=people,
form=form,
limit=limit,
nonvouched_only=nonvouched_only,
+ picture_only=picture_only,
show_pagination=show_pagination,
num_pages=num_pages)
18 apps/users/models.py
View
@@ -152,23 +152,29 @@ def fields(self):
attrs = ('username', 'first_name', 'last_name', 'email', 'last_login',
'date_joined')
d.update(dict((a, getattr(self.user, a)) for a in attrs))
- # Index group ids... for fun.
- groups = list(self.groups.values_list('name', flat=True))
- d.update(dict(groups=groups))
+ d.update(dict(has_photo=bool(self.photo)))
+ # Index groups and skills ... for fun.
+ d.update(dict(groups=list(self.groups.values_list('name', flat=True))))
return d
@classmethod
- def search(cls, query, vouched=None):
+ def search(cls, query, vouched=None, photo=None):
"""Sensible default search for UserProfiles."""
query = query.lower().strip()
fields = ('first_name__text', 'last_name__text', 'display_name__text',
'username__text', 'bio__text', 'website__text',
'email__text', 'groups__text', 'first_name__startswith',
'last_name__startswith', 'ircname')
- q = dict((field, query) for field in fields)
- s = S(cls).query(or_=q)
+ if query:
+ q = dict((field, query) for field in fields)
+ s = S(cls).query(or_=q)
+ else:
+ s = S(cls)
+
if vouched is not None:
s = s.filter(is_vouched=vouched)
+ if photo is not None:
+ s = s.filter(has_photo=photo)
return s
35 media/css/base.css
View
@@ -30,6 +30,23 @@ body {
font:16px/24px Georgia, serif;
}
+.skip{
+ left:-999em;
+ position:absolute;
+}
+
+.skip:focus{
+ background:rgb(255,255,255);
+ color:rgb(44,44,44);
+ height:35px;
+ left:0;
+ padding-top:5px;
+ right:0;
+ text-align:center;
+ top:0;
+ z-index:1031; /* One more than .nav-bar-fixed-top */
+}
+
.huge,.large,h1,h2,h3,h4 {
font-family:OpenSans, "Lucida Sans", "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif;
font-weight:400;
@@ -72,6 +89,10 @@ h4 {
line-height:100%;
}
+footer p, #main p {
+ font-family: 'Open Sans',sans-serif;
+}
+
.small,small {
font-size:12px;
line-height:100%;
@@ -422,6 +443,20 @@ legend {
margin-bottom:15px;
}
+.search-options {
+ display: none;
+}
+
+.search-options input, .search-options span {
+ display: block;
+ float: left;
+}
+
+.search-options input {
+ margin-left: 40px;
+ margin-right: 10px;
+}
+
.edit-profile {
max-width: 700px;
}
5 media/js/expand.js
View
@@ -0,0 +1,5 @@
+$(function(){
+ $('#advanced').click(function(){
+ $('.search-options').slideToggle('fast');
+ });
+});
1  requirements/dev.txt
View
@@ -9,6 +9,7 @@ Sphinx==1.1
# Testing
nose==1.0.0
+mock==0.8.0
-e git://github.com/jbalogh/django-nose.git#egg=django_nose
-e git://github.com/jbalogh/test-utils.git#egg=test-utils
pyquery
12 settings/default.py
View
@@ -97,6 +97,7 @@
'search': (
'js/libs/jquery.endless-scroll.js',
'js/infinite.js',
+ 'js/expand.js',
),
'backbone': (
'js/libs/underscore.js',
@@ -122,6 +123,11 @@
# StrictTransport
STS_SUBDOMAINS = True
+# Not all URLs need locale.
+SUPPORTED_NONLOCALES = list(base.SUPPORTED_NONLOCALES) + [
+ 'csp',
+]
+
AUTHENTICATION_BACKENDS = ('common.backends.MozilliansBrowserID',)
# BrowserID creates a user if one doesn't exist.
@@ -200,9 +206,11 @@
# Django-CSP
CSP_IMG_SRC = ("'self'", 'http://statse.webtrendslive.com',
- 'https://statse.webtrendslive.com',)
+ 'https://statse.webtrendslive.com',
+ 'http://www.gravatar.com',)
CSP_SCRIPT_SRC = ("'self'", 'http://statse.webtrendslive.com',
- 'https://statse.webtrendslive.com',)
+ 'https://statse.webtrendslive.com',
+ 'https://browserid.org',)
CSP_REPORT_ONLY = True
CSP_REPORT_URI = '/csp/report'
4 templates/base.html
View
@@ -30,9 +30,7 @@
{{ 'auth' if user.is_authenticated() else 'anon' }}"
data-locale="{{ LANG }}">
-<ul id="skip" class="hide">
- <li><a href="#main">{{ _('Skip to Content') }}</a></li>
-</ul>
+<a class="skip" href="#main">{{ _('Skip to Content') }}</a>
{% block nav %}
<nav class="navbar navbar-fixed-top">
<div class="navbar-inner">

No commit comments for this range

Something went wrong with that request. Please try again.