зеркало из https://github.com/mozilla/mozillians.git
[Fixes bug 1192815] Remove APIv1 code and UI
* Update Settings UI to reflect the changes * Remove unused fields from model and es * Remove v1 references from documentation * Remove tastypie from requirements
This commit is contained in:
Родитель
bbf2ad49fd
Коммит
6063638f2b
|
@ -29,5 +29,4 @@ If you believe an application is misusing Mozillians.org API data, please `file
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
apiv1/index
|
||||
apiv2/index
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
.. This Source Code Form is subject to the terms of the Mozilla Public
|
||||
.. License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
.. file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
.. _apiv1-groups:
|
||||
|
||||
==================
|
||||
Groups
|
||||
==================
|
||||
|
||||
The ``groups`` method of the :doc:`Mozillians API </api/apiv1/index>` returns information about groups.
|
||||
|
||||
**Requires Authentication**
|
||||
Yes
|
||||
|
||||
**Authorized Applications**
|
||||
Mozilla Corporation sites
|
||||
|
||||
Endpoint
|
||||
--------
|
||||
|
||||
``https://mozillians.org/api/v1/groups/``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
``app_key``
|
||||
*Required* **string** - The application's API key
|
||||
|
||||
``app_name``
|
||||
*Required* **string** - The application's name
|
||||
|
||||
``order_by``
|
||||
*Optional* **string** - The attribute to sort responses by, and the order to display them in
|
||||
|
||||
``limit``
|
||||
*Optional* **integer** - Return some number of results **Max: 500, Default: 20**
|
||||
|
||||
``offset``
|
||||
*Optional* **integer** - Skip some number of results **Default: 0**
|
||||
|
||||
``format``
|
||||
*Optional* **string (json/jsonp)** - Format of the response **Default: json**
|
||||
|
||||
Return Codes
|
||||
------------
|
||||
|
||||
==== ===========
|
||||
Code Description
|
||||
==== ===========
|
||||
200: OK Success!
|
||||
401: Wrong app_name or app_key OR application not activated OR application not authorized
|
||||
==== ===========
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Get groups, sorted by the number of members in the group, ordered from most to fewest members, limited to 3 results:**
|
||||
|
||||
Request::
|
||||
|
||||
/api/v1/groups/?app_name=foobar&app_key=12345&order_by=-number_of_members&limit=3
|
||||
|
||||
Response::
|
||||
|
||||
{
|
||||
"meta": {
|
||||
"limit": 3,
|
||||
"next": "/api/v1/groups/?order_by=-number_of_members&app_name=foobar&app_key=12345&limit=3&offset=3",
|
||||
"offset": 0,
|
||||
"previous": null,
|
||||
"total_count": 749
|
||||
},
|
||||
"objects":
|
||||
[
|
||||
{
|
||||
"id": "471",
|
||||
"name": "do_not_have_beards",
|
||||
"number_of_members": 2159,
|
||||
"resource_uri": "/api/v1/groups/471/",
|
||||
"url": "https://mozillians.org/group/do_not_have_beards/"
|
||||
},
|
||||
{
|
||||
"id": "18",
|
||||
"name": "have_beards",
|
||||
"number_of_members": 1513,
|
||||
"resource_uri": "/api/v1/groups/18/",
|
||||
"url": "https://mozillians.org/group/have_beards/"
|
||||
},
|
||||
{
|
||||
"id": "1907",
|
||||
"name": "sometimes_beards",
|
||||
"number_of_members": 183,
|
||||
"resource_uri": "/api/v1/groups/1907/",
|
||||
"url": "https://mozillians.org/group/sometimes_beards/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Get groups, ordered from most to fewest members and then by group name**::
|
||||
|
||||
/api/v1/groups/?app_name=foobar&app_key=12345&order_by=-number_of_members,name
|
||||
|
||||
**Get group having id 509**::
|
||||
|
||||
/api/v1/groups/509/?app_name=foobar&app_key=12345
|
|
@ -1,106 +0,0 @@
|
|||
.. This Source Code Form is subject to the terms of the Mozilla Public
|
||||
.. License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
.. file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
.. _apiv1-skills:
|
||||
|
||||
==================
|
||||
Skills
|
||||
==================
|
||||
|
||||
The ``skills`` method of the :doc:`Mozillians API </api/apiv1/index>` returns information about skills.
|
||||
|
||||
**Requires Authentication**
|
||||
Yes
|
||||
|
||||
**Authorized Applications**
|
||||
Mozilla Corporation sites
|
||||
|
||||
Endpoint
|
||||
--------
|
||||
|
||||
``https://mozillians.org/api/v1/skills/``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
``app_key``
|
||||
*Required* **string** - The application's API key
|
||||
|
||||
``app_name``
|
||||
*Required* **string** - The application's name
|
||||
|
||||
``order_by``
|
||||
*Optional* **string** - The attribute to sort responses by, and the order to display them in
|
||||
|
||||
``limit``
|
||||
*Optional* **integer** - Return some number of results **Max: 500, Default: 20**
|
||||
|
||||
``offset``
|
||||
*Optional* **integer** - Skip some number of results **Default: 0**
|
||||
|
||||
``format``
|
||||
*Optional* **string (json/jsonp)** - Format of the response **Default: json**
|
||||
|
||||
Return Codes
|
||||
------------
|
||||
|
||||
==== ===========
|
||||
Code Description
|
||||
==== ===========
|
||||
200: OK Success!
|
||||
401: Wrong app_name or app_key OR application not activated OR application not authorized
|
||||
==== ===========
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Get skills, sorted by the number of members having the skill, ordered from most to fewest members, limited to 3 results:**
|
||||
|
||||
Request::
|
||||
|
||||
/api/v1/skills/?app_name=foobar&app_key=12345&order_by=-number_of_members&limit=3
|
||||
|
||||
Response::
|
||||
|
||||
{
|
||||
"meta": {
|
||||
"limit": 3,
|
||||
"next": "/api/v1/skills/?order_by=-number_of_members&app_name=foobar&app_key=12345&limit=3&offset=3",
|
||||
"offset": 0,
|
||||
"previous": null,
|
||||
"total_count": 749
|
||||
},
|
||||
"objects":
|
||||
[
|
||||
{
|
||||
"id": "471",
|
||||
"name": "shaving",
|
||||
"number_of_members": 2159,
|
||||
"resource_uri": "/api/v1/skills/471/",
|
||||
"url": "https://mozillians.org/skill/shaving/"
|
||||
},
|
||||
{
|
||||
"id": "18",
|
||||
"name": "trimming",
|
||||
"number_of_members": 1513,
|
||||
"resource_uri": "/api/v1/skills/18/",
|
||||
"url": "https://mozillians.org/skill/trimming/"
|
||||
},
|
||||
{
|
||||
"id": "1907",
|
||||
"name": "growing",
|
||||
"number_of_members": 183,
|
||||
"resource_uri": "/api/v1/skills/1907/",
|
||||
"url": "https://mozillians.org/skill/growing/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Get skills, ordered from most to fewest members and then by skill id**::
|
||||
|
||||
/api/v1/skills/?app_name=foobar&app_key=12345&order_by=-number_of_members,id
|
||||
|
||||
**Get skill having id 509**::
|
||||
|
||||
/api/v1/skills/509/?app_name=foobar&app_key=12345
|
|
@ -1,172 +0,0 @@
|
|||
.. This Source Code Form is subject to the terms of the Mozilla Public
|
||||
.. License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
.. file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
.. _apiv1-users:
|
||||
|
||||
==================
|
||||
Users
|
||||
==================
|
||||
|
||||
The ``users`` method of the :doc:`Mozillians API </api/apiv1/index>` returns user profile information.
|
||||
|
||||
**Requires Authentication**
|
||||
Yes
|
||||
|
||||
**Authorized Applications**
|
||||
Community sites (only for determining vouched status), Mozilla Corporation sites
|
||||
|
||||
Endpoint
|
||||
--------
|
||||
|
||||
``https://mozillians.org/api/v1/users/``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
``app_key``
|
||||
*Required* **string** - The application's API key
|
||||
|
||||
``app_name``
|
||||
*Required* **string** - The application's name
|
||||
|
||||
``is_vouched``
|
||||
*Optional* **string (true/false)** - Return only vouched/unvouched users
|
||||
|
||||
``username``
|
||||
*Optional* **string** - Return user with matching username
|
||||
|
||||
``ircname``
|
||||
*Optional* **string** - Return user with matching ircname
|
||||
|
||||
``email``
|
||||
(For Community sites)
|
||||
*Required* **string** - Return user with matching email
|
||||
(For Mozilla Corporation sites)
|
||||
*Optional* **string** - Return user with matching email
|
||||
|
||||
``country``
|
||||
*Optional* **string** - Return users with matching country
|
||||
|
||||
``region``
|
||||
*Optional* **string** - Return users with matching region
|
||||
|
||||
``city``
|
||||
*Optional* **string** - Return users with matching city
|
||||
|
||||
``skills``
|
||||
*Optional* **string (comma-separated list)** - Return users with matching skills
|
||||
|
||||
``languages``
|
||||
*Optional* **string (comma-separated list)** - Return users with matching languages
|
||||
|
||||
``accounts``
|
||||
*Optional* **string** - Return users with matching external account identifier
|
||||
|
||||
``groups``
|
||||
*Optional* **string (comma-separated list)** - Return users with matching groups
|
||||
|
||||
``limit``
|
||||
*Optional* **integer** - Return some number of results **Max: 500, Default: 20**
|
||||
|
||||
``offset``
|
||||
*Optional* **integer** - Skip some number of results **Default: 0**
|
||||
|
||||
``format``
|
||||
*Optional* **string (json/jsonp)** - Format of the response **Default: json**
|
||||
|
||||
Return Codes
|
||||
------------
|
||||
|
||||
==== ===========
|
||||
Code Description
|
||||
==== ===========
|
||||
200: OK Success!
|
||||
401: Wrong app_name or app_key OR application not activated OR application not authorized
|
||||
==== ===========
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Look up a user by email address:**
|
||||
|
||||
Request::
|
||||
|
||||
/api/v1/users/?app_name=foobar&app_key=12345&email=test@example.com
|
||||
|
||||
Response *(Community site)*::
|
||||
|
||||
{
|
||||
"meta":
|
||||
{
|
||||
"limit": 20,
|
||||
"next": null,
|
||||
"offset": 0,
|
||||
"previous": null,
|
||||
"total_count": 1
|
||||
},
|
||||
"objects":
|
||||
[
|
||||
{
|
||||
"email": "test@example.com",
|
||||
"is_vouched": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Response *(Mozilla Corporation site)*::
|
||||
|
||||
{
|
||||
"meta":
|
||||
{
|
||||
"limit": 20,
|
||||
"next": null,
|
||||
"offset": 0,
|
||||
"previous": null,
|
||||
"total_count": 1
|
||||
},
|
||||
"objects":
|
||||
[
|
||||
{
|
||||
"allows_community_sites": true,
|
||||
"allows_mozilla_sites": true,
|
||||
"bio": "I've been a web typographer for 12 years.",
|
||||
"city": "Topeka",
|
||||
"country": "United States",
|
||||
"date_mozillian": "2008-12-01",
|
||||
"date_vouched": 2011-06-25T13:16:40,
|
||||
"email": "test@example.com",
|
||||
"full_name": "John Doe",
|
||||
"groups": "[(u'widgets',), (u'chocolate',)]",
|
||||
"id": "42",
|
||||
"ircname": "",
|
||||
"is_vouched": true,
|
||||
"languages": "[(u'hindi',), (u'english',)]",
|
||||
"photo": "https://mozillians.org/media/uploads/userprofile/3c5fcc399-bf2f-6caf-96fb-b40d9a03037269.jpg",
|
||||
"photo_thumbnail": "https://mozillians.org/media/uploads/sorl-cache/b2/e0/b2e004e13927d41497c7f6ab39bcafad.jpg",
|
||||
"region": "Kansas",
|
||||
"resource_uri": "/api/v1/users/42/",
|
||||
"skills": "[(u'card tricks',), (u'css3',), (u'skydiving instructor',)]",
|
||||
"timezone": "America/Topeka",
|
||||
"url": "https://mozillians.allizom.org/u/john_doe/",
|
||||
"username": "john_doe",
|
||||
"vouched_by": 808,
|
||||
"website": "http://johndozer.geocities.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Filter API responses:**
|
||||
|
||||
By *country*::
|
||||
|
||||
/api/v1/users/?app_name=foobar&app_key=12345&country=Greece
|
||||
|
||||
By *ircname*::
|
||||
|
||||
/api/v1/users/?app_name=foobar&app_key=12345&ircname=mr_amazing
|
||||
|
||||
By *group* AND *language*::
|
||||
|
||||
/api/v1/users/?app_name=foobar&app_key=12345&groups=beards&languages=french
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
.. This Source Code Form is subject to the terms of the Mozilla Public
|
||||
.. License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
.. file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
.. _api_v1:
|
||||
|
||||
======
|
||||
API v1
|
||||
======
|
||||
|
||||
Getting an API Key
|
||||
------------------
|
||||
|
||||
Community and Mozilla Corporation sites can request an API key by `submitting a bug <https://bugzilla.mozilla.org/enter_bug.cgi?product=Participation%20Infrastructure&component=API%20Requests>`_. The bug should include the **URL and description of the application** and **details about the expected use of API data**.
|
||||
|
||||
All requests are reviewed by product owners and data safety experts; not all requests are approved.
|
||||
|
||||
API keys are granted per application, not per user.
|
||||
|
||||
API Methods
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api-users
|
||||
api-groups
|
||||
api-skills
|
|
@ -5,47 +5,12 @@ from dal import autocomplete
|
|||
from import_export import fields
|
||||
from import_export.resources import ModelResource
|
||||
|
||||
from mozillians.api.models import APIApp, APIv2App
|
||||
from mozillians.api.models import APIv2App
|
||||
from mozillians.common.mixins import MozilliansAdminExportMixin
|
||||
|
||||
|
||||
class APIForm(forms.ModelForm):
|
||||
"""Override admin form to provide autocompletion."""
|
||||
|
||||
class Meta:
|
||||
model = APIApp
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'owner': autocomplete.ModelSelect2(url='users:users-autocomplete')
|
||||
}
|
||||
|
||||
|
||||
class APIAppResource(ModelResource):
|
||||
"""APIApp admin export resource."""
|
||||
email = fields.Field(attribute='owner__email')
|
||||
|
||||
|
||||
class APIAppAdmin(MozilliansAdminExportMixin, admin.ModelAdmin):
|
||||
"""APIApp Admin."""
|
||||
|
||||
list_display = ['name', 'key', 'owner', 'owner_email', 'is_mozilla_app', 'is_active']
|
||||
list_filter = ['is_mozilla_app', 'is_active']
|
||||
form = APIForm
|
||||
|
||||
def owner_email(self, obj):
|
||||
return obj.owner.email
|
||||
|
||||
owner_email.admin_order_field = 'owner__email'
|
||||
owner_email.short_description = 'Email'
|
||||
|
||||
resource_class = APIAppResource
|
||||
|
||||
|
||||
admin.site.register(APIApp, APIAppAdmin)
|
||||
|
||||
|
||||
class APIv2AppResource(ModelResource):
|
||||
"""APIApp admin export resource."""
|
||||
"""APIv2App admin export resource."""
|
||||
email = fields.Field(attribute='owner__email')
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0002_auto_20150820_0822'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='apiapp',
|
||||
name='owner',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='APIApp',
|
||||
),
|
||||
]
|
|
@ -2,7 +2,6 @@ import hmac
|
|||
import uuid
|
||||
from hashlib import sha1
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
|
@ -11,40 +10,6 @@ from mozillians.users.managers import PRIVACY_CHOICES, PRIVILEGED, PUBLIC
|
|||
from mozillians.users.models import PrivacyField, UserProfile
|
||||
|
||||
|
||||
class APIApp(models.Model):
|
||||
"""APIApp Model."""
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
description = models.TextField()
|
||||
url = models.URLField(max_length=300, blank=True, default='')
|
||||
owner = models.ForeignKey(User)
|
||||
key = models.CharField(
|
||||
help_text='Leave this field empty to generate a new API key.',
|
||||
max_length=256, blank=True, default='')
|
||||
is_mozilla_app = models.BooleanField(blank=True, default=False)
|
||||
is_active = models.BooleanField(blank=True, default=False)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'APIv1 Apps'
|
||||
verbose_name = 'APIv1 App'
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return unicode representation of object."""
|
||||
return "%s for %s" % (self.name, self.owner)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Generates a key if none and saves object."""
|
||||
if not self.key:
|
||||
self.key = self.generate_key()
|
||||
|
||||
return super(APIApp, self).save(*args, **kwargs)
|
||||
|
||||
def generate_key(self):
|
||||
"""Return a key."""
|
||||
new_uuid = uuid.uuid4()
|
||||
return hmac.new(str(new_uuid), digestmod=sha1).hexdigest()
|
||||
|
||||
|
||||
class APIv2App(models.Model):
|
||||
API_PRIVACY_CHOICES = [(PRIVILEGED, _lazy(u'Privileged'))] + list(PRIVACY_CHOICES)
|
||||
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
import factory
|
||||
|
||||
from mozillians.api.models import APIApp, APIv2App
|
||||
from mozillians.users.tests import UserFactory
|
||||
|
||||
|
||||
class APIAppFactory(factory.DjangoModelFactory):
|
||||
name = factory.Sequence(lambda n: 'App {0}'.format(n))
|
||||
description = factory.Sequence(lambda n: 'Description for App {0}'.format(n))
|
||||
owner = factory.SubFactory(UserFactory)
|
||||
is_active = True
|
||||
|
||||
class Meta:
|
||||
model = APIApp
|
||||
from mozillians.api.models import APIv2App
|
||||
|
||||
|
||||
class APIv2AppFactory(factory.DjangoModelFactory):
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from nose.tools import ok_
|
||||
|
||||
from mozillians.users.tests import UserFactory
|
||||
from mozillians.api.models import APIApp
|
||||
|
||||
|
||||
class APIAppTests(TestCase):
|
||||
def test_save_generates_key(self):
|
||||
owner = UserFactory.create()
|
||||
api_app = APIApp.objects.create(owner=owner, name='Test',
|
||||
description='Foo',
|
||||
key='')
|
||||
ok_(api_app.key != '')
|
|
@ -1,66 +0,0 @@
|
|||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from nose.tools import eq_, ok_
|
||||
|
||||
from mozillians.api.v1.authenticators import AppAuthentication
|
||||
from mozillians.api.tests import APIAppFactory
|
||||
|
||||
|
||||
class AppAuthenticationTests(TestCase):
|
||||
def test_valid_app(self):
|
||||
app = APIAppFactory.create()
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': app.key, 'app_name': app.name}
|
||||
authentication = AppAuthentication()
|
||||
ok_(authentication.is_authenticated(request))
|
||||
|
||||
def test_empty_app_name(self):
|
||||
app = APIAppFactory.create()
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': app.key}
|
||||
authentication = AppAuthentication()
|
||||
eq_(authentication.is_authenticated(request), False)
|
||||
|
||||
def test_empty_app_key(self):
|
||||
app = APIAppFactory.create()
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_name': app.name}
|
||||
authentication = AppAuthentication()
|
||||
eq_(authentication.is_authenticated(request), False)
|
||||
|
||||
def test_invalid_app_name(self):
|
||||
app = APIAppFactory.create()
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': app.key, 'app_name': 'invalid'}
|
||||
authentication = AppAuthentication()
|
||||
eq_(authentication.is_authenticated(request), False)
|
||||
|
||||
def test_invalid_app_key(self):
|
||||
app = APIAppFactory.create()
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': 'invalid', 'app_name': app.name}
|
||||
authentication = AppAuthentication()
|
||||
eq_(authentication.is_authenticated(request), False)
|
||||
|
||||
def test_invalid_app_name_and_key(self):
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': 'invalid', 'app_name': 'invalid'}
|
||||
authentication = AppAuthentication()
|
||||
eq_(authentication.is_authenticated(request), False)
|
||||
|
||||
def test_mozilla_app(self):
|
||||
app = APIAppFactory.create(is_mozilla_app=True)
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': app.key, 'app_name': app.name}
|
||||
authentication = AppAuthentication()
|
||||
authentication.is_authenticated(request)
|
||||
eq_(request.GET.get('restricted'), None)
|
||||
|
||||
def test_community_app(self):
|
||||
app = APIAppFactory.create(is_mozilla_app=False)
|
||||
request = RequestFactory()
|
||||
request.GET = {'app_key': app.key, 'app_name': app.name}
|
||||
authentication = AppAuthentication()
|
||||
authentication.is_authenticated(request)
|
||||
eq_(request.GET.get('restricted'), True)
|
|
@ -1,29 +0,0 @@
|
|||
from mock import MagicMock, patch
|
||||
from nose.tools import eq_
|
||||
|
||||
from mozillians.api.v1.resources import GraphiteMixIn
|
||||
from mozillians.common.tests import TestCase
|
||||
|
||||
|
||||
class GraphiteMixInTests(TestCase):
|
||||
@patch('mozillians.api.v1.resources.statsd.incr')
|
||||
def test_statsd_call(self, incr_mock):
|
||||
real_wrapper = MagicMock()
|
||||
view = MagicMock()
|
||||
|
||||
class Bar(object):
|
||||
def wrap_view(self, view):
|
||||
return real_wrapper
|
||||
|
||||
class Foo(GraphiteMixIn, Bar):
|
||||
def __init__(self):
|
||||
self.foo = view
|
||||
self.foo.im_class.__name__ = 'foo'
|
||||
self.foo.im_func.__name__ = 'bar'
|
||||
|
||||
foo = Foo()
|
||||
return_value = foo.wrap_view('foo')('request', 1, second=2)
|
||||
|
||||
eq_(return_value, real_wrapper.return_value)
|
||||
real_wrapper.assert_called_with('request', 1, second=2)
|
||||
incr_mock.assert_called_with('api.resources.foo.bar')
|
|
@ -2,21 +2,12 @@ from django.conf.urls import include, patterns, url
|
|||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from rest_framework import routers
|
||||
from tastypie.api import Api
|
||||
|
||||
import mozillians.groups.api.v1
|
||||
import mozillians.groups.api.v2
|
||||
import mozillians.users.api.v1
|
||||
import mozillians.users.api.v2
|
||||
from mozillians.users.views import VouchedAutocomplete
|
||||
|
||||
|
||||
# API v1 URLs
|
||||
v1_api = Api(api_name='v1')
|
||||
v1_api.register(mozillians.users.api.v1.UserResource())
|
||||
v1_api.register(mozillians.groups.api.v1.GroupResource())
|
||||
v1_api.register(mozillians.groups.api.v1.SkillResource())
|
||||
|
||||
# API v2 URLs
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'users', mozillians.users.api.v2.UserProfileViewSet)
|
||||
|
@ -25,7 +16,6 @@ router.register(r'skills', mozillians.groups.api.v2.SkillViewSet)
|
|||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'', include(v1_api.urls)),
|
||||
url(r'^v2/', include(router.urls), name='v2root'),
|
||||
# Django-autocomplete-light urls
|
||||
url(r'api-v2-autocomplete/$', login_required(VouchedAutocomplete.as_view()),
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
from django_statsd.clients import statsd
|
||||
|
||||
from tastypie.authentication import Authentication
|
||||
|
||||
from mozillians.api.models import APIApp
|
||||
|
||||
|
||||
class AppAuthentication(Authentication):
|
||||
"""App Authentication."""
|
||||
|
||||
def is_authenticated(self, request, **kwargs):
|
||||
"""Authenticate and authorize App."""
|
||||
app_key = request.GET.get('app_key', '')
|
||||
app_name = request.GET.get('app_name', '')
|
||||
|
||||
try:
|
||||
app = APIApp.objects.get(name__iexact=app_name, key=app_key, is_active=True)
|
||||
except APIApp.DoesNotExist:
|
||||
statsd.incr('api.auth.failed')
|
||||
return False
|
||||
|
||||
statsd.incr('api.auth.success')
|
||||
if not app.is_mozilla_app:
|
||||
statsd.incr('api.requests.total_community')
|
||||
data = request.GET.copy()
|
||||
data['restricted'] = True
|
||||
request.GET = data
|
||||
else:
|
||||
statsd.incr('api.requests.total_mozilla')
|
||||
return True
|
|
@ -1,31 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
from tastypie import paginator
|
||||
|
||||
|
||||
class Paginator(paginator.Paginator):
|
||||
"""Paginator with a hard limit on results per page."""
|
||||
|
||||
def get_limit(self):
|
||||
"""Determines the proper maximum number of results to return.
|
||||
|
||||
Overrides tastypie.paginator.Paginator class to provide a hard
|
||||
limit on number of results per page. Defaults to 500 results,
|
||||
can be adjusted through HARD_API_LIMIT_PER_PAGE variable in
|
||||
settings.
|
||||
|
||||
This should be replaced with 'max_limit' tastypie.Resource
|
||||
attribute when we upgrade to tastypie >= 0.9.12.
|
||||
|
||||
"""
|
||||
hard_limit = getattr(settings, 'HARD_API_LIMIT_PER_PAGE', 500)
|
||||
return min(super(Paginator, self).get_limit(), hard_limit)
|
||||
|
||||
def get_offset(self):
|
||||
"""Determines the proper starting offset of results to return.
|
||||
|
||||
Returns minimum value of offset as calculated by
|
||||
tastypie.paginator.Paginator and total objects to prevent
|
||||
Elastic Search crashes and timeouts.
|
||||
"""
|
||||
return min(super(Paginator, self).get_offset(), self.get_count())
|
|
@ -1,60 +0,0 @@
|
|||
from django.utils.cache import patch_cache_control
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
|
||||
|
||||
class ClientCacheResourceMixIn(object):
|
||||
"""
|
||||
Mixin class which sets Cache-Control headers on API responses
|
||||
using a ``cache_control`` dictionary from the resource's Meta
|
||||
class.
|
||||
|
||||
TODO: To be removed when we upgrade to django-tastypie >= 0.9.12.
|
||||
|
||||
Code from http://django-tastypie.readthedocs.org/en/latest/caching.html
|
||||
"""
|
||||
|
||||
def create_response(self, request, data, **response_kwargs):
|
||||
response = (super(ClientCacheResourceMixIn, self)
|
||||
.create_response(request, data, **response_kwargs))
|
||||
|
||||
if (request.method == 'GET' and response.status_code == 200 and
|
||||
hasattr(self.Meta, 'cache_control')):
|
||||
patch_cache_control(response, **self.Meta.cache_control)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class AdvancedSortingResourceMixIn(object):
|
||||
"""
|
||||
MixIn to allow sorting on multiple values in the same query.
|
||||
"""
|
||||
|
||||
def apply_sorting(self, obj_list, options=None):
|
||||
"""Allow sorting on multiple values. """
|
||||
sort_list = [order_value for order_value
|
||||
in options.get('order_by', '').split(',')
|
||||
if order_value.strip('-') in self.Meta.ordering]
|
||||
|
||||
if not sort_list:
|
||||
sort_list = self.Meta.default_order
|
||||
|
||||
return obj_list.order_by(*sort_list)
|
||||
|
||||
|
||||
class GraphiteMixIn(object):
|
||||
"""
|
||||
MixIn to post to graphite server every hit of API resource.
|
||||
"""
|
||||
|
||||
def wrap_view(self, view):
|
||||
real_wrapper = super(GraphiteMixIn, self).wrap_view(view)
|
||||
|
||||
def wrapper(request, *args, **kwargs):
|
||||
callback = getattr(self, view)
|
||||
counter_name = 'api.resources.{klass}.{func}'.format(
|
||||
klass=callback.im_class.__name__,
|
||||
func=callback.im_func.__name__)
|
||||
statsd.incr(counter_name)
|
||||
return real_wrapper(request, *args, **kwargs)
|
||||
return wrapper
|
|
@ -1,58 +0,0 @@
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Count, Sum
|
||||
|
||||
from tastypie import fields
|
||||
from tastypie.authorization import ReadOnlyAuthorization
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.serializers import Serializer
|
||||
|
||||
from mozillians.api.v1.authenticators import AppAuthentication
|
||||
from mozillians.api.v1.resources import (AdvancedSortingResourceMixIn,
|
||||
ClientCacheResourceMixIn,
|
||||
GraphiteMixIn)
|
||||
from mozillians.api.v1.paginator import Paginator
|
||||
from mozillians.common.utils import absolutify
|
||||
from mozillians.groups.models import Group, Skill
|
||||
|
||||
|
||||
class GroupBaseResource(AdvancedSortingResourceMixIn, ClientCacheResourceMixIn,
|
||||
GraphiteMixIn, ModelResource):
|
||||
number_of_members = fields.IntegerField(attribute='number_of_members',
|
||||
readonly=True)
|
||||
|
||||
class Meta:
|
||||
authentication = AppAuthentication()
|
||||
authorization = ReadOnlyAuthorization()
|
||||
cache_control = {'max-age': 0}
|
||||
list_allowed_methods = ['get']
|
||||
detail_allowed_methods = ['get']
|
||||
paginator_class = Paginator
|
||||
serializer = Serializer(formats=['json', 'jsonp'])
|
||||
fields = ['id', 'name', 'number_of_members']
|
||||
ordering = ['id', 'name', 'number_of_members']
|
||||
default_order = ['id']
|
||||
|
||||
|
||||
class GroupResource(GroupBaseResource):
|
||||
url = fields.CharField()
|
||||
|
||||
class Meta(GroupBaseResource.Meta):
|
||||
resource_name = 'groups'
|
||||
# This Sum hack counts the number of 1's in database, only
|
||||
# works with MySQL because it stores booleans as 0s and 1s
|
||||
queryset = (Group.objects
|
||||
.annotate(number_of_members=Count('members'))
|
||||
.filter(number_of_members__gt=0))
|
||||
|
||||
def dehydrate_url(self, bundle):
|
||||
url = reverse('groups:show_group', args=[bundle.obj.url])
|
||||
return absolutify(url)
|
||||
|
||||
|
||||
class SkillResource(GroupBaseResource):
|
||||
|
||||
class Meta(GroupBaseResource.Meta):
|
||||
resource_name = 'skills'
|
||||
queryset = (Skill.objects
|
||||
.annotate(number_of_members=Sum('members__is_vouched'))
|
||||
.filter(number_of_members__gt=0))
|
|
@ -1,70 +0,0 @@
|
|||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.client import Client
|
||||
|
||||
from nose.tools import eq_
|
||||
|
||||
from mozillians.api.tests import APIAppFactory
|
||||
from mozillians.common.templatetags.helpers import urlparams
|
||||
from mozillians.common.tests import TestCase
|
||||
from mozillians.common.utils import absolutify
|
||||
from mozillians.groups.tests import GroupFactory, SkillFactory
|
||||
from mozillians.users.tests import UserFactory
|
||||
|
||||
|
||||
class GroupResourceTests(TestCase):
|
||||
def setUp(self):
|
||||
self.resource_url = reverse(
|
||||
'api_dispatch_list',
|
||||
kwargs={'api_name': 'v1', 'resource_name': 'groups'})
|
||||
self.user = UserFactory.create()
|
||||
self.app = APIAppFactory.create(owner=self.user,
|
||||
is_mozilla_app=True)
|
||||
self.resource_url = urlparams(self.resource_url,
|
||||
app_name=self.app.name,
|
||||
app_key=self.app.key)
|
||||
|
||||
def test_list_groups(self):
|
||||
user = UserFactory.create()
|
||||
group = GroupFactory.create()
|
||||
group.add_member(user.userprofile)
|
||||
|
||||
client = Client()
|
||||
response = client.get(self.resource_url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['total_count'], 1)
|
||||
eq_(data['objects'][0]['name'], group.name)
|
||||
eq_(data['objects'][0]['number_of_members'], 1)
|
||||
eq_(int(data['objects'][0]['id']), group.id)
|
||||
eq_(data['objects'][0]['url'],
|
||||
absolutify(reverse('groups:show_group', args=[group.url])))
|
||||
|
||||
|
||||
class SkillResourceTests(TestCase):
|
||||
def setUp(self):
|
||||
self.resource_url = reverse(
|
||||
'api_dispatch_list',
|
||||
kwargs={'api_name': 'v1', 'resource_name': 'skills'})
|
||||
self.user = UserFactory.create()
|
||||
self.app = APIAppFactory.create(owner=self.user,
|
||||
is_mozilla_app=True)
|
||||
self.resource_url = urlparams(self.resource_url,
|
||||
app_name=self.app.name,
|
||||
app_key=self.app.key)
|
||||
|
||||
def test_list_skills(self):
|
||||
unvouched_user = UserFactory.create(vouched=False)
|
||||
user = UserFactory.create()
|
||||
skill = SkillFactory.create()
|
||||
skill.members.add(unvouched_user.userprofile)
|
||||
skill.members.add(user.userprofile)
|
||||
|
||||
client = Client()
|
||||
response = client.get(self.resource_url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['total_count'], 1)
|
||||
eq_(data['objects'][0]['name'], skill.name)
|
||||
eq_(data['objects'][0]['number_of_members'], 1,
|
||||
'List includes unvouched users')
|
||||
eq_(int(data['objects'][0]['id']), skill.id)
|
|
@ -11,39 +11,6 @@
|
|||
<h1>{{ _('Mozillians API') }}</h1>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
<h2>{{ _('API Version 1') }}</h2>
|
||||
</p>
|
||||
|
||||
<p class="field_description">
|
||||
{% trans api_schema_url='https://mozillians.readthedocs.org/en/latest/api/api.html',
|
||||
bugzilla_request_url=('https://bugzilla.mozilla.org/enter_bug.cgi?'
|
||||
'product=Participation Infrastructure&component=Phonebook') %}
|
||||
API v1 is phasing out, please consider using API v2. For any support requests for API v1
|
||||
file a bug <a href="{{ bugzilla_request_url }}">here</a>.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h3>{{ _('Your approved apps:') }}</h3>
|
||||
</p>
|
||||
|
||||
{% for app in apps %}
|
||||
<div class="api-key">
|
||||
<strong>{{ app.name }}</strong>
|
||||
{% if app.url %}
|
||||
<em>({{ app.url|urlize }})</em>
|
||||
{% endif %}
|
||||
<ul>
|
||||
<li>
|
||||
{{ _('Key') }}: {{ app.key }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ _('You have no active apps.') }}
|
||||
{% endfor %}
|
||||
|
||||
<p>
|
||||
<h2>{{ _('API Version 2') }}</h2>
|
||||
</p>
|
||||
|
|
|
@ -1,56 +1,22 @@
|
|||
<div role="tabpanel" class="tab-pane fade" id="developer">
|
||||
<form class="edit-profile" method="POST" action="{{ url('phonebook:profile_edit') }}?next=developer">
|
||||
{% csrf_token %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ _('API privacy') }}</div>
|
||||
<div class="panel-body">
|
||||
{% if developer_form.non_field_errors() %}
|
||||
<ul class="unstyled">
|
||||
{% for error in developer_form.non_field_errors() %}
|
||||
<li class="alert alert-error">{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<fieldset id="privacy" class="edit-controls">
|
||||
<p class="field_description">
|
||||
{% trans bugzilla_url='https://bugzilla.mozilla.org',
|
||||
reps_url='https://reps.mozilla.org' %}
|
||||
The Phonebook offers an API (currently v1) to help authorize contributors and share profile data to other
|
||||
tools and sites like <a id="services-bugzilla-url" href="{{ bugzilla_url }}">Bugzilla</a> and the
|
||||
<a id="services-mozilla-reps" href="{{ reps_url }}">Mozilla Reps Portal</a>.
|
||||
{% endtrans %}
|
||||
<h4>{{ _('Privacy') }}</h4>
|
||||
<p>
|
||||
{{ developer_form.allows_community_sites.label_tag() }}
|
||||
{{ developer_form.allows_community_sites }}
|
||||
</p>
|
||||
<p>
|
||||
{{ developer_form.allows_mozilla_sites.label_tag() }}
|
||||
{{ developer_form.allows_mozilla_sites }}
|
||||
</p>
|
||||
<p>
|
||||
{% trans %}
|
||||
API Version 2, currently under testing, respects the defined
|
||||
per field privacy levels. These fields will become obsolete
|
||||
and eventually dropped along with the API Version 1.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button class="btn btn-primary" id="form-submit-developer" type="submit"
|
||||
name="developer_section">{{ _('Update Privacy Settings') }}
|
||||
</button>
|
||||
{% trans bugzilla_url='https://bugzilla.mozilla.org',
|
||||
reps_url='https://reps.mozilla.org' %}
|
||||
The Phonebook offers an API (currently v2) to help authorize contributors and share profile
|
||||
data to other tools and sites like <a id="services-bugzilla-url" href="{{ bugzilla_url }}">Bugzilla</a> and the
|
||||
<a id="services-mozilla-reps" href="{{ reps_url }}">Mozilla Reps Portal</a>.
|
||||
The API respects the defined per field privacy levels.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
{% if user.userprofile.is_vouched %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ _('Developer') }}</div>
|
||||
<div class="panel-body">
|
||||
<fieldset id="api" class="edit-controls">
|
||||
<p>{{ _('Developers can request API keys and manage existing ones using our dedicated API page.') }}</p>
|
||||
<a class="cancel" href="{{ url('phonebook:apikeys') }}">{{ _('Manage API keys.') }}</a>
|
||||
</fieldset>
|
||||
<p>{{ _('Developers can request API keys and manage existing ones using our dedicated API page.') }}</p>
|
||||
<a class="btn btn-primary" href="{{ url('phonebook:apikeys') }}">{{ _('Manage API keys') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -337,12 +337,6 @@ class IRCForm(happyforms.ModelForm):
|
|||
fields = ('ircname', 'privacy_ircname',)
|
||||
|
||||
|
||||
class DeveloperForm(happyforms.ModelForm):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ('allows_community_sites', 'allows_mozilla_sites',)
|
||||
|
||||
|
||||
class BaseLanguageFormSet(BaseInlineFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -200,7 +200,6 @@ def edit_profile(request):
|
|||
'irc_section': ['irc_form'],
|
||||
'contribution_section': ['contribution_form'],
|
||||
'tshirt_section': ['tshirt_form'],
|
||||
'developer_section': ['developer_form']
|
||||
}
|
||||
|
||||
curr_sect = next((s for s in sections.keys() if s in request.POST), None)
|
||||
|
@ -236,8 +235,6 @@ def edit_profile(request):
|
|||
ctx['groups_privacy_form'] = forms.GroupsPrivacyForm(get_request_data('groups_privacy_form'),
|
||||
instance=profile)
|
||||
ctx['irc_form'] = forms.IRCForm(get_request_data('irc_form'), instance=profile)
|
||||
ctx['developer_form'] = forms.DeveloperForm(get_request_data('developer_form'),
|
||||
instance=profile)
|
||||
ctx['email_privacy_form'] = forms.EmailPrivacyForm(get_request_data('email_privacy_form'),
|
||||
instance=profile)
|
||||
alternate_email_formset_data = get_request_data('alternate_email_formset')
|
||||
|
@ -292,7 +289,6 @@ def edit_profile(request):
|
|||
'user_groups': user_groups,
|
||||
'profile': request.user.userprofile,
|
||||
'vouch_threshold': settings.CAN_VOUCH_THRESHOLD,
|
||||
'apps': user.apiapp_set.filter(is_active=True),
|
||||
'appsv2': profile.apps.filter(enabled=True),
|
||||
'forms_valid': forms_valid
|
||||
})
|
||||
|
@ -526,7 +522,6 @@ def apikeys(request):
|
|||
return redirect('phonebook:apikeys')
|
||||
|
||||
data = {
|
||||
'apps': request.user.apiapp_set.filter(is_active=True),
|
||||
'appsv2': profile.apps.filter(enabled=True),
|
||||
'apikey_request_form': apikey_request_form,
|
||||
}
|
||||
|
|
|
@ -75,7 +75,6 @@ LOCALE_PATHS = [path('locale')]
|
|||
EXEMPT_L10N_URLS = [
|
||||
'^/oidc/authenticate/',
|
||||
'^/oidc/callback/',
|
||||
'^/api/v1/',
|
||||
'^/api/v2/',
|
||||
'^/admin/'
|
||||
]
|
||||
|
|
|
@ -105,11 +105,6 @@ CSP_STYLE_SRC = base.CSP_STYLE_SRC + (
|
|||
'http://*.mozilla.org',
|
||||
)
|
||||
|
||||
# Basket
|
||||
BASKET_URL = 'http://127.0.0.1'
|
||||
BASKET_API_KEY = 'basket_api_key'
|
||||
BASKET_MANAGERS = None # or list of email addresses.
|
||||
|
||||
# Demo keys for ReCaptcha, no for prodution
|
||||
NORECAPTCHA_SITE_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
|
||||
NORECAPTCHA_SECRET_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
||||
|
|
|
@ -551,9 +551,6 @@ class UserProfileAdmin(AdminImageMixin, MozilliansAdminExportMixin, admin.ModelA
|
|||
'fields': ('geo_country', 'geo_region', 'geo_city',
|
||||
'lng', 'lat', 'timezone')
|
||||
}),
|
||||
('Services', {
|
||||
'fields': ('allows_community_sites', 'allows_mozilla_sites')
|
||||
}),
|
||||
('Privacy Settings', {
|
||||
'fields': ('privacy_photo', 'privacy_full_name', 'privacy_full_name_local',
|
||||
'privacy_ircname', 'privacy_email', 'privacy_bio',
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
from urllib2 import unquote
|
||||
from urlparse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from tastypie import fields, http
|
||||
from tastypie.authorization import ReadOnlyAuthorization
|
||||
from tastypie.bundle import Bundle
|
||||
from tastypie.exceptions import ImmediateHttpResponse
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.serializers import Serializer
|
||||
|
||||
from mozillians.api.v1.authenticators import AppAuthentication
|
||||
from mozillians.api.v1.paginator import Paginator
|
||||
from mozillians.api.v1.resources import (ClientCacheResourceMixIn,
|
||||
GraphiteMixIn)
|
||||
from mozillians.common import utils
|
||||
from mozillians.users.models import GroupMembership, UserProfile
|
||||
|
||||
|
||||
class UserResource(ClientCacheResourceMixIn, GraphiteMixIn, ModelResource):
|
||||
"""User Resource."""
|
||||
email = fields.CharField(attribute='user__email', null=True, readonly=True)
|
||||
username = fields.CharField(attribute='user__username', null=True, readonly=True)
|
||||
vouched_by = fields.IntegerField(attribute='vouched_by__id',
|
||||
null=True, readonly=True)
|
||||
date_vouched = fields.DateTimeField(attribute='date_vouched', null=True,
|
||||
readonly=True)
|
||||
|
||||
groups = fields.CharField()
|
||||
skills = fields.CharField()
|
||||
languages = fields.CharField()
|
||||
url = fields.CharField()
|
||||
accounts = fields.CharField()
|
||||
city = fields.CharField(attribute='city__name', null=True, readonly=True, default='')
|
||||
region = fields.CharField(attribute='region__name', null=True, readonly=True, default='')
|
||||
country = fields.CharField(attribute='country__code2', null=True, readonly=True, default='')
|
||||
photo_thumbnail = fields.CharField()
|
||||
|
||||
class Meta:
|
||||
queryset = UserProfile.objects.all()
|
||||
authentication = AppAuthentication()
|
||||
authorization = ReadOnlyAuthorization()
|
||||
serializer = Serializer(formats=['json', 'jsonp'])
|
||||
paginator_class = Paginator
|
||||
cache_control = {'max-age': 0}
|
||||
list_allowed_methods = ['get']
|
||||
detail_allowed_methods = ['get']
|
||||
resource_name = 'users'
|
||||
restrict_fields = False
|
||||
restricted_fields = ['email', 'is_vouched']
|
||||
fields = ['id', 'full_name', 'is_vouched', 'vouched_by',
|
||||
'date_vouched', 'groups', 'skills',
|
||||
'bio', 'photo', 'ircname', 'country', 'region', 'city',
|
||||
'date_mozillian', 'timezone', 'email', 'allows_mozilla_sites',
|
||||
'allows_community_sites']
|
||||
|
||||
def build_filters(self, filters=None):
|
||||
database_filters = {}
|
||||
valid_filters = [f for f in filters if f in
|
||||
['email', 'country', 'region', 'city', 'ircname',
|
||||
'username', 'groups', 'skills',
|
||||
'is_vouched', 'name', 'accounts']]
|
||||
getvalue = lambda x: unquote(filters[x].lower())
|
||||
|
||||
if 'accounts' in valid_filters:
|
||||
database_filters['accounts'] = Q(
|
||||
externalaccount__identifier__icontains=getvalue('accounts'))
|
||||
|
||||
if 'email' in valid_filters:
|
||||
database_filters['email'] = Q(
|
||||
user__email__iexact=getvalue('email'))
|
||||
|
||||
if 'username' in valid_filters:
|
||||
database_filters['username'] = Q(
|
||||
user__username__iexact=getvalue('username'))
|
||||
|
||||
if 'name' in valid_filters:
|
||||
database_filters['name'] = Q(full_name__iexact=getvalue('name'))
|
||||
|
||||
if 'is_vouched' in valid_filters:
|
||||
value = getvalue('is_vouched')
|
||||
if value == 'true':
|
||||
database_filters['is_vouched'] = Q(is_vouched=True)
|
||||
elif value == 'false':
|
||||
database_filters['is_vouched'] = Q(is_vouched=False)
|
||||
|
||||
if 'country' in valid_filters:
|
||||
database_filters['country'] = Q(country__code2=getvalue('country'))
|
||||
|
||||
if 'region' in valid_filters:
|
||||
database_filters['region'] = Q(region__name=getvalue('region'))
|
||||
|
||||
if 'city' in valid_filters:
|
||||
database_filters['city'] = Q(city__name=getvalue('city'))
|
||||
|
||||
if 'ircname' in valid_filters:
|
||||
database_filters['ircname'] = Q(
|
||||
**{'{0}__iexact'.format('ircname'):
|
||||
getvalue('ircname')})
|
||||
|
||||
if 'groups' in valid_filters:
|
||||
kwargs = {
|
||||
'groups__name__in': getvalue('groups').split(','),
|
||||
'groupmembership__status': GroupMembership.MEMBER
|
||||
}
|
||||
database_filters['groups'] = Q(**kwargs)
|
||||
|
||||
if 'skills' in valid_filters:
|
||||
database_filters['skills'] = Q(skills__name__in=getvalue('skills').split(','))
|
||||
|
||||
return database_filters
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
if (bundle.request.GET.get('restricted', False) or not
|
||||
bundle.data['allows_mozilla_sites']):
|
||||
data = {}
|
||||
for key in self._meta.restricted_fields:
|
||||
data[key] = bundle.data[key]
|
||||
bundle = Bundle(obj=bundle.obj, data=data, request=bundle.request)
|
||||
return bundle
|
||||
|
||||
def dehydrate_accounts(self, bundle):
|
||||
accounts = [{'identifier': a.identifier, 'type': a.type}
|
||||
for a in bundle.obj.externalaccount_set.all()]
|
||||
return accounts
|
||||
|
||||
def dehydrate_groups(self, bundle):
|
||||
groups = bundle.obj.groups.values_list('name', flat=True)
|
||||
return list(groups)
|
||||
|
||||
def dehydrate_skills(self, bundle):
|
||||
skills = bundle.obj.skills.values_list('name', flat=True)
|
||||
return list(skills)
|
||||
|
||||
def dehydrate_languages(self, bundle):
|
||||
languages = bundle.obj.languages.values_list('code', flat=True)
|
||||
return list(languages)
|
||||
|
||||
def dehydrate_photo(self, bundle):
|
||||
if bundle.obj.photo:
|
||||
return urljoin(settings.SITE_URL, bundle.obj.photo.url)
|
||||
return ''
|
||||
|
||||
def dehydrate_photo_thumbnail(self, bundle):
|
||||
return urljoin(settings.SITE_URL, bundle.obj.get_photo_url())
|
||||
|
||||
def dehydrate_url(self, bundle):
|
||||
url = reverse('phonebook:profile_view',
|
||||
args=[bundle.obj.user.username])
|
||||
return utils.absolutify(url)
|
||||
|
||||
def get_detail(self, request, **kwargs):
|
||||
if request.GET.get('restricted', False):
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
return super(UserResource, self).get_detail(request, **kwargs)
|
||||
|
||||
def apply_filters(self, request, applicable_filters):
|
||||
if (request.GET.get('restricted', False) and
|
||||
'email' not in applicable_filters and len(applicable_filters) != 1):
|
||||
raise ImmediateHttpResponse(response=http.HttpForbidden())
|
||||
|
||||
mega_filter = Q()
|
||||
for db_filter in applicable_filters.values():
|
||||
mega_filter &= db_filter
|
||||
|
||||
if request.GET.get('restricted', False):
|
||||
mega_filter &= Q(allows_community_sites=True)
|
||||
|
||||
return UserProfile.objects.complete().filter(mega_filter).distinct().order_by('id')
|
|
@ -74,8 +74,6 @@ class UserProfileMappingType(MappingType, Indexable):
|
|||
'languages': {'type': 'string', 'index': 'not_analyzed'},
|
||||
'bio': {'type': 'string', 'analyzer': 'snowball'},
|
||||
'is_vouched': {'type': 'boolean'},
|
||||
'allows_mozilla_sites': {'type': 'boolean'},
|
||||
'allows_community_sites': {'type': 'boolean'},
|
||||
'photo': {'type': 'boolean'},
|
||||
'last_updated': {'type': 'date'},
|
||||
'date_joined': {'type': 'date'}
|
||||
|
@ -124,8 +122,7 @@ class UserProfileMappingType(MappingType, Indexable):
|
|||
obj = cls.get_model().objects.get(pk=obj_id)
|
||||
doc = {}
|
||||
|
||||
attrs = ('id', 'is_vouched', 'ircname',
|
||||
'allows_mozilla_sites', 'allows_community_sites')
|
||||
attrs = ('id', 'is_vouched', 'ircname', )
|
||||
for a in attrs:
|
||||
data = getattr(obj, a)
|
||||
if isinstance(data, basestring):
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0015_auto_20170323_1202'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='userprofile',
|
||||
name='allows_community_sites',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userprofile',
|
||||
name='allows_mozilla_sites',
|
||||
),
|
||||
]
|
|
@ -177,15 +177,6 @@ class UserProfile(UserProfilePrivacyModel):
|
|||
country = models.ForeignKey('cities_light.Country', blank=True, null=True,
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
allows_community_sites = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_lazy(u'Sites that can determine my vouched status'),
|
||||
choices=((True, _lazy(u'All Community Sites')),
|
||||
(False, _lazy(u'Only Mozilla Properties'))))
|
||||
allows_mozilla_sites = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_lazy(u'Allow Mozilla sites to access my profile data?'),
|
||||
choices=((True, _lazy(u'Yes')), (False, _lazy(u'No'))))
|
||||
basket_token = models.CharField(max_length=1024, default='', blank=True)
|
||||
date_mozillian = models.DateField('When was involved with Mozilla',
|
||||
null=True, blank=True, default=None)
|
||||
|
|
|
@ -1,391 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from nose.tools import eq_, ok_
|
||||
|
||||
from mozillians.api.tests import APIAppFactory
|
||||
from mozillians.common.templatetags.helpers import urlparams
|
||||
from mozillians.common.tests import TestCase
|
||||
from mozillians.common.utils import absolutify
|
||||
from mozillians.groups.models import GroupMembership
|
||||
from mozillians.groups.tests import GroupFactory, SkillFactory
|
||||
from mozillians.users.models import ExternalAccount
|
||||
from mozillians.users.tests import CityFactory, CountryFactory, RegionFactory, UserFactory
|
||||
|
||||
|
||||
@override_settings(CAN_VOUCH_THRESHOLD=1)
|
||||
class UserResourceTests(TestCase):
|
||||
def setUp(self):
|
||||
voucher = UserFactory.create()
|
||||
country = CountryFactory()
|
||||
region = RegionFactory()
|
||||
city = CityFactory()
|
||||
self.user = UserFactory.create(
|
||||
userprofile={'vouched': False,
|
||||
'country': country,
|
||||
'region': region,
|
||||
'city': city})
|
||||
self.user.userprofile.vouch(voucher.userprofile)
|
||||
group = GroupFactory.create()
|
||||
group.add_member(self.user.userprofile)
|
||||
skill = SkillFactory.create()
|
||||
self.user.userprofile.skills.add(skill)
|
||||
self.user.userprofile.externalaccount_set.create(type=ExternalAccount.TYPE_SUMO,
|
||||
identifier='Apitest')
|
||||
|
||||
self.resource_url = reverse(
|
||||
'api_dispatch_list',
|
||||
kwargs={'api_name': 'v1', 'resource_name': 'users'})
|
||||
self.mozilla_app = APIAppFactory.create(
|
||||
owner=self.user, is_mozilla_app=True)
|
||||
self.mozilla_resource_url = urlparams(
|
||||
self.resource_url, app_name=self.mozilla_app.name,
|
||||
app_key=self.mozilla_app.key)
|
||||
self.community_app = APIAppFactory.create(
|
||||
owner=self.user, is_mozilla_app=False)
|
||||
self.community_resource_url = urlparams(
|
||||
self.resource_url, app_name=self.community_app.name,
|
||||
app_key=self.community_app.key)
|
||||
|
||||
def test_get_list_mozilla_app(self):
|
||||
client = Client()
|
||||
response = client.get(self.mozilla_resource_url, follow=True)
|
||||
eq_(response.status_code, 200)
|
||||
ok_(json.loads(response.content))
|
||||
|
||||
def test_get_list_community_app(self):
|
||||
client = Client()
|
||||
response = client.get(self.community_resource_url, follow=True)
|
||||
eq_(response.status_code, 403)
|
||||
|
||||
def test_get_detail_mozilla_app(self):
|
||||
client = Client()
|
||||
url = reverse('api_dispatch_detail',
|
||||
kwargs={'api_name': 'v1', 'resource_name': 'users',
|
||||
'pk': self.user.userprofile.id})
|
||||
url = urlparams(url, app_name=self.mozilla_app.name,
|
||||
app_key=self.mozilla_app.key)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
profile = self.user.userprofile
|
||||
eq_(response.status_code, 200)
|
||||
eq_(data['id'], profile.id)
|
||||
eq_(data['full_name'], profile.full_name)
|
||||
eq_(data['is_vouched'], profile.is_vouched)
|
||||
eq_(data['vouched_by'], profile.vouched_by.id)
|
||||
# eq_(data['date_vouched'], profile.date_vouched)
|
||||
eq_(data['groups'], list(profile.groups.values_list('name', flat=True)))
|
||||
eq_(data['skills'], list(profile.skills.values_list('name', flat=True)))
|
||||
eq_(data['accounts'],
|
||||
[{'identifier': a.identifier, 'type': a.type}
|
||||
for a in profile.externalaccount_set.all()])
|
||||
eq_(data['bio'], profile.bio)
|
||||
eq_(data['photo'], profile.photo)
|
||||
eq_(data['photo_thumbnail'], profile.get_photo_url())
|
||||
eq_(data['ircname'], profile.ircname)
|
||||
eq_(data['country'], profile.country.code2)
|
||||
eq_(data['region'], profile.region.name)
|
||||
eq_(data['city'], profile.city.name)
|
||||
eq_(data['date_mozillian'], profile.date_mozillian)
|
||||
eq_(data['timezone'], profile.timezone)
|
||||
eq_(data['email'], profile.email)
|
||||
eq_(data['url'],
|
||||
absolutify(reverse('phonebook:profile_view',
|
||||
args=[profile.user.username])))
|
||||
|
||||
def test_get_detail_community_app(self):
|
||||
client = Client()
|
||||
url = reverse('api_dispatch_detail',
|
||||
kwargs={'api_name': 'v1', 'resource_name': 'users',
|
||||
'pk': self.user.userprofile.id})
|
||||
url = urlparams(url, app_name=self.community_app.name,
|
||||
app_key=self.community_app.key)
|
||||
response = client.get(url, follow=True)
|
||||
eq_(response.status_code, 403)
|
||||
|
||||
@override_settings(HARD_API_LIMIT_PER_PAGE=10)
|
||||
def test_request_with_normal_limit(self):
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, limit=5)
|
||||
response = client.get(url, follow=True)
|
||||
eq_(response.status_code, 200)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['limit'], 5)
|
||||
|
||||
@override_settings(HARD_API_LIMIT_PER_PAGE=1)
|
||||
def test_request_with_huge_limit(self):
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, limit=200000000000000000000)
|
||||
response = client.get(url, follow=True)
|
||||
eq_(response.status_code, 200)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['limit'], 1)
|
||||
|
||||
def test_request_with_normal_offset(self):
|
||||
UserFactory.create()
|
||||
UserFactory.create()
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, offset=1)
|
||||
response = client.get(url, follow=True)
|
||||
eq_(response.status_code, 200)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['offset'], 1)
|
||||
|
||||
def test_request_with_huge_offset(self):
|
||||
UserFactory.create()
|
||||
UserFactory.create()
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, offset=100000000)
|
||||
response = client.get(url, follow=True)
|
||||
eq_(response.status_code, 200)
|
||||
data = json.loads(response.content)
|
||||
eq_(data['meta']['offset'], data['meta']['total_count'])
|
||||
|
||||
def test_is_vouched_true(self):
|
||||
UserFactory.create()
|
||||
UserFactory.create(vouched=False)
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, is_vouched='true')
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
for obj in data['objects']:
|
||||
ok_(obj['is_vouched'])
|
||||
|
||||
def test_is_vouched_false(self):
|
||||
UserFactory.create()
|
||||
user = UserFactory.create(vouched=False)
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, is_vouched='false')
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_accounts(self):
|
||||
client = Client()
|
||||
user_1 = UserFactory.create()
|
||||
user_1.userprofile.externalaccount_set.create(type=ExternalAccount.TYPE_SUMO,
|
||||
identifier='AccountTest')
|
||||
user_2 = UserFactory.create()
|
||||
user_2.userprofile.externalaccount_set.create(type=ExternalAccount.TYPE_SUMO,
|
||||
identifier='AccountTest')
|
||||
|
||||
url = urlparams(self.mozilla_resource_url, accounts='count')
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 2)
|
||||
eq_(data['objects'][0]['accounts'][0]['identifier'], 'AccountTest')
|
||||
|
||||
def test_search_skills(self):
|
||||
client = Client()
|
||||
skill_1 = SkillFactory.create()
|
||||
skill_2 = SkillFactory.create()
|
||||
user_1 = UserFactory.create()
|
||||
user_1.userprofile.skills.add(skill_1)
|
||||
user_2 = UserFactory.create()
|
||||
user_2.userprofile.skills.add(skill_2)
|
||||
|
||||
url = urlparams(self.mozilla_resource_url, skills=skill_1.name)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user_1.userprofile.id)
|
||||
|
||||
def test_search_groups(self):
|
||||
client = Client()
|
||||
group_1 = GroupFactory.create()
|
||||
group_2 = GroupFactory.create()
|
||||
user_1 = UserFactory.create()
|
||||
group_1.add_member(user_1.userprofile)
|
||||
user_2 = UserFactory.create()
|
||||
group_2.add_member(user_2.userprofile)
|
||||
|
||||
url = urlparams(self.mozilla_resource_url, groups=group_1.name)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user_1.userprofile.id)
|
||||
|
||||
def test_search_groups_mixed_membership(self):
|
||||
client = Client()
|
||||
group = GroupFactory.create()
|
||||
user_1 = UserFactory.create()
|
||||
user_2 = UserFactory.create()
|
||||
group.add_member(user_1.userprofile)
|
||||
group.add_member(user_2.userprofile, GroupMembership.PENDING)
|
||||
|
||||
url = urlparams(self.mozilla_resource_url, groups=group.name)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user_1.userprofile.id)
|
||||
|
||||
def test_search_groups_pending_membership(self):
|
||||
client = Client()
|
||||
group = GroupFactory.create()
|
||||
user_1 = UserFactory.create()
|
||||
user_2 = UserFactory.create()
|
||||
group.add_member(user_1.userprofile, GroupMembership.PENDING)
|
||||
group.add_member(user_2.userprofile, GroupMembership.PENDING)
|
||||
|
||||
url = urlparams(self.mozilla_resource_url, groups=group.name)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 0)
|
||||
|
||||
def test_search_combined_skills_country(self):
|
||||
country = CountryFactory.create(code2='fr')
|
||||
user_1 = UserFactory.create(userprofile={'country': country})
|
||||
UserFactory.create(userprofile={'country': country})
|
||||
skill = SkillFactory.create()
|
||||
user_1.userprofile.skills.add(skill)
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
skills=skill.name, country=country.code2)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user_1.userprofile.id)
|
||||
|
||||
def test_query_with_space(self):
|
||||
city = CityFactory.create(name='Mountain View')
|
||||
user = UserFactory.create(userprofile={'city': city})
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, city='mountain view')
|
||||
request = client.get(url, follow=True)
|
||||
data = json.loads(request.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_name(self):
|
||||
user = UserFactory.create(userprofile={'full_name': u'Νίκος Κούκος'})
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
name=user.userprofile.full_name)
|
||||
request = client.get(url, follow=True)
|
||||
data = json.loads(request.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_username(self):
|
||||
user = UserFactory.create()
|
||||
url = urlparams(self.mozilla_resource_url, username=user.username)
|
||||
client = Client()
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_country(self):
|
||||
country = CountryFactory.create(code2='fr')
|
||||
user = UserFactory.create(userprofile={'country': country})
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
country=user.userprofile.country.code2)
|
||||
client = Client()
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_region(self):
|
||||
region = RegionFactory.create(name='la lo')
|
||||
user = UserFactory.create(userprofile={'region': region})
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
region=user.userprofile.region.name)
|
||||
client = Client()
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_city(self):
|
||||
city = CityFactory.create(name=u'αθήνα')
|
||||
user = UserFactory.create(userprofile={'city': city})
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
city=user.userprofile.city.name)
|
||||
client = Client()
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_search_ircname(self):
|
||||
user = UserFactory.create(userprofile={'ircname': 'nikos'})
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
ircname=user.userprofile.ircname)
|
||||
client = Client()
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['id'], user.userprofile.id)
|
||||
|
||||
def test_community_app_does_not_allow_community_sites(self):
|
||||
user = UserFactory.create(userprofile={'allows_community_sites': False})
|
||||
client = Client()
|
||||
url = urlparams(self.community_resource_url, email=user.email)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 0)
|
||||
|
||||
def test_community_app_does_allows_community_sites(self):
|
||||
user = UserFactory.create(userprofile={'allows_community_sites': True})
|
||||
client = Client()
|
||||
url = urlparams(self.community_resource_url, email=user.email)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(len(data['objects'][0]), 2)
|
||||
eq_(data['objects'][0]['email'], user.email)
|
||||
eq_(data['objects'][0]['is_vouched'], user.userprofile.is_vouched)
|
||||
|
||||
def test_mozillian_app_does_not_allow_mozilla_sites(self):
|
||||
user = UserFactory.create(userprofile={'allows_mozilla_sites': False})
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, email=user.email)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(len(data['objects'][0]), 2)
|
||||
eq_(data['objects'][0]['email'], user.email)
|
||||
eq_(data['objects'][0]['is_vouched'], user.userprofile.is_vouched)
|
||||
|
||||
def test_mozilla_app_does_allows_mozilla_sites(self):
|
||||
user = UserFactory.create(userprofile={'allows_mozilla_sites': True})
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url, email=user.email)
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['email'], user.email)
|
||||
|
||||
def test_only_completed_profiles(self):
|
||||
user = UserFactory.create(userprofile={'full_name': ''})
|
||||
client = Client()
|
||||
response = client.get(self.mozilla_resource_url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 2)
|
||||
for obj in data['objects']:
|
||||
ok_(obj['email'] != user.email)
|
||||
|
||||
def test_distinct_results(self):
|
||||
user = UserFactory.create()
|
||||
group_1 = GroupFactory.create()
|
||||
group_2 = GroupFactory.create()
|
||||
group_1.add_member(user.userprofile)
|
||||
group_2.add_member(user.userprofile)
|
||||
client = Client()
|
||||
url = urlparams(self.mozilla_resource_url,
|
||||
groups=','.join([group_1.name, group_2.name]))
|
||||
response = client.get(url, follow=True)
|
||||
data = json.loads(response.content)
|
||||
eq_(response.status_code, 200)
|
||||
eq_(len(data['objects']), 1)
|
|
@ -202,9 +202,7 @@ class UserProfileTests(TestCase):
|
|||
eq_(profile.full_name, 'foobar')
|
||||
|
||||
def test_extract_document(self):
|
||||
user = UserFactory.create(userprofile={'allows_community_sites': False,
|
||||
'allows_mozilla_sites': False,
|
||||
'full_name': 'Nikos Koukos',
|
||||
user = UserFactory.create(userprofile={'full_name': 'Nikos Koukos',
|
||||
'bio': 'This is my bio'})
|
||||
profile = user.userprofile
|
||||
group_1 = GroupFactory.create()
|
||||
|
@ -224,8 +222,6 @@ class UserProfileTests(TestCase):
|
|||
eq_(result['is_vouched'], profile.is_vouched)
|
||||
eq_(result['region'], 'attika')
|
||||
eq_(result['city'], 'athens')
|
||||
eq_(result['allows_community_sites'], profile.allows_community_sites)
|
||||
eq_(result['allows_mozilla_sites'], profile.allows_mozilla_sites)
|
||||
eq_(set(result['country']), set(['gr', 'greece']))
|
||||
eq_(result['fullname'], profile.full_name.lower())
|
||||
eq_(result['name'], profile.full_name.lower())
|
||||
|
|
|
@ -29,9 +29,6 @@ django-sha2==0.4 \
|
|||
--hash=sha256:b06a86451a99d6c26bfa65082999dbf658e087e596e9292cce688b53eabaaada
|
||||
django-statsd-mozilla==0.3.8.2 \
|
||||
--hash=sha256:d305466c95eb580f82f8d1db14bcb90b42648671c274a8f4032ba24dbc7bb00e
|
||||
django-tastypie==0.12.2 \
|
||||
--hash=sha256:21625ce191ccb734f47b90ef92b603d04366bb67d0eb52bd060a8d76e6706018 \
|
||||
--hash=sha256:7feccf811a8c2c60bc227e0d956015f0ea6097c804660e8f4a4ada0e74817c24
|
||||
Babel==2.3.4 \
|
||||
--hash=sha256:3318ed2960240d61cbc6558858ee00c10eed77a6508c4d1ed8e6f7f48399c975 \
|
||||
--hash=sha256:c535c4403802f6eb38173cd4863e419e2274921a01a8aad8a5b497c131c62875
|
||||
|
@ -110,15 +107,10 @@ Pillow==4.0.0 \
|
|||
--hash=sha256:6df6b68b00ff0e5bd13ef7b45147056dc4493261495efd3f3cb41a49c94d5ee7 \
|
||||
--hash=sha256:3eb056415b5a98a9d6a387ed224fa5cf1df34ca4e94d6f04772709af9f0aba73 \
|
||||
--hash=sha256:6df335aff243d9bc5e3b68855ef64bc1f16817f4d5dd71ae41563facb3cefdd6
|
||||
python-dateutil==1.5 \
|
||||
--hash=sha256:6f197348b46fb8cdf9f3fcfc2a7d5a97da95db3e2e8667cf657216274fe1b009
|
||||
amqplib==1.0.2 \
|
||||
--hash=sha256:843d69b681a60afd21fbf50f310404ec67fcdf9d13dfcf6e9d41f3b456217e5b
|
||||
python-memcached==1.53 \
|
||||
--hash=sha256:bcf71371d997bb46a3168a7b63aae66b56cccacc025af9310db4315681ef8868
|
||||
python-mimeparse==0.1.4 \
|
||||
--hash=sha256:a4baeab6061428239d87c9b619f814bc6bd66b9c4c71c023301a50ddc519eeb4 \
|
||||
--hash=sha256:3c69a21e37e77f754e6fc09ebda70acd92c90d8a58f29a41cc0248351378ddc3
|
||||
basket-client==0.3.12 \
|
||||
--hash=sha256:d7aa5e6208eeeabb8f1387a7cbb2d0f2b35dfa889c1f034c86a88b4968db3bfe \
|
||||
--hash=sha256:2dd953cd03b3068d0e596aa32224488c486b1170e46387c5bc14f20737d6a2d8
|
||||
|
|
Загрузка…
Ссылка в новой задаче