[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:
Nikos Roussos 2017-03-29 14:28:08 +03:00
Родитель bbf2ad49fd
Коммит 6063638f2b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: BADFF1767BA7C8E1
34 изменённых файлов: 57 добавлений и 1552 удалений

Просмотреть файл

@ -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