Skip to content

Commit

Permalink
[Closes #4532] Add search filters for groups and group categories (#4533
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Alessio Fabiani committed Jun 18, 2019
1 parent fab2985 commit ceb422e
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 105 deletions.
169 changes: 122 additions & 47 deletions geonode/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from django.db.models import Q
from django.conf.urls import url
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
Expand Down Expand Up @@ -283,12 +284,12 @@ class Meta:
class GroupCategoryResource(TypeFilteredResource):
detail_url = fields.CharField()
member_count = fields.IntegerField()
resource_counts = fields.CharField()

class Meta:
queryset = GroupCategory.objects.all()
allowed_methods = ['get']
include_resource_uri = False
fields = ['name', 'slug']
filtering = {'slug': ALL,
'name': ALL}

Expand All @@ -298,69 +299,88 @@ def dehydrate_detail_url(self, bundle):
def dehydrate_member_count(self, bundle):
return bundle.obj.groups.all().count()


class GroupResource(TypeFilteredResource):
"""Groups api"""
def dehydrate(self, bundle):
"""Provide additional resource counts"""

counts = _get_resource_counts(
resourcebase_filter_kwargs={
'group__groupprofile__categories': bundle.obj
}
)
bundle.data.update(resource_counts=counts)
return bundle


class GroupProfileResource(ModelResource):
categories = fields.ToManyField(
GroupCategoryResource,
'categories',
full=True
)
member_count = fields.CharField()
manager_count = fields.CharField()
detail_url = fields.CharField()
member_count = fields.IntegerField()
manager_count = fields.IntegerField()
categories = fields.ToManyField(GroupCategoryResource, 'categories', full=True)

def build_filters(self, filters=None, ignore_bad_filters=False):
"""adds filtering by group functionality"""
if filters is None:
filters = {}

orm_filters = super(GroupResource, self).build_filters(filters)

if 'group' in filters:
orm_filters['group'] = filters['group']

if 'name__icontains' in filters:
orm_filters['title__icontains'] = filters['name__icontains']
orm_filters['title_en__icontains'] = filters['name__icontains']
return orm_filters

def apply_filters(self, request, applicable_filters):
"""filter by group if applicable by group functionality"""

group = applicable_filters.pop('group', None)
name = applicable_filters.pop('name__icontains', None)

semi_filtered = super(
GroupResource,
self).apply_filters(
request,
applicable_filters)

if group is not None:
semi_filtered = semi_filtered.filter(
groupmember__group__slug=group)

if name is not None:
semi_filtered = semi_filtered.filter(
Q(title__icontains=name) | Q(title_en__icontains=name))

return semi_filtered
class Meta:
queryset = GroupProfile.objects.all()
resource_name = 'group_profile'
allowed_methods = ['get']
filtering = {
'title': ALL,
'slug': ALL,
'categories': ALL_WITH_RELATIONS,
}
ordering = ['title', 'last_modified']

def dehydrate_member_count(self, bundle):
"""Provide relative URL to the geonode UI's page on the group"""
return bundle.obj.member_queryset().count()

def dehydrate_manager_count(self, bundle):
"""Provide relative URL to the geonode UI's page on the group"""
return bundle.obj.get_managers().count()

def dehydrate_detail_url(self, bundle):
"""Return relative URL to the geonode UI's page on the group"""
return reverse('group_detail', args=[bundle.obj.slug])


class GroupResource(ModelResource):
group_profile = fields.ToOneField(
GroupProfileResource,
'groupprofile',
full=True,
null=True,
blank=True
)
resource_counts = fields.CharField()

class Meta:
queryset = GroupProfile.objects.all()
queryset = Group.objects.all()
resource_name = 'groups'
allowed_methods = ['get']
filtering = {
'title': ALL,
'categories': ALL_WITH_RELATIONS,
'name': ALL,
'group_profile': ALL_WITH_RELATIONS,
}
ordering = ['title', 'last_modified']
ordering = ['name', 'last_modified']

def dehydrate(self, bundle):
"""Provide additional resource counts"""

counts = _get_resource_counts(
resourcebase_filter_kwargs={'group': bundle.obj})
bundle.data.update(resource_counts=counts)
return bundle

def get_object_list(self, request):
"""
Overridden in order to exclude the ``anoymous`` group from the list
"""

qs = super(GroupResource, self).get_object_list(request)
return qs.exclude(name="anonymous")


class ProfileResource(TypeFilteredResource):
Expand Down Expand Up @@ -760,3 +780,58 @@ class StyleResource(QGISStyleResource):
class StyleResource(GeoserverStyleResource):
"""Wrapper for Generic Style Resource"""
pass


def _get_resource_counts(
resourcebase_filter_kwargs
):
"""Return a dict with counts of resources of various types
The ``resourcebase_filter_kwargs`` argument should be a dict with a suitable
queryset filter that can be applied to select only the relevant
``ResourceBase`` objects to use when retrieving counts. For example::
_get_resource_counts({
'group__slug': 'my-group',
})
The above function call would result in only counting ``ResourceBase``
objects that belong to the group that has ``my-group`` as slug
"""

qs = ResourceBase.objects.filter(
**resourcebase_filter_kwargs
).values(
'polymorphic_ctype__model',
'is_approved',
'is_published',
).annotate(counts=Count('polymorphic_ctype__model'))
types = [
'layer',
'document',
'map',
'all'
]
counts = {}
for type_ in types:
counts[type_] = {
'total': 0,
'visible': 0,
'published': 0,
'approved': 0,
}
for record in qs:
resource_type = record['polymorphic_ctype__model']
is_visible = all((record['is_approved'], record['is_published']))
counts['all']['total'] += 1
counts['all']['visible'] += 1 if is_visible else 0
counts['all']['published'] += 1 if record['is_published'] else 0
counts['all']['approved'] += 1 if record['is_approved'] else 0
section = counts.get(resource_type)
if section is not None:
section['total'] += 1
section['visible'] += 1 if is_visible else 0
section['published'] += 1 if record['is_published'] else 0
section['approved'] += 1 if record['is_approved'] else 0
return counts
1 change: 1 addition & 0 deletions geonode/api/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@


class OAuthAuthentication(Authentication):

def extract_auth_header(self, request):
auth_header = None
try:
Expand Down
36 changes: 16 additions & 20 deletions geonode/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,22 @@

from tastypie.api import Api

from geonode.api.api import StyleResource
from .api import TagResource, TopicCategoryResource, ProfileResource, \
GroupResource, RegionResource, OwnersResource, ThesaurusKeywordResource, \
GroupCategoryResource
from .resourcebase_api import LayerResource, MapResource, DocumentResource, \
ResourceBaseResource, FeaturedResourceBaseResource
from . import api as resources
from . import resourcebase_api as resourcebase_resources

api = Api(api_name='api')

api.register(LayerResource())
api.register(MapResource())
api.register(DocumentResource())
api.register(ProfileResource())
api.register(ResourceBaseResource())
api.register(TagResource())
api.register(RegionResource())
api.register(TopicCategoryResource())
api.register(GroupResource())
api.register(FeaturedResourceBaseResource())
api.register(OwnersResource())
api.register(ThesaurusKeywordResource())
api.register(GroupCategoryResource())
api.register(StyleResource())
api.register(resources.GroupCategoryResource())
api.register(resources.GroupResource())
api.register(resources.OwnersResource())
api.register(resources.ProfileResource())
api.register(resources.RegionResource())
api.register(resources.StyleResource())
api.register(resources.TagResource())
api.register(resources.ThesaurusKeywordResource())
api.register(resources.TopicCategoryResource())
api.register(resourcebase_resources.DocumentResource())
api.register(resourcebase_resources.FeaturedResourceBaseResource())
api.register(resourcebase_resources.LayerResource())
api.register(resourcebase_resources.MapResource())
api.register(resourcebase_resources.ResourceBaseResource())
20 changes: 10 additions & 10 deletions geonode/groups/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,41 +696,38 @@ class GroupProfileTest(GeoNodeBaseTestSupport):
@override_settings(MEDIA_ROOT="/tmp/geonode_tests")
def test_group_logo_is_present_on_list_view(self):
"""Verify that a group's logo is rendered on list view."""
test_group = Group(name="tester")
test_profile = GroupProfile(
group=test_group,
title="test",
slug="test",
description="test",
access="public",
logo=SimpleUploadedFile("dummy-file.jpg", b"dummy contents")
)
test_group.save()
test_profile.save()
response = self.client.get(
reverse("api_dispatch_list",
kwargs={"api_name": "api", "resource_name": "groups"})
)
response_payload = json.loads(response.content)
returned = response_payload["objects"]
group = [g for g in returned if g["title"] == test_profile.title][0]
group_profile = [
g["group_profile"] for g in returned if
g["group_profile"]["title"] == test_profile.title
][0]
self.assertEqual(200, response.status_code)
self.assertEqual(group["logo"], test_profile.logo.url)
self.assertEqual(group_profile["logo"], test_profile.logo.url)

def test_group_logo_is_not_present_on_list_view(self):
"""
Verify that no logo exists in list view when a group doesn't have one.
"""

test_group = Group(name="tester")
test_profile = GroupProfile(
group=test_group,
title="test",
slug="test",
description="test",
access="public"
)
test_group.save()
test_profile.save()

response = self.client.get(
Expand All @@ -739,6 +736,9 @@ def test_group_logo_is_not_present_on_list_view(self):
)
response_payload = json.loads(response.content)
returned = response_payload["objects"]
group = [g for g in returned if g["title"] == test_profile.title][0]
group_profile = [
g["group_profile"] for g in returned if
g["group_profile"]["title"] == test_profile.title
][0]
self.assertEqual(200, response.status_code)
self.assertIsNone(group["logo"])
self.assertIsNone(group_profile["logo"])
8 changes: 5 additions & 3 deletions geonode/layers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,9 +1010,11 @@ def create_gs_thumbnail_geonode(instance, overwrite=False, check_bbox=False):
else:
layers = instance.alternate.encode('utf-8')
# Compute Bounds
_l = Layer.objects.get(alternate=layers)
wgs84_bbox = bbox_to_projection(_l.bbox)
local_bboxes.append(wgs84_bbox)
_ll = Layer.objects.filter(alternate=layers)
for _l in _ll:
if _l.name == instance.name:
wgs84_bbox = bbox_to_projection(_l.bbox)
local_bboxes.append(wgs84_bbox)

if local_bboxes:
for _bbox in local_bboxes:
Expand Down
2 changes: 2 additions & 0 deletions geonode/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,8 @@ def get_geonode_catalogue_service():
'DATE_ENABLED': True,
'REGION_ENABLED': True,
'EXTENT_ENABLED': True,
'GROUPS_ENABLED': True,
'GROUP_CATEGORIES_ENABLED': True,
}

# Make Free-Text Kaywords writable from users or read-only
Expand Down

0 comments on commit ceb422e

Please sign in to comment.