Skip to content

Commit

Permalink
Merge pull request #8 from gaiaresources/BIOSYS-18_StatsEndpoints
Browse files Browse the repository at this point in the history
Added statistics + whoami endpoints
  • Loading branch information
tony-gaia authored Dec 14, 2016
2 parents 33edf9e + ba25120 commit debd7ae
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 5 deletions.
14 changes: 14 additions & 0 deletions biosys/apps/main/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.utils import timezone
from rest_framework import serializers
Expand All @@ -10,6 +11,18 @@
from main.models import Project, Site, Dataset, GenericRecord, Observation, SpeciesObservation


class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('username', 'first_name', 'last_name', 'email')


class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
exclude = ('password',)


class ProjectSerializer(serializers.ModelSerializer):
timezone = serializers.CharField(required=False)

Expand Down Expand Up @@ -79,6 +92,7 @@ class GenericRecordListSerializer(serializers.ListSerializer):
"""
This serializer uses the django bulk_create instead of creating one by one.
"""

def create(self, validated_data):
records = []
model = self.child.Meta.model
Expand Down
4 changes: 3 additions & 1 deletion biosys/apps/main/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

url_patterns = [
url(r'projects?/(?P<pk>\d+)/sites/?', main_views.ProjectSitesView.as_view(), name='project-sites'), # bulk sites
url(r'datasets?/(?P<pk>\d+)/data/?', main_views.DatasetDataView.as_view(), name='dataset-data') # bulk data upload
url(r'datasets?/(?P<pk>\d+)/data/?', main_views.DatasetDataView.as_view(), name='dataset-data'), # bulk data upload
url(r'statistics/?', main_views.StatisticsView.as_view(), name="statistics"),
url(r'whoami/?', main_views.WhoamiView.as_view(), name="whoami")
]

urls = router.urls + url_patterns
Expand Down
57 changes: 56 additions & 1 deletion biosys/apps/main/api/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from __future__ import absolute_import, unicode_literals, print_function, division

from collections import OrderedDict

from django.shortcuts import get_object_or_404
from dry_rest_permissions.generics import DRYPermissions
from rest_framework import viewsets, filters, generics
from rest_framework.permissions import IsAuthenticated, BasePermission, SAFE_METHODS
from rest_framework.views import APIView
from rest_framework.views import Response

from main import models
from main.api import serializers
from main.models import Project, Site, Dataset
from main.models import Project, Site, Dataset, GenericRecord, Observation, SpeciesObservation
from main.utils_auth import is_admin
from main.utils_species import HerbieFacade

Expand Down Expand Up @@ -180,3 +184,54 @@ def get_serializer_context(self):
if 'species_mapping' not in ctx:
ctx['species_mapping'] = self.species_facade_class().name_id_by_species_name()
return ctx


class StatisticsView(APIView):
permission_classes = (IsAuthenticated,)

def get(self, request, **kwargs):
data = OrderedDict()
qs = Project.objects.all()
data['projects'] = {
'total': qs.count()
}
qs = Dataset.objects.all()
data['datasets'] = OrderedDict([
('total', qs.count()),
('generic', {
'total': qs.filter(type=Dataset.TYPE_GENERIC).count()
}),
('observation', {
'total': qs.filter(type=Dataset.TYPE_OBSERVATION).count()
}),
('speciesObservation', {
'total': qs.filter(type=Dataset.TYPE_SPECIES_OBSERVATION).count()
}),
])
# records
generic_records_count = GenericRecord.objects.count()
observation_record_count = Observation.objects.count()
species_observation_count = SpeciesObservation.objects.count()
data['records'] = OrderedDict([
('total', generic_records_count + observation_record_count + species_observation_count),
('generic', {
'total': generic_records_count
}),
('observation', {
'total': observation_record_count
}),
('speciesObservation', {
'total': species_observation_count
}),
])
return Response(data)


class WhoamiView(APIView):
serializers = serializers.SimpleUserSerializer

def get(self, request, **kwargs):
data = {}
if request.user.is_authenticated():
data = self.serializers(request.user).data
return Response(data)
101 changes: 101 additions & 0 deletions biosys/apps/main/tests/api/test_misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from django.test import TestCase
from django_dynamic_fixture import G
from django.shortcuts import reverse
from rest_framework import status
from rest_framework.test import APIClient
from django.contrib.auth import get_user_model


class TestWhoAmI(TestCase):
def setUp(self):
self.url = reverse('api:whoami')

def test_get(self):
anonymous = APIClient()
client = anonymous
self.assertEqual(
client.get(self.url).status_code,
status.HTTP_401_UNAUTHORIZED
)

user = G(get_user_model())
user.set_password('password')
user.save()
client = APIClient()
self.assertTrue(client.login(username=user.username, password='password'))
resp = client.get(self.url)
self.assertEqual(
resp.status_code,
status.HTTP_200_OK
)
# test that the response contains username, first and last name and email at least
data = resp.json()
self.assertEqual(user.username, data['username'])
self.assertEqual(user.first_name, data['first_name'])
self.assertEqual(user.last_name, data['last_name'])
self.assertEqual(user.email, data['email'])

# test that the password is not in the returned fields
self.assertFalse('password' in data)

def test_not_allowed_methods(self):
user = G(get_user_model())
user.set_password('password')
user.save()
client = APIClient()
self.assertTrue(client.login(username=user.username, password='password'))
self.assertEqual(
client.post(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(
client.put(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(
client.patch(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)


class TestStatistics(TestCase):
def setUp(self):
self.url = reverse('api:statistics')

def test_get(self):
anonymous = APIClient()
client = anonymous
self.assertEqual(
client.get(self.url).status_code,
status.HTTP_401_UNAUTHORIZED
)

user = G(get_user_model())
user.set_password('password')
user.save()
client = APIClient()
self.assertTrue(client.login(username=user.username, password='password'))
resp = client.get(self.url)
self.assertEqual(
resp.status_code,
status.HTTP_200_OK
)

def test_not_allowed_methods(self):
user = G(get_user_model())
user.set_password('password')
user.save()
client = APIClient()
self.assertTrue(client.login(username=user.username, password='password'))
self.assertEqual(
client.post(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(
client.put(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(
client.patch(self.url, {}).status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
1 change: 0 additions & 1 deletion biosys/apps/publish/views/data_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class DataView(LoginRequiredMixin, TemplateView):

def get_context_data(self, **kwargs):
if 'projects' not in kwargs:
# kwargs['projects'] = [ds.project for ds in DataSet.objects.all().order_by('project').distinct('project')]
kwargs['projects'] = Project.objects.all()
return super(DataView, self).get_context_data(**kwargs)

Expand Down
6 changes: 4 additions & 2 deletions biosys/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
"""
from __future__ import absolute_import, unicode_literals, print_function, division

from confy import env, database
import os
import sys

from confy import env, database
from unipath import Path

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
Expand Down Expand Up @@ -129,7 +130,8 @@
'rest_framework.renderers.JSONRenderer',
# 'rest_framework.renderers.BrowsableAPIRenderer', # commented because we use the swagger explorer
),
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
}

SWAGGER_SETTINGS = {
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mixer>=5.4.1,<6.0
django-extensions>=1.7.2,<1.8
ipython>=5.1.0
Werkzeug>=0.10.4,<1.0
django-dynamic-fixture>=1.9.0,<2.0



Expand Down

0 comments on commit debd7ae

Please sign in to comment.