Repository: mdn/kuma

Issue:
Backend work to support User Acct creation
### Summary
Create the Kuma back-end required to support the new account signup flow (see #5911).

### Acceptance Criteria
- [x] Create a new function `is_username_taken(user)` available to Jinja, as well as its associated tests.
- [x] Create a new API endpoint for doing the same, as well as its associated tests.
- [ ] (Not certain of this one yet) To the endpoint above, add the ability to check if one or more email addresses are already taken by another account, as well as tests.
 


Discussion:

Resolving patch:
diff --git a/kuma/api/v1/urls.py b/kuma/api/v1/urls.py
--- a/kuma/api/v1/urls.py
+++ b/kuma/api/v1/urls.py
@@ -16,4 +16,7 @@
     url(r'^bc-signal/?$',
         views.bc_signal,
         name='api.v1.bc_signal'),
+    url(r'^users/(?P<username>[^/]+)/?$',
+        views.get_user,
+        name='api.v1.get_user'),
 ]
diff --git a/kuma/api/v1/views.py b/kuma/api/v1/views.py
--- a/kuma/api/v1/views.py
+++ b/kuma/api/v1/views.py
@@ -1,9 +1,9 @@
 from django.conf import settings
-from django.http import HttpResponsePermanentRedirect, JsonResponse
+from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.shortcuts import get_object_or_404
 from django.utils.translation import activate, ugettext as _
 from django.views.decorators.cache import never_cache
-from django.views.decorators.http import require_GET
+from django.views.decorators.http import require_GET, require_safe
 from ratelimit.decorators import ratelimit
 from rest_framework import serializers, status
 from rest_framework.decorators import api_view
@@ -21,6 +21,7 @@
     SearchQueryBackend,
     TagGroupFilterBackend)
 from kuma.search.search import SearchView
+from kuma.users.models import User
 from kuma.users.templatetags.jinja_helpers import get_avatar_url
 from kuma.wiki.models import Document
 from kuma.wiki.templatetags.jinja_helpers import absolutify
@@ -300,3 +301,23 @@ def bc_signal(request):
         serializer.save()
         return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
     return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+@never_cache
+@require_safe
+def get_user(request, username):
+    """
+    Returns a JSON response with a small subset of public information if a
+    user with the given username exists, otherwise returns a status code of
+    404. The case of the username is not important, since the collation of
+    the username column of the user table in MySQL is case-insensitive.
+    """
+    fields = ('username', 'title', 'fullname', 'organization', 'location',
+              'timezone', 'locale')
+    try:
+        user = User.objects.only(*fields).get(username=username)
+    except User.DoesNotExist:
+        raise Http404(f'No user exists with the username "{username}".')
+    data = {field: getattr(user, field) for field in fields}
+    data['avatar_url'] = get_avatar_url(user)
+    return JsonResponse(data)
diff --git a/kuma/users/templatetags/jinja_helpers.py b/kuma/users/templatetags/jinja_helpers.py
--- a/kuma/users/templatetags/jinja_helpers.py
+++ b/kuma/users/templatetags/jinja_helpers.py
@@ -12,6 +12,8 @@
 from kuma.core.templatetags.jinja_helpers import datetimeformat
 from kuma.core.urlresolvers import reverse
 
+from ..models import User
+
 
 @library.global_function
 def get_avatar_url(user, provider='github'):
@@ -168,3 +170,12 @@ def get_primary_email(email_addresses):
     for email in email_addresses:
         if email_addresses[email].get('primary'):
             return email
+
+
+@library.global_function
+def is_username_taken(username):
+    """
+    Returns True if a user with the given username exists (case-insentive),
+    otherwise False.
+    """
+    return User.objects.filter(username=username).exists()


Test patch:
diff --git a/kuma/api/v1/tests/test_views.py b/kuma/api/v1/tests/test_views.py
--- a/kuma/api/v1/tests/test_views.py
+++ b/kuma/api/v1/tests/test_views.py
@@ -330,3 +330,49 @@ def test_search_basic(self):
         assert data['documents']
         assert data['count'] == 5
         assert data['locale'] == 'fr'
+
+
+@pytest.mark.parametrize('http_method', ('put', 'post', 'delete', 'options'))
+def test_get_user_disallowed_methods(client, wiki_user, http_method):
+    """
+    HTTP methods other than GET and HEAD are not allowed on the api.v1.get_user
+    endpoint.
+    """
+    url = reverse('api.v1.get_user', args=(wiki_user.username,))
+    response = getattr(client, http_method)(url)
+    assert response.status_code == 405
+    assert_no_cache_header(response)
+
+
+@pytest.mark.parametrize('case', ('upper', 'lower'))
+@pytest.mark.parametrize('http_method', ('get', 'head'))
+def test_get_existing_user(client, wiki_user, wiki_user_github_account,
+                           http_method, case):
+    """
+    Test GET and HEAD on the api.v1.get_user endpoint for an existing user, and
+    also that the username is case insensitive.
+    """
+    username = getattr(str, case)(wiki_user.username)
+    url = reverse('api.v1.get_user', args=(username,))
+    response = getattr(client, http_method)(url)
+    assert response.status_code == 200
+    assert response['content-type'] == 'application/json'
+    assert_no_cache_header(response)
+    if http_method == 'get':
+        data = response.json()
+        assert data['username'] == wiki_user.username
+        assert data['avatar_url'] == wiki_user_github_account.get_avatar_url()
+        for field in ('title', 'fullname', 'organization', 'location',
+                      'timezone', 'locale'):
+            assert data[field] == getattr(wiki_user, field)
+
+
+@pytest.mark.parametrize('http_method', ('get', 'head'))
+def test_get_nonexisting_user(db, client, http_method):
+    """
+    Test GET and HEAD on the api.v1.get_user endpoint for a non-existing user.
+    """
+    url = reverse('api.v1.get_user', args=('nonexistent',))
+    response = getattr(client, http_method)(url)
+    assert response.status_code == 404
+    assert_no_cache_header(response)
diff --git a/kuma/users/tests/test_helpers.py b/kuma/users/tests/test_helpers.py
--- a/kuma/users/tests/test_helpers.py
+++ b/kuma/users/tests/test_helpers.py
@@ -1,6 +1,7 @@
 from django.conf import settings
 
-from ..templatetags.jinja_helpers import get_avatar_url, public_email
+from ..templatetags.jinja_helpers import (get_avatar_url, is_username_taken,
+                                          public_email)
 
 
 def test_get_avatar_url(wiki_user, wiki_user_github_account):
@@ -19,3 +20,10 @@ def test_public_email():
     assert ('<span class="email">'
             '&#110;&#111;&#116;&#46;&#97;&#110;&#46;&#101;&#109;&#97;&#105;'
             '&#108;</span>' == public_email('not.an.email'))
+
+
+def test_is_username_taken(wiki_user):
+    assert is_username_taken(wiki_user.username)
+    assert is_username_taken(wiki_user.username.upper())
+    assert not is_username_taken('nonexistent')
+    assert not is_username_taken(None)