Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #254 from readevalprint/atomic_service_page

[fix bug 753398] service tab with API key generator
  • Loading branch information...
commit 2e5fe2f226b9ccef5bcd838dc76c5a8097693adc 2 parents c604621 + 031c3d0
Tim Watts authored
66  apps/phonebook/templates/phonebook/edit_profile.html
... ...
@@ -1,15 +1,23 @@
1 1
 {% extends "base.html" %}
2 2
 
  3
+{% block site_js %}
  4
+  {{ super() }}
  5
+  {{ js('edit_profile') }}
  6
+{% endblock %}
  7
+
  8
+{% block site_css %}
  9
+  {{ super() }}
  10
+  {{ css('edit_profile') }}
  11
+{% endblock %}
  12
+
3 13
 {% block page_title %}{{ _('Edit Your Profile') }}{% endblock %}
4 14
 {% block body_id %}edit-profile{% endblock %}
5 15
 {% block body_classes %}box-content{% endblock %}
6  
-{% block page_js %}
7  
-  {{ js('edit_profile') }}
8  
-{% endblock %}
9 16
 
10 17
 {% block main_content %}
11  
-  <form action="{{ edit_form_action }}"
12  
-        method="POST" enctype="multipart/form-data"
  18
+  <form action="{{ url('profile.edit') }}"
  19
+        method="POST"
  20
+        enctype="multipart/form-data"
13 21
         class="form-horizontal edit-profile">
14 22
     {{ csrf() }}
15 23
     <h1>{{ _('Edit Your Profile') }}</h1>
@@ -19,6 +27,7 @@
19 27
         <li><a href="#skills" data-toggle="tab">{{ _('Skills & Groups') }}</a></li>
20 28
         <li><a href="#vouches" data-toggle="tab">{{ _('Vouches & Invites') }}</a></li>
21 29
         <li><a href="#account" data-toggle="tab">{{ _('Account') }}</a></li>
  30
+        <li><a href="#services" data-toggle="tab">{{ _('Services') }}</a></li>
22 31
       </ul>
23 32
       <div class="tab-content">
24 33
         <div class="tab-pane active" id="1">
@@ -42,22 +51,22 @@
42 51
         </div>
43 52
         <div class="tab-pane" id="skills">
44 53
           <h2>{{ _('Groups') }}</h2>
45  
-            <p class="field_description">
46  
-              {% trans %}
47  
-                Groups are a community of Mozillians with some relation to each
48  
-                other. This can be an interest, team, project, product or
49  
-                sub-community.
50  
-              {% endtrans %}
51  
-            </p>
  54
+          <p class="field_description">
  55
+            {% trans %}
  56
+              Groups are a community of Mozillians with some relation to each
  57
+              other. This can be an interest, team, project, product or
  58
+              sub-community.
  59
+            {% endtrans %}
  60
+          </p>
52 61
           {{ form.groups.label_tag() }}
53 62
           {{ form.groups }}
54 63
           <h2>{{ _('Skills') }}</h2>
55  
-            <p class="field_description">
56  
-              {% trans %}
57  
-                A skill is the learned capacity to carry out pre-determined
58  
-                results often with the minimum outlay of time, energy, or both.
59  
-              {% endtrans %}
60  
-            </p>
  64
+          <p class="field_description">
  65
+            {% trans %}
  66
+              A skill is the learned capacity to carry out pre-determined
  67
+              results often with the minimum outlay of time, energy, or both.
  68
+            {% endtrans %}
  69
+          </p>
61 70
           {{ form.skills.label_tag() }}
62 71
           {{ form.skills }}
63 72
         </div>
@@ -135,6 +144,27 @@
135 144
             </div>
136 145
           </div>
137 146
         </div>
  147
+        <div class="tab-pane" id="services">
  148
+          <div class="control-group">
  149
+            <h2>{{ _("API") }}</h2>
  150
+            <p class="field_description">
  151
+              {% trans %}
  152
+                The Mozillians' Phonebook offers an API to Vouched Mozillians to help share profile data to other tools and sites
  153
+              {% endtrans %}
  154
+            </p>
  155
+            <label class="control-label">{{ _("Api Key") }}</label>
  156
+            <div class="controls">
  157
+              <span class="label-text">
  158
+                <div class="input-append">
  159
+                  <input id="api-key" type="text" class="span4" autocomplete="off" data-value="{{ profile.get_api_key() }}" value="{{ profile.get_api_key() }}">
  160
+                  <button type="submit" name="reset_api_key" class="btn btn-mini btn-danger">
  161
+                    {{ _("Generate new API Key") }}
  162
+                  </button>
  163
+                </div>
  164
+              </span>
  165
+            </div>
  166
+          </div>
  167
+        </div>
138 168
       </div>
139 169
     </div>
140 170
     <div id="edit_controls">
26  apps/phonebook/tests/test_views.py
@@ -276,6 +276,32 @@ def test_replace_photo(self):
276 276
         new_photo = doc('#profile-photo').attr('src')
277 277
         assert new_photo != old_photo
278 278
 
  279
+    def test_api_key(self):
  280
+        """Assert that the Api key will be created and displayed"""
  281
+        client = self.mozillian_client
  282
+        r = client.get(reverse('profile.edit'), follow=True)
  283
+
  284
+        doc = pq(r.content)
  285
+        api_key = doc('#api-key').attr('value')
  286
+
  287
+        p = self.mozillian.get_profile()
  288
+        assert p.get_api_key() == api_key
  289
+
  290
+    def test_reset_api_key(self):
  291
+        """Assert that resetingthe aPI key changes it."""
  292
+        client = self.mozillian_client
  293
+        r = client.get(reverse('profile.edit'), follow=True)
  294
+
  295
+        doc = pq(r.content)
  296
+        original_api_key = doc('#api-key').attr('value')
  297
+
  298
+        data = {'reset_api_key': True}
  299
+        r = client.post(reverse('profile.edit'), data, follow=True)
  300
+
  301
+        doc = pq(r.content)
  302
+        new_api_key = doc('#api-key').attr('value')
  303
+        assert original_api_key != new_api_key
  304
+
279 305
 
280 306
 class TestVouch(TestCase):
281 307
     """
46  apps/phonebook/views.py
@@ -13,6 +13,8 @@
13 13
 import commonware.log
14 14
 from funfactory.urlresolvers import reverse
15 15
 from product_details import product_details
  16
+from funfactory.helpers import urlparams
  17
+from tastypie.models import ApiKey
16 18
 from tower import ugettext as _
17 19
 
18 20
 from groups.helpers import stringify_groups
@@ -72,6 +74,15 @@ def edit_profile(request):
72 74
                 instance=profile,
73 75
         )
74 76
         form.fields['region'].choices = COUNTRIES
  77
+
  78
+        if 'reset_api_key' in request.POST:
  79
+            # The rest of the form is irrelevant.
  80
+            try:
  81
+                request.user.api_key.delete()
  82
+            except ApiKey.DoesNotExist:
  83
+                pass
  84
+            return redirect(urlparams(reverse('profile.edit'), 'services'))
  85
+
75 86
         if form.is_valid():
76 87
             old_username = request.user.username
77 88
             form.save(request)
@@ -84,24 +95,24 @@ def edit_profile(request):
84 95
                                           'changed.'))
85 96
 
86 97
             return redirect(reverse('profile', args=[request.user.username]))
87  
-    else:
88  
-        initial = dict(first_name=request.user.first_name,
89  
-                       last_name=request.user.last_name,
90  
-                       bio=profile.bio,
91  
-                       website=profile.website,
92  
-                       irc_nickname=profile.ircname,
93  
-                       groups=user_groups,
94  
-                       skills=user_skills)
  98
+        else:
  99
+            initial = dict(first_name=request.user.first_name,
  100
+                           last_name=request.user.last_name,
  101
+                           bio=profile.bio,
  102
+                           website=profile.website,
  103
+                           irc_nickname=profile.ircname,
  104
+                           groups=user_groups,
  105
+                           skills=user_skills)
  106
+
  107
+            form = forms.ProfileForm(
  108
+                    instance=profile,
  109
+                    initial=initial,
  110
+            )
  111
+            form.fields['country'].choices = COUNTRIES
95 112
 
96 113
         if not request.user.username.startswith('u/'):
97 114
             initial.update(username=request.user.username)
98 115
 
99  
-        form = forms.ProfileForm(
100  
-                instance=profile,
101  
-                initial=initial,
102  
-        )
103  
-        form.fields['country'].choices = COUNTRIES
104  
-
105 116
     # When changing this keep in mind that the same view is used for
106 117
     # user.register.
107 118
     d = dict(form=form,
@@ -152,7 +163,9 @@ def search(request):
152 163
 
153 164
         # If nothing has been entered don't load any searches.
154 165
         if not (not query and vouched is None and profilepic is None):
155  
-            profiles = UserProfile.search(query, vouched=vouched, photo=profilepic)
  166
+            profiles = UserProfile.search(query,
  167
+                                          vouched=vouched,
  168
+                                          photo=profilepic)
156 169
 
157 170
             paginator = Paginator(profiles, limit)
158 171
 
@@ -164,7 +177,8 @@ def search(request):
164 177
                 people = paginator.page(paginator.num_pages)
165 178
 
166 179
             if len(profiles) == 1:
167  
-                return redirect(reverse('profile', args=[people[0].user.username]))
  180
+                return redirect(reverse('profile',
  181
+                                        args=[people[0].user.username]))
168 182
 
169 183
             if paginator.count > forms.PAGINATION_LIMIT:
170 184
                 show_pagination = True
5  apps/users/models.py
@@ -14,6 +14,7 @@
14 14
 from PIL import Image, ImageOps
15 15
 from product_details import product_details
16 16
 from sorl.thumbnail import ImageField
  17
+from tastypie.models import ApiKey
17 18
 from tower import ugettext as _, ugettext_lazy as _lazy
18 19
 
19 20
 from groups.models import Group, Skill
@@ -156,6 +157,10 @@ def vouch(self, vouched_by, system=True, commit=True):
156 157
             # Email the user and tell them they were vouched.
157 158
             self._email_now_vouched()
158 159
 
  160
+    def get_api_key(self):
  161
+        api_key, created = ApiKey.objects.get_or_create(user=self.user)
  162
+        return api_key.key
  163
+
159 164
     def _email_now_vouched(self):
160 165
         """Email this user, letting them know they are now vouched."""
161 166
         subject = _(u'You are now vouched on Mozillians!')
15  apps/users/tests.py
@@ -338,7 +338,7 @@ class TestUser(TestCase):
338 338
     """Test User functionality"""
339 339
 
340 340
     def test_userprofile(self):
341  
-        u = User.objects.create(username='tmp', email='tmp@domain.com')
  341
+        u = user()
342 342
 
343 343
         UserProfile.objects.all().delete()
344 344
 
@@ -354,6 +354,19 @@ def test_userprofile(self):
354 354
         # Good to go
355 355
         assert u.get_profile()
356 356
 
  357
+    def test_apikey(self):
  358
+        """Test that get_api_key() will create a key if missing."""
  359
+        # A new user will not have a key created.
  360
+        u = user()
  361
+        p = u.get_profile()
  362
+        from tastypie.models import ApiKey
  363
+
  364
+        # A new key is not generated automatically on a user.
  365
+        self.assertRaises(ApiKey.DoesNotExist, lambda: u.api_key)
  366
+
  367
+        # get_api_key will always return a key, creating one if needed.
  368
+        eq_(p.get_api_key(), u.api_key.key)
  369
+
357 370
 
358 371
 class TestMigrateRegistration(TestCase):
359 372
         """Test funky behavior of flee ldap"""
4  media/css/user.css
... ...
@@ -0,0 +1,4 @@
  1
+.control-group h2 {
  2
+  border-bottom: 1px solid black;
  3
+  margin-bottom: 10px;
  4
+}
4  settings/default.py
@@ -69,6 +69,9 @@
69 69
         'test': (
70 70
             'css/qunit.css',
71 71
         ),
  72
+        'edit_profile': (
  73
+            'css/user.css',
  74
+        ),
72 75
     },
73 76
     'js': {
74 77
         'common': (
@@ -149,6 +152,7 @@
149 152
     'cronjobs',
150 153
     'elasticutils',
151 154
     'sorl.thumbnail',
  155
+    'tastypie',
152 156
 
153 157
     'django.contrib.admin',
154 158
     'django.contrib.auth',

0 notes on commit 2e5fe2f

Please sign in to comment.
Something went wrong with that request. Please try again.