Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[Fix bug 747421] Add temp page for MozCamp LATAM.

- Adds a temporary view, template, and route at /mozcamp.
- Adds a temporary setting, KEYNOTE_FILE, IT will need to set.
- Revert me soon!
  • Loading branch information...
commit aa4a5b9731ddbd03f071378bd220522c519a5070 2 parents 0c2427c + 508dd65
James Socol authored
8 apps/groups/templates/groups/group.html
View
@@ -72,7 +72,13 @@ <h1 class="span12">Users in Group "{{ name }}"</h1>
<img class="profile-photo" src="{{ group.steward.photo_url() }}" alt="{{ _('Profile Photo') }}">
</div>
<ul>
- <li><span>{{ group.steward.display_name }}</span></li>
+ <li>
+ <span>
+ <a href="{{ url('profile', group.steward.user.username) }}">
+ {{ group.steward.display_name }}
+ </a>
+ </span>
+ </li>
<li>
<a href="mailto:{{ group.steward.user.email }}">
{{ group.steward.user.email }}
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"""
31 apps/phonebook/templates/phonebook/keynote.html
View
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+
+{% block page_title %}{{ _('MozCamp LATAM: Keynote') }}{% endblock %}
+{% block body_classes %}box-content{% endblock %}
+
+{% block page_js %}
+ <script type='text/javascript' src='http://videos.mozilla.org/serv/air_mozilla/jwplayer.js'></script>
+ <script>
+ jwplayer('player').setup({
+ 'flashplayer': 'http://videos.mozilla.org/serv/air_mozilla/player.swf',
+ 'file': '{{ settings.KEYNOTE_FILE }}',
+ 'provider': 'rtmp',
+ 'streamer': 'rtmp://fml.1237.edgecastcdn.net/201237/',
+ 'rtmp.subscribe': 'true',
+ 'controlbar': 'over',
+ 'playlist': 'none',
+ 'dock': 'true',
+ 'icons': 'true',
+ 'quality': 'true',
+ 'autostart': 'true',
+ 'width': '620',
+ 'height': '350'
+ });
+ </script>
+{% endblock %}
+
+{% block main_content %}
+ <div style="margin: 30px auto; width: 620px;">
+ <div id="player">Do not adjust your television...</div>
+ </div>
+{% endblock %}
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')
1  apps/phonebook/urls.py
View
@@ -19,6 +19,7 @@
url('^opensearch.xml$', views.search_plugin, name='search_plugin'),
url('^search$', views.search, name='search'),
url('^vouch$', views.vouch, name='vouch'),
+ url('^mozcamp$', views.keynote, name='keynote'),
url('^invite$', views.invite, name='invite'),
url('^invited/(?P<id>\d+)$', views.invited, name='invited'),
37 apps/phonebook/views.py
View
@@ -8,7 +8,7 @@
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import redirect, render
from django.views.decorators.cache import cache_page, never_cache
-from django.views.decorators.http import require_POST
+from django.views.decorators.http import require_POST, require_GET
import commonware.log
from funfactory.urlresolvers import reverse
@@ -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)
@@ -221,3 +226,9 @@ def vouch(request):
return redirect(reverse('profile', args=[p.user.username]))
return HttpResponseForbidden
+
+
+@require_GET
+@vouch_required
+def keynote(request):
+ return render(request, 'phonebook/keynote.html')
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
2  apps/users/views.py
View
@@ -84,8 +84,6 @@ def register(request):
messages.info(request, _(u'Your account has been created.'))
return redirect('profile', user.username)
- # When changing this keep in mind that the same view is used for
- # phonebook.edit_profile.
# 'user' object must be passed in because we are not logged in
return render(request, 'registration/register.html',
dict(form=form,
30 bin/jenkins.sh
View
@@ -14,15 +14,18 @@ echo "Starting build on executor $EXECUTOR_NUMBER..."
# Make sure there's no old pyc files around.
find . -name '*.pyc' -exec rm {} \;
-echo "Making Virtualenv"
-virtualenv $VENV --no-site-packages
+
+if [ ! -d "$VENV" ]; then
+ echo "Making virtualenv..."
+ virtualenv $VENV --no-site-packages
+ pip install --upgrade pip
+fi
source $VENV/bin/activate
-pip install --upgrade pip
pip install coverage
-pip install PyQuery
-pip install mock
+pip install -r requirements/compiled.txt
+pip install -r requirements/dev.txt
-git submodule sync -q
+git submodule sync
git submodule update --init --recursive
if [ ! -d "$WORKSPACE/vendor" ]; then
@@ -30,10 +33,6 @@ if [ ! -d "$WORKSPACE/vendor" ]; then
exit 1
fi
-source $VENV/bin/activate
-# pip install -q -r requirements/compiled.txt
-# pip install -q -r requirements/dev.txt
-
cat > settings/local.py <<SETTINGS
import logging
from settings import *
@@ -87,7 +86,7 @@ DEBUG = TEMPLATE_DEBUG = True
# ElasticSearch
ES_DISABLED = False
ES_HOSTS = ['127.0.0.1:9200']
-ES_INDEXES = dict(default='mozillians_dev')
+ES_INDEXES = dict(default='test_${JOB_NAME}')
SETTINGS
echo "Creating database if we need it..."
@@ -97,7 +96,12 @@ echo "Database name: ${JOB_NAME}"
echo "Starting tests..."
export FORCE_DB=1
-coverage run manage.py test --noinput --with-xunit
-coverage xml $(find apps lib -name '*.py')
+
+if [ -z $COVERAGE ]; then
+ python manage.py test --noinput --with-xunit --logging-clear-handlers
+else
+ coverage run --omit='*migrations*' manage.py test --noinput --with-xunit --logging-clear-handlers
+ coverage xml --omit='*migrations*' $(find apps lib -name '*.py')
+fi
echo "FIN"
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
17 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,12 @@
# Django-CSP
CSP_IMG_SRC = ("'self'", 'http://statse.webtrendslive.com',
- 'https://statse.webtrendslive.com',)
+ 'https://statse.webtrendslive.com',
+ 'http://www.gravatar.com',
+ 'https://secure.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'
@@ -222,3 +231,7 @@
# This is for the commons/helper.py thumbnail.
DEFAULT_IMAGE_SRC = path('./media/uploads/unknown.png')
+
+# TEMPORARY
+# TODO: Remove this
+KEYNOTE_FILE = None
Please sign in to comment.
Something went wrong with that request. Please try again.