diff --git a/geonode/api/filters.py b/geonode/api/filters.py
new file mode 100644
index 00000000000..3d33ec983b7
--- /dev/null
+++ b/geonode/api/filters.py
@@ -0,0 +1,64 @@
+#########################################################################
+#
+# Copyright (C) 2016 OSGeo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+#########################################################################
+
+from django_filters import rest_framework as filters
+from geonode.groups.models import GroupCategory, GroupProfile
+from django.contrib.auth.models import Group
+
+TEXT_LOOKUPS = (
+ "exact",
+ "contains",
+ "icontains",
+ "startswith",
+ "istartswith",
+ "endswith",
+ "iendswith",
+ "in",
+ "isnull",
+)
+
+
+class GroupCategoryFilter(filters.FilterSet):
+ class Meta:
+ model = GroupCategory
+ fields = {
+ "slug": TEXT_LOOKUPS,
+ "name": TEXT_LOOKUPS,
+ }
+
+
+class GroupProfileFilter(filters.FilterSet):
+ class Meta:
+ model = GroupProfile
+ fields = {
+ "title": TEXT_LOOKUPS,
+ "slug": TEXT_LOOKUPS,
+ "categories__slug": TEXT_LOOKUPS,
+ "categories__name": TEXT_LOOKUPS,
+ }
+
+
+class GroupFilter(filters.FilterSet):
+ class Meta:
+ model = Group
+ fields = {
+ "name": TEXT_LOOKUPS,
+ "groupprofile__title": TEXT_LOOKUPS,
+ "groupprofile__slug": TEXT_LOOKUPS,
+ }
diff --git a/geonode/api/serializers.py b/geonode/api/serializers.py
new file mode 100644
index 00000000000..31160ba2c7b
--- /dev/null
+++ b/geonode/api/serializers.py
@@ -0,0 +1,122 @@
+#########################################################################
+#
+# Copyright (C) 2016 OSGeo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+#########################################################################
+from rest_framework import serializers
+from dynamic_rest.serializers import DynamicModelSerializer
+
+from geonode.groups.models import GroupCategory, GroupProfile
+from django.contrib.auth.models import Group
+from django.db.models import Q
+from .api import _get_resource_counts
+from django.urls import reverse
+
+
+class GroupCategorySerializer(DynamicModelSerializer):
+ detail_url = serializers.SerializerMethodField()
+ member_count = serializers.SerializerMethodField()
+ resource_counts = serializers.SerializerMethodField()
+
+ class Meta:
+ model = GroupCategory
+ fields = ["id", "slug", "name", "detail_url", "member_count", "resource_counts"]
+
+ def get_detail_url(self, obj):
+ return obj.get_absolute_url()
+
+ def get_member_count(self, obj):
+ request = self.context.get("request")
+ if not request:
+ return 0
+ user = request.user
+ filtered = obj.groups.all()
+
+ if not user.is_authenticated:
+ filtered = filtered.exclude(access="private")
+ elif not user.is_superuser:
+ filtered = filtered.filter(Q(id__in=user.group_list_all()) | ~Q(access="private"))
+
+ return filtered.count()
+
+ def get_resource_counts(self, obj):
+ request = self.context.get("request")
+ if not request:
+ return {}
+ return _get_resource_counts(
+ request,
+ resourcebase_filter_kwargs={"group__groupprofile__categories": obj},
+ )
+
+
+class GroupProfileSerializer(DynamicModelSerializer):
+ categories = GroupCategorySerializer(many=True, read_only=True)
+ member_count = serializers.SerializerMethodField()
+ manager_count = serializers.SerializerMethodField()
+ logo_url = serializers.CharField(read_only=True)
+ detail_url = serializers.CharField(source="get_absolute_url", read_only=True)
+ resource_uri = serializers.SerializerMethodField()
+
+ class Meta:
+ model = GroupProfile
+ fields = [
+ "id",
+ "resource_uri",
+ "title",
+ "slug",
+ "description",
+ "email",
+ "access",
+ "created",
+ "last_modified",
+ "categories",
+ "member_count",
+ "manager_count",
+ "logo_url",
+ "detail_url",
+ ]
+
+ def get_resource_uri(self, obj):
+ return reverse("group-profile-detail", args=[obj.pk])
+
+ def get_member_count(self, obj):
+ return obj.member_queryset().count()
+
+ def get_manager_count(self, obj):
+ return obj.get_managers().count()
+
+
+class GroupSerializer(DynamicModelSerializer):
+ group_profile = GroupProfileSerializer(source="groupprofile", read_only=True, allow_null=True)
+ resource_counts = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Group
+ fields = [
+ "id",
+ "name",
+ "group_profile",
+ "resource_counts",
+ ]
+
+ def get_resource_counts(self, obj):
+ request = self.context.get("request")
+ if not request:
+ return {}
+ return _get_resource_counts(
+ request,
+ resourcebase_filter_kwargs={"group": obj, "metadata_only": False},
+ )
diff --git a/geonode/api/tests.py b/geonode/api/tests.py
index 3538dc8b987..63ce90899d2 100644
--- a/geonode/api/tests.py
+++ b/geonode/api/tests.py
@@ -47,7 +47,7 @@
)
from geonode.utils import check_ogc_backend
from geonode.decorators import on_ogc_backend
-from geonode.groups.models import GroupProfile
+from geonode.groups.models import GroupProfile, GroupCategory
from geonode.base.auth import get_or_create_token
from geonode.tests.base import GeoNodeBaseTestSupport
from geonode.base.populate_test_data import all_public, create_models, remove_models
@@ -382,19 +382,17 @@ def test_owners_lockdown(self):
@override_settings(API_LOCKDOWN=True)
def test_groups_lockdown(self):
- groups_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "groups"})
+ groups_list_url = reverse("groups-list")
- filter_url = groups_list_url
-
- resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 0)
+ resp = self.api_client.get(groups_list_url)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.json()["total"], 0)
# now test with logged in user
self.api_client.client.login(username="bobby", password="bob")
- resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 1)
+ resp = self.api_client.get(groups_list_url)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["groups"]), 1)
@override_settings(API_LOCKDOWN=True)
def test_regions_lockdown(self):
@@ -454,7 +452,8 @@ def setUp(self):
self.bar = GroupProfile.objects.get(slug="bar")
self.anonymous_user = get_anonymous_user()
self.profiles_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "profiles"})
- self.groups_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "groups"})
+ self.groups_list_url = reverse("groups-list")
+ self.bar_category, _ = GroupCategory.objects.get_or_create(slug="bar", name="bar")
def test_profiles_filters(self):
"""Test profiles filtering"""
@@ -496,26 +495,67 @@ def test_groups_filters(self):
filter_url = self.groups_list_url
resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 1)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["groups"]), 1)
- filter_url = f"{self.groups_list_url}?name__icontains=bar"
+ resp = self.api_client.get(f"{filter_url}?name__icontains=bar")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["groups"]), 1)
- resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 1)
+ resp = self.api_client.get(f"{filter_url}?name__icontains=BaR")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["groups"]), 1)
- filter_url = f"{self.groups_list_url}?name__icontains=BaR"
+ resp = self.api_client.get(f"{filter_url}?name__icontains=foo")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["groups"]), 0)
- resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 1)
+ def test_group_categories_filters(self):
+ """Test group categories filtering"""
+ with self.settings(API_LOCKDOWN=False):
+ group_categories_list_url = reverse("group-category-list")
+ resp = self.api_client.get(group_categories_list_url)
+ self.assertEqual(resp.status_code, 200)
- filter_url = f"{self.groups_list_url}?name__icontains=foo"
+ resp = self.api_client.get(f"{group_categories_list_url}?name__icontains=bar")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_categories"]), 1)
- resp = self.api_client.get(filter_url)
- self.assertValidJSONResponse(resp)
- self.assertEqual(len(self.deserialize(resp)["objects"]), 0)
+ resp = self.api_client.get(f"{group_categories_list_url}?name__icontains=BaR")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_categories"]), 1)
+
+ resp = self.api_client.get(f"{group_categories_list_url}?name__icontains=nonexistent")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_categories"]), 0)
+
+ resp = self.api_client.get(f"{group_categories_list_url}?slug=bar")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_categories"]), 1)
+
+ def test_group_profiles_filters(self):
+ """Test group profiles filtering"""
+ with self.settings(API_LOCKDOWN=False):
+ group_profiles_list_url = reverse("group-profile-list")
+
+ resp = self.api_client.get(group_profiles_list_url)
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.api_client.get(f"{group_profiles_list_url}?title__icontains=bar")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_profiles"]), 1)
+
+ resp = self.api_client.get(f"{group_profiles_list_url}?title__icontains=BaR")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_profiles"]), 1)
+
+ resp = self.api_client.get(f"{group_profiles_list_url}?title__icontains=nonexistent")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_profiles"]), 0)
+
+ resp = self.api_client.get(f"{group_profiles_list_url}?slug=bar")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.json()["group_profiles"]), 1)
def test_category_filters(self):
"""Test category filtering"""
diff --git a/geonode/api/urls.py b/geonode/api/urls.py
index c63a6c9c72f..d98869bb7bb 100644
--- a/geonode/api/urls.py
+++ b/geonode/api/urls.py
@@ -23,7 +23,12 @@
from . import api as resources
from . import resourcebase_api as resourcebase_resources
-from .views import UserInfoView
+from .views import (
+ UserInfoView,
+ GroupCategoryViewSet,
+ GroupViewSet,
+ GroupProfileViewSet,
+)
api = Api(api_name="api")
@@ -46,6 +51,11 @@
router = routers.DynamicRouter()
+
+router.register(r"groupcategory", GroupCategoryViewSet, base_name="group-category")
+router.register(r"group", GroupViewSet, base_name="groups")
+router.register(r"group_profile", GroupProfileViewSet, base_name="group-profile")
+
urlpatterns = [
path("userinfo/", UserInfoView.as_view(), name="userinfo"),
-]
+] + router.urls
diff --git a/geonode/api/views.py b/geonode/api/views.py
index e29d3605010..69553c75e2a 100644
--- a/geonode/api/views.py
+++ b/geonode/api/views.py
@@ -36,6 +36,21 @@
from rest_framework.views import APIView
from rest_framework.response import Response
+from rest_framework.filters import OrderingFilter
+from rest_framework.viewsets import ReadOnlyModelViewSet
+from dynamic_rest.viewsets import WithDynamicViewSetMixin
+
+from django.conf import settings
+from django.db.models import Q
+from django_filters.rest_framework import DjangoFilterBackend
+from geonode.groups.models import GroupCategory, GroupProfile
+from geonode.base.api.pagination import GeoNodeApiPagination
+from .serializers import (
+ GroupCategorySerializer,
+ GroupProfileSerializer,
+ GroupSerializer,
+)
+from .filters import GroupCategoryFilter, GroupProfileFilter, GroupFilter
def verify_access_token(request, key):
@@ -88,6 +103,71 @@ def get(self, request):
return response
+class GroupCategoryViewSet(WithDynamicViewSetMixin, ReadOnlyModelViewSet):
+ serializer_class = GroupCategorySerializer
+ pagination_class = GeoNodeApiPagination
+ filter_backends = [DjangoFilterBackend, OrderingFilter]
+ filterset_class = GroupCategoryFilter
+ ordering_fields = ["name"]
+ ordering = ["name"]
+
+ def get_queryset(self):
+ user = self.request.user if self.request else None
+ if settings.API_LOCKDOWN and (not user or not user.is_authenticated):
+ return GroupCategory.objects.none()
+ return GroupCategory.objects.all()
+
+
+class GroupProfileViewSet(WithDynamicViewSetMixin, ReadOnlyModelViewSet):
+ serializer_class = GroupProfileSerializer
+ pagination_class = GeoNodeApiPagination
+ filter_backends = [DjangoFilterBackend, OrderingFilter]
+ filterset_class = GroupProfileFilter
+ ordering_fields = ["title", "last_modified"]
+ ordering = ["title"]
+
+ def get_queryset(self):
+ user = self.request.user if self.request else None
+
+ if settings.API_LOCKDOWN and (not user or not user.is_authenticated):
+ return GroupProfile.objects.none()
+
+ qs = GroupProfile.objects.all()
+
+ if not user or not user.is_authenticated:
+ return qs.exclude(access="private")
+
+ if not user.is_superuser:
+ return qs.filter(Q(pk__in=user.group_list_all()) | ~Q(access="private"))
+
+ return qs
+
+
+class GroupViewSet(WithDynamicViewSetMixin, ReadOnlyModelViewSet):
+ serializer_class = GroupSerializer
+ pagination_class = GeoNodeApiPagination
+ filter_backends = [DjangoFilterBackend, OrderingFilter]
+ filterset_class = GroupFilter
+ ordering_fields = ["name", "groupprofile__last_modified"]
+ ordering = ["name"]
+
+ def get_queryset(self):
+ user = self.request.user if self.request else None
+
+ if settings.API_LOCKDOWN and (not user or not user.is_authenticated):
+ return Group.objects.none()
+
+ qs = Group.objects.exclude(groupprofile=None).exclude(name="anonymous")
+
+ if not user or not user.is_authenticated:
+ return qs.exclude(groupprofile__access="private")
+
+ if not user.is_superuser:
+ return qs.filter(Q(groupprofile__in=user.group_list_all()) | ~Q(groupprofile__access="private"))
+
+ return qs
+
+
@csrf_exempt
def verify_token(request):
if request.POST and "token" in request.POST: