Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Atomic service page #254

Merged
merged 1 commit into from

3 participants

@readevalprint

removed the merge and the html api tempaltes

apps/phonebook/views.py
@@ -66,6 +67,15 @@ def edit_profile(request):
request.FILES,
instance=profile,
)
+
+ if 'reset_api_key' in request.POST:
+ # The rest of the form is irrelevant.
+ try:
+ request.user.api_key.delete()
+ except ApiKey.DoesNotExist:
+ pass
+ return redirect(reverse('profile.edit'))
@jsocol
jsocol added a note

As Giorgos said on the other pull req, it's probably worth doing

return redirect(urlparams(reverse('profile.edit'), fragment='services'))

So people come back to the same page.

Did not know about the fragment argument. Always learning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jsocol jsocol commented on the diff
apps/users/models.py
@@ -134,6 +135,13 @@ def vouch(self, vouched_by, system=True, commit=True):
# Email the user and tell them they were vouched.
self._email_now_vouched()
+ def get_api_key(self):
@jsocol
jsocol added a note

As Giorgos said, this could be simplified to:

key, created = ApiKey.objects.get_or_create(user=self.user)
return key
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/users/tests.py
@@ -353,6 +353,16 @@ def test_userprofile(self):
# Good to go
assert u.get_profile()
+ def test_apikey(self):
+ """Test that get_api_key() will create a key if missing."""
+ # A new user will not have a key created.
+ u = User.objects.create(username='tmp', email='tmp@domain.com')
+ p = u.get_profile()
+ from tastypie.models import ApiKey
+ # assertRaises needs a callable
@jsocol
jsocol added a note

Rather than document assertRaises, can this comment explain what the assertion is: e.g. "First, there shouldn't be an API key."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jsocol

Thanks, this is much easier to review! Let's get follow-up bugs filed for the HTML API and the rest of it?

Just a couple of small comments, but I'd like to see the follow-up commit that addresses them before giving an r+.

apps/users/tests.py
@@ -353,6 +353,16 @@ def test_userprofile(self):
# Good to go
assert u.get_profile()
+ def test_apikey(self):
+ """Test that get_api_key() will create a key if missing."""
+ # A new user will not have a key created.
+ u = User.objects.create(username='tmp', email='tmp@domain.com')
@jsocol
jsocol added a note

If you rebase this onto master you can use the new users.tests.user modelmaker.

Cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jsocol

OK, here's my request to finish this out:
1) Fix the couple of comments. There are really only 2 or 3.
2) Rebase on top of master, squash viciously, get it down to one commit.
3) Let me see the result, we should be at r+ after that.

@readevalprint

squashed, r?

apps/phonebook/views.py
((22 lines not shown))
- form = forms.ProfileForm(
- instance=profile,
- initial=initial,
- )
+ if not request.user.username.startswith('u/'):
+ initial.update(username=request.user.username)
+
+ form = forms.ProfileForm(
@tallowen
tallowen added a note

If this is a post request shouldn't the form returned be the one that is created here so that form errors get passed back to the view?

@jsocol
jsocol added a note

Yeah, this should be in part of an else: block, in case it's a GET.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jsocol

This is basically r+ from me with the little note about the form in apps/phonebook/views.py:104 and... an apology. I wouldn't have touched the <legend>s if bug 763509 hadn't ended up requiring me to also add <h2>s to a tab content pane.

So... you should fix up that form. Tests and the view and template all look good. Then you'll probably want to rebase and deal with the conflicts I just created for you. :(

But after that, you can go ahead and land it, I think anything else is follow-up bug worthy.

@readevalprint

Added else block back and squashed

@readevalprint readevalprint merged commit 2e5fe2f into mozilla:master
@jsocol

Isn't this else: the counterpart to if form.is_valid(): and not if request.method == 'POST'? Seems like the indent level is a little too far here.

Yup, fixed it, but am having problems with the product and cannot runt the tests locally

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
66 apps/phonebook/templates/phonebook/edit_profile.html
@@ -1,15 +1,23 @@
{% extends "base.html" %}
+{% block site_js %}
+ {{ super() }}
+ {{ js('edit_profile') }}
+{% endblock %}
+
+{% block site_css %}
+ {{ super() }}
+ {{ css('edit_profile') }}
+{% endblock %}
+
{% block page_title %}{{ _('Edit Your Profile') }}{% endblock %}
{% block body_id %}edit-profile{% endblock %}
{% block body_classes %}box-content{% endblock %}
-{% block page_js %}
- {{ js('edit_profile') }}
-{% endblock %}
{% block main_content %}
- <form action="{{ edit_form_action }}"
- method="POST" enctype="multipart/form-data"
+ <form action="{{ url('profile.edit') }}"
+ method="POST"
+ enctype="multipart/form-data"
class="form-horizontal edit-profile">
{{ csrf() }}
<h1>{{ _('Edit Your Profile') }}</h1>
@@ -19,6 +27,7 @@
<li><a href="#skills" data-toggle="tab">{{ _('Skills & Groups') }}</a></li>
<li><a href="#vouches" data-toggle="tab">{{ _('Vouches & Invites') }}</a></li>
<li><a href="#account" data-toggle="tab">{{ _('Account') }}</a></li>
+ <li><a href="#services" data-toggle="tab">{{ _('Services') }}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="1">
@@ -42,22 +51,22 @@
</div>
<div class="tab-pane" id="skills">
<h2>{{ _('Groups') }}</h2>
- <p class="field_description">
- {% trans %}
- Groups are a community of Mozillians with some relation to each
- other. This can be an interest, team, project, product or
- sub-community.
- {% endtrans %}
- </p>
+ <p class="field_description">
+ {% trans %}
+ Groups are a community of Mozillians with some relation to each
+ other. This can be an interest, team, project, product or
+ sub-community.
+ {% endtrans %}
+ </p>
{{ form.groups.label_tag() }}
{{ form.groups }}
<h2>{{ _('Skills') }}</h2>
- <p class="field_description">
- {% trans %}
- A skill is the learned capacity to carry out pre-determined
- results often with the minimum outlay of time, energy, or both.
- {% endtrans %}
- </p>
+ <p class="field_description">
+ {% trans %}
+ A skill is the learned capacity to carry out pre-determined
+ results often with the minimum outlay of time, energy, or both.
+ {% endtrans %}
+ </p>
{{ form.skills.label_tag() }}
{{ form.skills }}
</div>
@@ -135,6 +144,27 @@
</div>
</div>
</div>
+ <div class="tab-pane" id="services">
+ <div class="control-group">
+ <h2>{{ _("API") }}</h2>
+ <p class="field_description">
+ {% trans %}
+ The Mozillians' Phonebook offers an API to Vouched Mozillians to help share profile data to other tools and sites
+ {% endtrans %}
+ </p>
+ <label class="control-label">{{ _("Api Key") }}</label>
+ <div class="controls">
+ <span class="label-text">
+ <div class="input-append">
+ <input id="api-key" type="text" class="span4" autocomplete="off" data-value="{{ profile.get_api_key() }}" value="{{ profile.get_api_key() }}">
+ <button type="submit" name="reset_api_key" class="btn btn-mini btn-danger">
+ {{ _("Generate new API Key") }}
+ </button>
+ </div>
+ </span>
+ </div>
+ </div>
+ </div>
</div>
</div>
<div id="edit_controls">
View
26 apps/phonebook/tests/test_views.py
@@ -276,6 +276,32 @@ def test_replace_photo(self):
new_photo = doc('#profile-photo').attr('src')
assert new_photo != old_photo
+ def test_api_key(self):
+ """Assert that the Api key will be created and displayed"""
+ client = self.mozillian_client
+ r = client.get(reverse('profile.edit'), follow=True)
+
+ doc = pq(r.content)
+ api_key = doc('#api-key').attr('value')
+
+ p = self.mozillian.get_profile()
+ assert p.get_api_key() == api_key
+
+ def test_reset_api_key(self):
+ """Assert that resetingthe aPI key changes it."""
+ client = self.mozillian_client
+ r = client.get(reverse('profile.edit'), follow=True)
+
+ doc = pq(r.content)
+ original_api_key = doc('#api-key').attr('value')
+
+ data = {'reset_api_key': True}
+ r = client.post(reverse('profile.edit'), data, follow=True)
+
+ doc = pq(r.content)
+ new_api_key = doc('#api-key').attr('value')
+ assert original_api_key != new_api_key
+
class TestVouch(TestCase):
"""
View
46 apps/phonebook/views.py
@@ -13,6 +13,8 @@
import commonware.log
from funfactory.urlresolvers import reverse
from product_details import product_details
+from funfactory.helpers import urlparams
+from tastypie.models import ApiKey
from tower import ugettext as _
from groups.helpers import stringify_groups
@@ -72,6 +74,15 @@ def edit_profile(request):
instance=profile,
)
form.fields['region'].choices = COUNTRIES
+
+ if 'reset_api_key' in request.POST:
+ # The rest of the form is irrelevant.
+ try:
+ request.user.api_key.delete()
+ except ApiKey.DoesNotExist:
+ pass
+ return redirect(urlparams(reverse('profile.edit'), 'services'))
+
if form.is_valid():
old_username = request.user.username
form.save(request)
@@ -84,24 +95,24 @@ def edit_profile(request):
'changed.'))
return redirect(reverse('profile', args=[request.user.username]))
- else:
- initial = dict(first_name=request.user.first_name,
- last_name=request.user.last_name,
- bio=profile.bio,
- website=profile.website,
- irc_nickname=profile.ircname,
- groups=user_groups,
- skills=user_skills)
+ else:
+ initial = dict(first_name=request.user.first_name,
+ last_name=request.user.last_name,
+ bio=profile.bio,
+ website=profile.website,
+ irc_nickname=profile.ircname,
+ groups=user_groups,
+ skills=user_skills)
+
+ form = forms.ProfileForm(
+ instance=profile,
+ initial=initial,
+ )
+ form.fields['country'].choices = COUNTRIES
if not request.user.username.startswith('u/'):
initial.update(username=request.user.username)
- form = forms.ProfileForm(
- instance=profile,
- initial=initial,
- )
- form.fields['country'].choices = COUNTRIES
-
# When changing this keep in mind that the same view is used for
# user.register.
d = dict(form=form,
@@ -152,7 +163,9 @@ def search(request):
# 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)
+ profiles = UserProfile.search(query,
+ vouched=vouched,
+ photo=profilepic)
paginator = Paginator(profiles, limit)
@@ -164,7 +177,8 @@ def search(request):
people = paginator.page(paginator.num_pages)
if len(profiles) == 1:
- return redirect(reverse('profile', args=[people[0].user.username]))
+ return redirect(reverse('profile',
+ args=[people[0].user.username]))
if paginator.count > forms.PAGINATION_LIMIT:
show_pagination = True
View
5 apps/users/models.py
@@ -14,6 +14,7 @@
from PIL import Image, ImageOps
from product_details import product_details
from sorl.thumbnail import ImageField
+from tastypie.models import ApiKey
from tower import ugettext as _, ugettext_lazy as _lazy
from groups.models import Group, Skill
@@ -156,6 +157,10 @@ def vouch(self, vouched_by, system=True, commit=True):
# Email the user and tell them they were vouched.
self._email_now_vouched()
+ def get_api_key(self):
@jsocol
jsocol added a note

As Giorgos said, this could be simplified to:

key, created = ApiKey.objects.get_or_create(user=self.user)
return key
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ api_key, created = ApiKey.objects.get_or_create(user=self.user)
+ return api_key.key
+
def _email_now_vouched(self):
"""Email this user, letting them know they are now vouched."""
subject = _(u'You are now vouched on Mozillians!')
View
15 apps/users/tests.py
@@ -338,7 +338,7 @@ class TestUser(TestCase):
"""Test User functionality"""
def test_userprofile(self):
- u = User.objects.create(username='tmp', email='tmp@domain.com')
+ u = user()
UserProfile.objects.all().delete()
@@ -354,6 +354,19 @@ def test_userprofile(self):
# Good to go
assert u.get_profile()
+ def test_apikey(self):
+ """Test that get_api_key() will create a key if missing."""
+ # A new user will not have a key created.
+ u = user()
+ p = u.get_profile()
+ from tastypie.models import ApiKey
+
+ # A new key is not generated automatically on a user.
+ self.assertRaises(ApiKey.DoesNotExist, lambda: u.api_key)
+
+ # get_api_key will always return a key, creating one if needed.
+ eq_(p.get_api_key(), u.api_key.key)
+
class TestMigrateRegistration(TestCase):
"""Test funky behavior of flee ldap"""
View
4 media/css/user.css
@@ -0,0 +1,4 @@
+.control-group h2 {
+ border-bottom: 1px solid black;
+ margin-bottom: 10px;
+}
View
4 settings/default.py
@@ -69,6 +69,9 @@
'test': (
'css/qunit.css',
),
+ 'edit_profile': (
+ 'css/user.css',
+ ),
},
'js': {
'common': (
@@ -149,6 +152,7 @@
'cronjobs',
'elasticutils',
'sorl.thumbnail',
+ 'tastypie',
'django.contrib.admin',
'django.contrib.auth',
Something went wrong with that request. Please try again.