From c4ede744e5f93a3d1a58cf6402ce6e62f4dc60cd Mon Sep 17 00:00:00 2001 From: Tobias Megies Date: Mon, 2 May 2016 18:01:51 +0200 Subject: [PATCH 1/2] introduce an option to negate the filtering on a queryset i.e. make the filter not return events that match all given criteria, but rather return events, that do not match the combination of given criteria. otherwise it is impossible to define a retrieve permission for a combination of two criteria (i.e. when user "does not have permission" to exclude events that match the given combination of criteria, e.g. exclude events at site X and below magnitude Y) --- src/jane/documents/models.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/jane/documents/models.py b/src/jane/documents/models.py index 0cc1635..63d649e 100644 --- a/src/jane/documents/models.py +++ b/src/jane/documents/models.py @@ -320,7 +320,7 @@ def get_filtered_queryset_radial_distance( Distance(km=deg2km(max_radius)))) return queryset - def get_filtered_queryset(self, document_type, queryset=None, + def get_filtered_queryset(self, document_type, queryset=None, negate=False, **kwargs): """ Returns a queryset filtered on the items in the JSON document. @@ -329,6 +329,12 @@ def get_filtered_queryset(self, document_type, queryset=None, queryset is passed. :param queryset: If no queryset is passed, a new one will be created, otherwise an existing one will be used and filtered. + :type negate: bool + :param negate: When ``negate=True`` then the resulting queryset is + inverted, i.e. the resulting queryset returns all events that do + not match all given criteria. For example, ``.., negate=True, + max_magnitude=1, site="unterhaching")`` returns all events except + events at site Unterhaching that are below magnitude 1. :param kwargs: Any additional query parameters. Assuming a key named ``"example"`` in the JSON file you can search @@ -362,6 +368,12 @@ def get_filtered_queryset(self, document_type, queryset=None, ``public=True`` ``kwargs={"!public": True}`` + The resulting query set can be inverted with ``negate=True``:: + + * For example, using ``.., negate=True, max_magnitude=1, + site="unterhaching")`` returns all events except for events that are + both declared at site Unterhaching and below magnitude 1. + Please note that as soon as you search for a value, all values that are null will be discarded from the queryset (even if you search for !=)! This might be changed in the future. @@ -448,6 +460,11 @@ def get_filtered_queryset(self, document_type, queryset=None, else: raise NotImplementedError() + # if "negate" option is selected: instead of filtering to given + # criteria, filter out all items that match given criteria + if where != [] and negate: + where = ["NOT ({})".format(" AND ".join(where))] + queryset = queryset.extra(where=where) if "ordering" in kwargs and kwargs["ordering"] in meta: From 614eacbd915cdfdc815b9965256e0bfff188d768 Mon Sep 17 00:00:00 2001 From: Tobias Megies Date: Sat, 30 Apr 2016 13:09:21 +0200 Subject: [PATCH 2/2] add handling "site" tag and additional retrieve permissions note that this needs krischer/django-plugins#11 to work --- src/jane/quakeml/plugins.py | 96 ++++++++++++++++++- src/jane/static/web_gis/src/directives/map.js | 8 +- .../web_gis/templates/event_modal.tpl.html | 2 + 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/jane/quakeml/plugins.py b/src/jane/quakeml/plugins.py index e4e8ae9..93599b7 100644 --- a/src/jane/quakeml/plugins.py +++ b/src/jane/quakeml/plugins.py @@ -83,6 +83,94 @@ def filter_queryset_user_does_not_have_permission(self, queryset, return queryset +def _site_magnitude_threshold_retrieve_permission( + class_name, magnitude_threshold, site=None): + """ + Class factory that returns a quakeml retrieve permission based on a + magnitude threshold, optionally only working on a specific site. + If multiple of these restrictions are defined, all of them apply separately + and the user must have all of them set, down to the lowest threshold + restriction that is supposed to apply. + """ + class _SiteMagnitudeThresholdRetrievePermissionPlugin( + RetrievePermissionPluginPoint): + """ + If user does not have this permission, any events below given magnitude + threshold are filtered out (optionally only for a specific site). + """ + name = 'quakeml' + title = 'Can See Magnitude <{} Events {}Permission'.format( + magnitude_threshold, site and "At site='{}' ".format(site) or "") + + # Permission codename and name according to Django's nomenclature. + # XXX no idea if dots are allowed in codename, so replace them + permission_codename = 'can_see_mag_lessthan_{}_site_{}_events'.format( + magnitude_threshold, site or "any").replace(".", "_") + permission_name = 'Can See Magnitude <{} Events{}'.format( + magnitude_threshold, site and " At site='{}'".format(site) or "") + + def filter_queryset_user_has_permission(self, queryset, model_type): + # If the user has the permission: don't restrict queryset. + return queryset + + def filter_queryset_user_does_not_have_permission(self, queryset, + model_type): + # model_type can be document or document index. + if model_type == "document": + # XXX: Find a good way to do this. + raise NotImplementedError() + elif model_type == "index": + # Modify the queryset to only contain indices that are above + # given magnitude threshold. + # XXX check what happens with events that have null for + # XXX magnitude.. + kwargs = {} + # if no site is specified, just do a normal filter by magnitude + # threshold + if site is None: + kwargs["min_magnitude"] = magnitude_threshold + negate = False + # if site is specified, we need to search for events matching + # both criteria and then invert the resulting queryset + else: + kwargs['site'] = site + kwargs["max_magnitude"] = magnitude_threshold - 0.01 + negate = True + queryset = queryset.model.objects.get_filtered_queryset( + document_type="quakeml", queryset=queryset, negate=negate, + **kwargs) + else: + raise NotImplementedError() + return queryset + + new_class = _SiteMagnitudeThresholdRetrievePermissionPlugin + # Set the class type name. + setattr(new_class, "__name__", class_name) + return new_class + + +# Retrieve permissions for small events, if users don't have these permissions +# small events are not accessible to them +MagnitudeLessThan1RetrievePermissionPlugin = \ + _site_magnitude_threshold_retrieve_permission( + "MagnitudeLessThan1RetrievePermissionPlugin", magnitude_threshold=1.0) +MagnitudeLessThan2RetrievePermissionPlugin = \ + _site_magnitude_threshold_retrieve_permission( + "MagnitudeLessThan2RetrievePermissionPlugin", magnitude_threshold=2.0) + +# Retrieve permissions for small events attributed to a specific site (e.g. a +# specific deep geothermal project), if users don't have these permissions +# small events that are attributed to that site are not accessible to them +UnterhachingLessThan1RetrievePermissionPlugin = \ + _site_magnitude_threshold_retrieve_permission( + "UnterhachingLessThan1RetrievePermissionPlugin", + magnitude_threshold=1.0, site="geothermie_unterhaching") +UnterhachingLessThan2RetrievePermissionPlugin = \ + _site_magnitude_threshold_retrieve_permission( + "UnterhachingLessThan2RetrievePermissionPlugin", + magnitude_threshold=2.0, site="geothermie_unterhaching") + + class QuakeMLIndexerPlugin(IndexerPluginPoint): """ Each document type can have one indexer. @@ -114,7 +202,8 @@ class QuakeMLIndexerPlugin(IndexerPluginPoint): "author": "str", "public": "bool", "evaluation_mode": "str", - "event_type": "str" + "event_type": "str", + "site": "str", } def index(self, document): @@ -160,6 +249,10 @@ def index(self, document): evaluation_mode = extra["evaluationMode"]["value"] else: evaluation_mode = None + if "site" in extra: + site = extra["site"]["value"] + else: + site = None indices.append({ "quakeml_id": str(event.resource_id), @@ -181,6 +274,7 @@ def index(self, document): # fast queries using PostGIS. "geometry": [Point(org.longitude, org.latitude)] if org else None, + "site": site, }) return indices diff --git a/src/jane/static/web_gis/src/directives/map.js b/src/jane/static/web_gis/src/directives/map.js index 5079e4e..71ce4e7 100644 --- a/src/jane/static/web_gis/src/directives/map.js +++ b/src/jane/static/web_gis/src/directives/map.js @@ -482,9 +482,14 @@ app.directive('openlayers3', function($q, $log, bing_key, $modal) { public = "not specified" } + var site = feature.get("site"); + if (site == null) { + site = "not specified" + } + tooltip_title += "\nAgency: " + feature.get("agency") + " | Author: " + author + " | Evaluation mode: " + evaluation_mode + - " | Public: " + public; + " | Public: " + public + " | Site: " + site; if (feature.get('magnitude')) { @@ -561,6 +566,7 @@ app.directive('openlayers3', function($q, $log, bing_key, $modal) { modal.$scope.magnitude_type = feature.get("magnitude_type"); modal.$scope.origin_time = feature.get("origin_time"); modal.$scope.public = feature.get("public"); + modal.$scope.site = feature.get("site"); modal.$scope.quakeml_id = feature.get("quakeml_id"); diff --git a/src/jane/static/web_gis/templates/event_modal.tpl.html b/src/jane/static/web_gis/templates/event_modal.tpl.html index 0a16037..9641adf 100644 --- a/src/jane/static/web_gis/templates/event_modal.tpl.html +++ b/src/jane/static/web_gis/templates/event_modal.tpl.html @@ -29,6 +29,8 @@
{{ evaluation_mode }}
Public
{{ public }}
+
Site
+
{{ site }}