diff --git a/doc/dev/database.rst b/doc/dev/database.rst index 4544c397..d8ab4b07 100644 --- a/doc/dev/database.rst +++ b/doc/dev/database.rst @@ -269,7 +269,7 @@ Station and its related models .. attribute:: enhydris.models.Station.overseer - The overseers is the person responsible for the meteorological + The overseer is the person responsible for the meteorological station in the past. In the case of manual (not automatic) stations, this means the weather observers. This is a simple text field. @@ -394,6 +394,11 @@ Time series and related models it contains an appropriate time step as a `pandas "frequency" string`_, e.g. "10min", "H", "M", "Y". + .. attribute:: enhydris.models.Timeseries.publicly_available + + Specifies whether anonymous users can download the time series + data. + .. _pandas "frequency" string: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects .. method:: enhydris.models.Timeseries.get_data(start_date=None, end_date=None, timezone=None) diff --git a/doc/general/install.rst b/doc/general/install.rst index 62d87034..84ef0ad6 100644 --- a/doc/general/install.rst +++ b/doc/general/install.rst @@ -1,4 +1,4 @@ -.. _install: +. _install: ============================== Installation and configuration @@ -250,16 +250,41 @@ These are the settings available to Enhydris, in addition to the set to ``False`` (the default), only privileged users are allowed to add/edit/remove data from the db. - See also :data:`ENHYDRIS_OPEN_CONTENT`. - -.. data:: ENHYDRIS_OPEN_CONTENT - - If set to ``True``, users who haven't logged on can view timeseries - data and station file (e.g. image) content. Otherwise, only logged on - users can do so. Logged on users can always view everything. - - When this setting is ``False``, ``REGISTRATION_OPEN`` must obviously - also be set to ``False``. + See also :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and + :data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`. + +.. data:: ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE + + Time series have a + :attr:`enhydris.models.Timeseries.publicly_available` attribute which + specifies whether anonymous users can download the time series data. + If the attribute is ``False``, only logged on users have this + permission (and, again, this depends on + :data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`). The setting + specifies the default value for the attribute, that is, whether by + default the related checkbox in the form is checked or not. The + default for the setting is ``True``, but it is recommended to + explicitly set it. + +.. data:: ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS + + If this is ``False`` (the default), all logged on users have + permission to download the time series data for all time series (for + anonymous user there's a + :attr:`enhydris.models.Timeseries.publicly_available` attribute for + each individual time series; see also + :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE). Note that if you want + all logged on users to have such permission, but the general public + not to, you must also make sure that :data:`REGISTRATION_OPEN` is + ``False``. + + If :data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS` is ``True``, + logged on users can only view time series data for which they have + specifically been given permission. By default, only the station + owner and maintainers have such access, but they can specify which + other users also have access. Permission to view time series data + applies to all time series of a station. Individual time series can + again be marked as publicly available. .. data:: ENHYDRIS_MAP_BASE_LAYERS diff --git a/doc/general/release-notes.rst b/doc/general/release-notes.rst index bf5cb6fb..69868e57 100644 --- a/doc/general/release-notes.rst +++ b/doc/general/release-notes.rst @@ -19,20 +19,27 @@ Upgrading from 3.0 4. Login as a superuser and go to the dashboard. 5. Go to "Sites" and make sure they're set correctly. 6. In the settings, make sure ``SITE_ID`` is set correctly. - 7. Update the database with ``python manage.py migrate``. This will put + 7. In the settings, remove setting `ENHYDRIS_OPEN_CONTENT` and instead + set :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and + :data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`. Note that, during + migration, the new :attr:`publicly_available` attribute will be set + on existing time series to the value of + :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE`. + 8. Update the database with ``python manage.py migrate``. This will put all stations to the site specified with ``SITE_ID``, and will add all users to the group whose name is the domain of the current site (the group will be created automatically if it does not exist). - 8. If you have been using a single database to power many sites, then: + 9. If you have been using a single database to power many sites, then: * In the settings, make sure :data:`ENHYDRIS_SITES_FOR_NEW_STATIONS` is set correctly. Restart the server if necessary. - * Logon as a superuseri and go to the dashboard + * Logon as a superuser and go to the dashboard * Go to each of the stations that used to be specified by ``ENHYDRIS_SITE_STATION_FILTER`` and make sure the "Sites" field is set correctly. * If any users need to be able to log on to a different site from the one where you performed the database update, go to each of these users and put them in the appropriate groups. +10. Restart the server. Changes from 3.0 ---------------- @@ -94,6 +101,18 @@ If you discover errors after upgrading, fixing them is similar: 1. Download the time series that have the problem. 2. Upload the time series, specifying an appropriate time zone. +Permissions +^^^^^^^^^^^ + +Several things have been changed in the permissions model. Setting +``ENHYDRIS_OPEN_CONTENT`` has been abolished and replaced with the new +settings :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and +:data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`. These two settings +together can do much more than what ``ENHYDRIS_OPEN_CONTENT`` did. +Another important difference is that these new settings apply only +to time series data, not to gentity files, so gentity files are now +always publicly available. + Version 3.0 =========== diff --git a/enhydris/admin/station.py b/enhydris/admin/station.py index 4d9639b5..82748ebe 100644 --- a/enhydris/admin/station.py +++ b/enhydris/admin/station.py @@ -268,6 +268,7 @@ class TimeseriesInline(InlinePermissionsMixin, nested_admin.NestedStackedInline) extra = 1 fields = ( ("type", "time_step"), + "publicly_available", ("data", "default_timezone", "replace_or_append"), ) @@ -373,6 +374,8 @@ def _get_permissions_fields(self, request, obj): permissions_fields.append("creator") if request.user.has_perm("enhydris.change_station_maintainers", obj): permissions_fields.append("maintainers") + if settings.ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS: + permissions_fields.append("timeseries_data_viewers") if permissions_fields: self._fieldsets.append( ( diff --git a/enhydris/api/permissions.py b/enhydris/api/permissions.py index 49ee8215..3d585aeb 100644 --- a/enhydris/api/permissions.py +++ b/enhydris/api/permissions.py @@ -26,13 +26,3 @@ def has_object_permission(self, request, view, obj): return request.user.has_perm( "enhydris.change_station", obj.timeseries_group.gentity.gpoint.station ) - - -class CanAccessGentityFileContent(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: - return request.user.has_perm("enhydris.view_gentityfile_content", obj) - else: - return request.user.has_perm( - "enhydris.change_station", obj.gentity.gpoint.station - ) diff --git a/enhydris/api/tests/test_views/test_gentity_file.py b/enhydris/api/tests/test_views/test_gentity_file.py index f8aac131..7e3c0571 100644 --- a/enhydris/api/tests/test_views/test_gentity_file.py +++ b/enhydris/api/tests/test_views/test_gentity_file.py @@ -1,8 +1,6 @@ from unittest.mock import MagicMock, mock_open, patch -from django.contrib.auth.models import User from django.db.models.fields.files import FieldFile -from django.test.utils import override_settings from rest_framework.test import APITestCase from model_mommy import mommy @@ -50,7 +48,6 @@ def test_detail_returns_nothing_if_wrong_station(self): self.assertEqual(r.status_code, 404) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class GentityFileContentTestCase(APITestCase): def setUp(self): # Mocking. We mock several things in Django and Python so that: @@ -85,35 +82,6 @@ def test_content_type(self): self.assertEqual(self.response["Content-Type"], "image/jpeg") -@override_settings(ENHYDRIS_OPEN_CONTENT=False) -@patch("enhydris.api.views.open", mock_open(read_data="ABCDEF")) -@patch("os.path.getsize", return_value=6) -class GentityFileContentPermissionsTestCase(APITestCase): - def setUp(self): - self.station = mommy.make(models.Station) - self.gentity_file = mommy.make(models.GentityFile, gentity=self.station) - self.url = "/api/stations/{}/files/{}/content/".format( - self.station.id, self.gentity_file.id - ) - self.saved_fieldfile_file = FieldFile.file - self.filemock = MagicMock(**{"return_value.name": "some_image.jpg"}) - FieldFile.file = property(self.filemock, MagicMock(), MagicMock()) - - def tearDown(self): - FieldFile.file = self.saved_fieldfile_file - - def test_anonymous_user_is_denied(self, m): - self.response = self.client.get(self.url) - self.assertEqual(self.response.status_code, 401) - - def test_logged_on_user_is_ok(self, m): - self.user1 = mommy.make(User, is_active=True, is_superuser=False) - self.client.force_authenticate(user=self.user1) - self.response = self.client.get(self.url) - self.assertEqual(self.response.status_code, 200) - - -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class GentityFileContentWithoutFileTestCase(APITestCase): def setUp(self): # Mommy creates a GentityFile without an associated file, so the diff --git a/enhydris/api/tests/test_views/test_timeseries.py b/enhydris/api/tests/test_views/test_timeseries.py index f8b694e7..b0a70256 100644 --- a/enhydris/api/tests/test_views/test_timeseries.py +++ b/enhydris/api/tests/test_views/test_timeseries.py @@ -35,26 +35,37 @@ def test_post_nonexistent_timeseries(self): self.assertEqual(response.status_code, 404) -@override_settings(ENHYDRIS_OPEN_CONTENT=False) @patch("enhydris.models.Timeseries.get_data", return_value=HTimeseries()) +@override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=False) class TsdataGetPermissionsTestCase(APITestCase): - def setUp(self): + @classmethod + def setUpTestData(cls): station = mommy.make(models.Station, display_timezone="Etc/GMT-2") timeseries_group = mommy.make( models.TimeseriesGroup, gentity=station, precision=2, ) - timeseries = mommy.make(models.Timeseries, timeseries_group=timeseries_group) - self.url = ( + cls.timeseries = mommy.make( + models.Timeseries, + timeseries_group=timeseries_group, + publicly_available=False, + ) + cls.url = ( f"/api/stations/{station.id}/timeseriesgroups/{timeseries_group.id}/" - f"timeseries/{timeseries.id}/data/" + f"timeseries/{cls.timeseries.id}/data/" ) - def test_anonymous_user_is_denied(self, m): + def test_anonymous_user_is_denied_by_default(self, m): self.response = self.client.get(self.url) self.assertEqual(self.response.status_code, 401) + def test_anonymous_user_is_accepted_if_publicly_available_is_true(self, m): + self.timeseries.publicly_available = True + self.timeseries.save() + self.response = self.client.get(self.url) + self.assertEqual(self.response.status_code, 200) + def test_logged_on_user_is_ok(self, m): self.user1 = mommy.make(User, is_active=True, is_superuser=False) self.client.force_authenticate(user=self.user1) @@ -62,11 +73,10 @@ def test_logged_on_user_is_ok(self, m): self.assertEqual(self.response.status_code, 200) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class GetDataTestCase(APITestCase, TimeseriesDataMixin): @classmethod def setUpTestData(cls): - cls.create_timeseries() + cls.create_timeseries(publicly_available=True) def _get_response(self, urlsuffix=""): return self.client.get( @@ -98,11 +108,10 @@ def test_response_content_in_other_timezone(self): ) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class GetDataInVariousFormatsTestCase(APITestCase, TimeseriesDataMixin): def setUp(self): super().setUp() - self.create_timeseries() + self.create_timeseries(publicly_available=True) self.get_data_patch = patch( "enhydris.models.Timeseries.get_data", return_value=self.htimeseries ) @@ -306,7 +315,6 @@ def test_status_code(self): ) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class TsdataStartAndEndDateTestCase(APITestCase): def setUp(self): self.station = mommy.make(models.Station) @@ -314,7 +322,9 @@ def setUp(self): models.TimeseriesGroup, gentity=self.station, precision=2 ) self.timeseries = mommy.make( - models.Timeseries, timeseries_group=self.timeseries_group + models.Timeseries, + timeseries_group=self.timeseries_group, + publicly_available=True, ) def _make_request(self, query_string): @@ -361,7 +371,6 @@ def test_called_get_data_with_very_late_start_date(self, m): ) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class TsdataInvalidStartOrEndDateTestCase(APITestCase): def setUp(self): self.station = mommy.make(models.Station) @@ -369,7 +378,9 @@ def setUp(self): models.TimeseriesGroup, gentity=self.station, precision=2 ) self.timeseries = mommy.make( - models.Timeseries, timeseries_group=self.timeseries_group + models.Timeseries, + timeseries_group=self.timeseries_group, + publicly_available=True, ) def _make_request(self, query_string): @@ -390,7 +401,6 @@ def test_invalid_end_date(self, m): m.assert_called_once_with(start_date=None, end_date=None, timezone=None) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class TsdataHeadTestCase(APITestCase): def setUp(self): self.station = mommy.make(models.Station) @@ -401,7 +411,9 @@ def setUp(self): precision=2, ) self.timeseries = mommy.make( - models.Timeseries, timeseries_group=self.timeseries_group + models.Timeseries, + timeseries_group=self.timeseries_group, + publicly_available=True, ) self.timeseries.set_data( StringIO("2018-12-09 13:10,20,\n"), default_timezone="Etc/GMT" @@ -422,7 +434,6 @@ def test_head(self): self.assertNotContains(response, "2018-12-09 13:10,") -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class TimeseriesBottomTestCase(APITestCase): def setUp(self): self.station = mommy.make(models.Station, display_timezone="Etc/GMT-2") @@ -432,7 +443,9 @@ def setUp(self): precision=2, ) self.timeseries = mommy.make( - models.Timeseries, timeseries_group=self.timeseries_group + models.Timeseries, + timeseries_group=self.timeseries_group, + publicly_available=True, ) self.timeseries.set_data( StringIO("2018-12-09 13:10,20,\n"), default_timezone="Etc/GMT-2" @@ -465,7 +478,7 @@ def test_response_content_with_timezone(self): self.assertEqual(self.response.content.decode(), "2018-12-09 16:10,20.00,") -@override_settings(ENHYDRIS_OPEN_CONTENT=False) +@override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=False) class TimeseriesBottomPermissionsTestCase(APITestCase): def setUp(self): station = mommy.make(models.Station) @@ -474,7 +487,11 @@ def setUp(self): gentity=station, precision=2, ) - timeseries = mommy.make(models.Timeseries, timeseries_group=timeseries_group) + timeseries = mommy.make( + models.Timeseries, + timeseries_group=timeseries_group, + publicly_available=False, + ) timeseries.set_data( StringIO("2018-12-09 13:10,20,\n"), default_timezone="Etc/GMT-2" ) @@ -667,14 +684,13 @@ def test_authorized_user_can_delete_timeseries(self): self.assertEqual(response.status_code, 204) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) @patch("enhydris.api.views.TimeseriesViewSet._get_stats_for_all_intervals") @patch("enhydris.models.Timeseries.get_data") class TimeseriesChartDateBoundsTestCase(APITestCase, TimeseriesDataMixin): def setUp(self): # "_get_sampled_data_to_plot" is mocked to avoid executing the actual logic of # comparisons in the view, which would compare mocks, raising an exception - self.create_timeseries() + self.create_timeseries(publicly_available=True) self.url = ( f"/api/stations/{self.station.id}/timeseriesgroups" f"/{self.timeseries_group.id}/timeseries/{self.timeseries.id}/chart/" @@ -740,42 +756,40 @@ def _value(self, yyyymmddhhmm, min, max, mean): } -@override_settings(ENHYDRIS_OPEN_CONTENT=True) @patch("enhydris.api.views.TimeseriesViewSet.CHART_MAX_INTERVALS", new=20) @patch("enhydris.models.Timeseries.get_data") +@override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=False) class TimeseriesChartTestCase( APITestCase, TimeseriesDataMixin, TimeseriesChartTestMixin ): - def create_timeseries(self): + def _create_timeseries(self, publicly_available=None): # Create the timeseries so that we have 5 entries, one per year - super().create_timeseries() + super().create_timeseries(publicly_available=publicly_available) self.htimeseries.data = pd.DataFrame( index=[dt.datetime(year, 1, 1) for year in range(2010, 2015)], data={"value": [year for year in range(2010, 2015)], "flags": [""] * 5}, columns=["value", "flags"], ) - - def setUp(self): - self.create_timeseries() self.url = ( f"/api/stations/{self.station.id}/timeseriesgroups" f"/{self.timeseries_group.id}/timeseries/{self.timeseries.id}/chart/" ) - @override_settings(ENHYDRIS_OPEN_CONTENT=False) def test_unauthenticated_user_denied(self, mock): + self._create_timeseries(publicly_available=False) mock.return_value = self.htimeseries response = self.client.get(self.url) self.assertEqual(response.status_code, 401) - @override_settings(ENHYDRIS_OPEN_CONTENT=False) def test_authenticated_user_allowed(self, mock): + self._create_timeseries(publicly_available=False) self.client.force_authenticate(user=mommy.make(User, is_active=True)) mock.return_value = self.htimeseries response = self.client.get(self.url) self.assertEqual(response.status_code, 200) def test_all_values_returned(self, mock): + self._create_timeseries(publicly_available=True) mock.return_value = self.htimeseries response = self.client.get(self.url) expected = [ @@ -788,6 +802,7 @@ def test_all_values_returned(self, mock): self._assertChartResponse(response, expected) def test_null_values_are_dropped(self, mock): + self._create_timeseries(publicly_available=True) self.htimeseries.data.loc["2010-01-01", "value"] = np.nan mock.return_value = self.htimeseries response = self.client.get(self.url) @@ -800,14 +815,13 @@ def test_null_values_are_dropped(self, mock): self._assertChartResponse(response, expected) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) @patch("enhydris.api.views.TimeseriesViewSet.CHART_MAX_INTERVALS", new=3) @patch("enhydris.models.Timeseries.get_data") class TimeseriesChartValuesTestCase( APITestCase, TimeseriesDataMixin, TimeseriesChartTestMixin ): def setUp(self): - self.create_timeseries() + self.create_timeseries(publicly_available=True) self.htimeseries.data = pd.DataFrame( index=[dt.datetime(year, 1, 1) for year in range(2010, 2021)], data={"value": [year for year in range(2010, 2021)], "flags": [""] * 11}, diff --git a/enhydris/api/views.py b/enhydris/api/views.py index 0849787b..0f651be9 100644 --- a/enhydris/api/views.py +++ b/enhydris/api/views.py @@ -102,20 +102,12 @@ def get_queryset(self): class GentityFileViewSet(ReadOnlyModelViewSet): serializer_class = serializers.GentityFileSerializer - def get_permissions(self): - if self.action == "content": - pc = [permissions.CanAccessGentityFileContent] - else: - pc = [permissions.CanEditOrReadOnly] - return [x() for x in pc] - def get_queryset(self): return models.GentityFile.objects.filter(gentity_id=self.kwargs["station_id"]) @action(detail=True, methods=["get"]) def content(self, request, pk=None, *, station_id): gfile = self.get_object() - self.check_object_permissions(request, gfile) try: gfile_content_file = gfile.content.file filename = gfile_content_file.name diff --git a/enhydris/locale/el/LC_MESSAGES/django.mo b/enhydris/locale/el/LC_MESSAGES/django.mo index 476992b2..b552f48a 100644 Binary files a/enhydris/locale/el/LC_MESSAGES/django.mo and b/enhydris/locale/el/LC_MESSAGES/django.mo differ diff --git a/enhydris/locale/el/LC_MESSAGES/django.po b/enhydris/locale/el/LC_MESSAGES/django.po index dd5962aa..c8ed7472 100644 --- a/enhydris/locale/el/LC_MESSAGES/django.po +++ b/enhydris/locale/el/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-23 08:00-0500\n" +"POT-Creation-Date: 2023-04-17 08:22-0500\n" "PO-Revision-Date: 2011-11-24 14:33+0200\n" "Last-Translator: Antonis Christofides \n" "Language-Team: LANGUAGE \n" @@ -46,30 +46,30 @@ msgstr "" msgid "Replaced {} existing objects in category {} with {} new objects" msgstr "" -#: enhydris/admin/station.py:26 enhydris/models/gentity.py:48 +#: enhydris/admin/station.py:27 enhydris/models/gentity.py:48 msgid "Code" msgstr "Κωδικός" -#: enhydris/admin/station.py:28 +#: enhydris/admin/station.py:29 msgid "" "If the station has a code (e.g. one given by another agency), you can enter " "it here." msgstr "" "Αν ο σταθμός έχει κωδικό από άλλη υπηρεσία, μπορείτε να τον καταχωρίσετε εδώ." -#: enhydris/admin/station.py:33 +#: enhydris/admin/station.py:34 msgid "Co-ordinates" msgstr "Συντεταγμένες" -#: enhydris/admin/station.py:34 +#: enhydris/admin/station.py:35 msgid "Longitude and latitude in decimal degrees" msgstr "Γεωγραφικό μήκος και πλάτος σε μοίρες με δεκαδικά" -#: enhydris/admin/station.py:37 enhydris/models/gentity.py:81 +#: enhydris/admin/station.py:38 enhydris/models/gentity.py:81 msgid "Original SRID" msgstr "Αρχικό SRID" -#: enhydris/admin/station.py:40 +#: enhydris/admin/station.py:41 msgid "" "Set this to 4326 if you have no idea what we're talking about. If the " "latitude and longitude has been converted from another co-ordinate system, " @@ -79,15 +79,15 @@ msgstr "" "πλάτος και μήκος έχουν βρεθεί με μετατροπή από άλλο σύστημα συντεταγμένων, " "προσδιορίστε το SRID εκείνου του συστήματος." -#: enhydris/admin/station.py:111 +#: enhydris/admin/station.py:112 msgid "Data file" msgstr "Αρχείο δεδομένων" -#: enhydris/admin/station.py:113 +#: enhydris/admin/station.py:114 msgid "Time zone" msgstr "Ζώνη ώρας" -#: enhydris/admin/station.py:117 +#: enhydris/admin/station.py:118 msgid "" "The timestamps of the time series are stored in UTC in the database. In " "order to convert them to UTC, we need to know their time zone. If the time " @@ -99,25 +99,25 @@ msgstr "" "χρησιμοποιηθεί τυχόν ζώνη ώρας που ορίζεται στο αρχείο, και αν δεν ορίζεται " "τότε θα χρησιμοποιηθεί αυτή που ορίζεται εδώ." -#: enhydris/admin/station.py:124 +#: enhydris/admin/station.py:125 msgid "What to do" msgstr "Τρόπος εισαγωγής" -#: enhydris/admin/station.py:128 +#: enhydris/admin/station.py:129 msgid "Append this file's data to the already existing" msgstr "Πρόσθεση των δεδομένων αυτού του αρχείου στο τέλος των υπαρχόντων" -#: enhydris/admin/station.py:131 +#: enhydris/admin/station.py:132 msgid "Discard any already existing data and replace them with this file" msgstr "Διαγραφή όλων των δεδομένων και αντικατάστασή τους με αυτό το αρχείο" -#: enhydris/admin/station.py:176 +#: enhydris/admin/station.py:178 msgid "The file does not seem to be a valid UTF-8 file: " msgstr "" "Αυτό δεν φαίνεται να είναι έγκυρο έγκυρο αρχείο, ή η κωδικοποίησή του δεν " "είναι UTF-8: " -#: enhydris/admin/station.py:186 +#: enhydris/admin/station.py:183 msgid "" "The file you attempted to upload does not specify a time zone. Please " "specify the time zone of the timestamps." @@ -125,7 +125,7 @@ msgstr "" "Το αρχείο που προσπαθήσατε να ανεβάσετε δεν ορίζει ζώνη ώρας. Παρακαλώ " "ορίστε τη ζώνη ώρας των χρονοσφραγίδων." -#: enhydris/admin/station.py:201 +#: enhydris/admin/station.py:198 msgid "" "Can't append; the first record of the time series to append is earlier than " "the last record of the existing time series." @@ -133,7 +133,7 @@ msgstr "" "Δεν είναι δυνατή η πρόσθεση δεδομένων στο τέλος, γιατί η πρώτη εγγραφή του " "αρχείου προηγείται της τελευταίας εγγραφής της υπάρχουσας χρονοσειράς." -#: enhydris/admin/station.py:233 +#: enhydris/admin/station.py:230 msgid "" "The data for the time series \"{} - {} - {}\" will be imported soon. You " "will be notified by email when the importing finishes." @@ -141,19 +141,19 @@ msgstr "" "Τα δεδομένα της χρονοσειράς \"{} - {} - {}\" θα εισαχθούν σύντομα. Θα " "ειδοποιηθείτε με email όταν ολοκληρωθεί η εισαγωγή." -#: enhydris/admin/station.py:261 +#: enhydris/admin/station.py:258 msgid "There can be only one {} time series in each time series group." msgstr "Μπορεί να υπάρχει μόνο μία {} χρονοσειρά σε κάθε ομάδα χρονοσειρών." -#: enhydris/admin/station.py:285 +#: enhydris/admin/station.py:283 msgid "Metadata" msgstr "Μεταδεδομένα" -#: enhydris/admin/station.py:304 +#: enhydris/admin/station.py:302 msgid "Site" msgstr "Ιστότοπος" -#: enhydris/admin/station.py:355 +#: enhydris/admin/station.py:353 msgid "General information" msgstr "Γενικές πληροφορίες" @@ -204,8 +204,8 @@ msgid "" "displaying timestamps of time series referring to this object." msgstr "" "Οι χρονοσφραγίδες των χρονοσειρών αποθηκεύονται σε UTC. Επιλέξτε τη ζώνη " -"ώρας για προβολή χρονοσφραγίδων των χρονοσειρών που αναφέρονται σ' αυτό " -"το αντικείμενο." +"ώρας για προβολή χρονοσφραγίδων των χρονοσειρών που αναφέρονται σ' αυτό το " +"αντικείμενο." #: enhydris/models/gentity.py:83 msgid "Altitude" @@ -267,7 +267,7 @@ msgstr "Τύπος ημερολογιακών καταχωρήσεων" msgid "Log entry types" msgstr "Τύποι ημερολογιακών καταχωρήσεων" -#: enhydris/models/gentity.py:175 enhydris/models/timeseries.py:76 +#: enhydris/models/gentity.py:175 enhydris/models/timeseries.py:86 #: enhydris/templates/enhydris/station_detail/tabs/gentity_events.html:10 #: enhydris/templates/enhydris/station_detail/tabs/gentity_events.html:27 msgid "Type" @@ -319,11 +319,19 @@ msgstr "Αρμόδιος" msgid "Maintainers" msgstr "Συναρμόδιοι" -#: enhydris/models/gentity.py:238 +#: enhydris/models/gentity.py:236 +msgid "Time series data viewers" +msgstr "Δικαιούχοι ανάγνωσης" + +#: enhydris/models/gentity.py:237 +msgid "Users with permission to view time series data" +msgstr "Χρήστες με δικαίωμα να δουν τα δεδομένα των χρονοσειρών" + +#: enhydris/models/gentity.py:245 msgid "Station" msgstr "Σταθμός" -#: enhydris/models/gentity.py:239 +#: enhydris/models/gentity.py:246 #: enhydris/templates/enhydris/base/main_with_map-default.html:25 #: enhydris/templates/enhydris/station_list/main-default.html:11 msgid "Stations" @@ -361,23 +369,23 @@ msgstr "Ακρωνύμιο" msgid "Organization" msgstr "Οργανισμός" -#: enhydris/models/timeseries.py:68 +#: enhydris/models/timeseries.py:77 msgid "Initial" msgstr "Αρχική" -#: enhydris/models/timeseries.py:69 +#: enhydris/models/timeseries.py:78 msgid "Checked" msgstr "Ελεγμένη" -#: enhydris/models/timeseries.py:70 +#: enhydris/models/timeseries.py:79 msgid "Regularized" msgstr "Κανονικοποιημένη" -#: enhydris/models/timeseries.py:71 +#: enhydris/models/timeseries.py:80 msgid "Aggregated" msgstr "Συναθροισμένη" -#: enhydris/models/timeseries.py:82 +#: enhydris/models/timeseries.py:92 msgid "" "E.g. \"10min\", \"H\" (hourly), \"D\" (daily), \"M\" (monthly), \"Y" "\" (yearly). More specifically, it's an optional number plus a unit, with no " @@ -389,37 +397,49 @@ msgstr "" "μεταξύ τους. Οι διαθέσιμες μονάδες είναι min, H, D, M, Y. Αφήστε το κενό αν " "η χρονοσειρά είναι ακανόνιστη." -#: enhydris/models/timeseries.py:87 +#: enhydris/models/timeseries.py:97 msgid "Time step" msgstr "Βήμα" -#: enhydris/models/timeseries.py:91 +#: enhydris/models/timeseries.py:101 +msgid "Publicly available" +msgstr "Δημοσίως διαθέσιμη" + +#: enhydris/models/timeseries.py:103 +msgid "" +"Whether users who have not logged on have permission to download the time " +"series data." +msgstr "" +"Ορίζει το αν χρήστες που δεν έχουν πραγματοποιήσει είσοδο έχουν δικαίωμα να " +"κατεβάσουν τα δεδομένα της χρονοσειράς." + +#: enhydris/models/timeseries.py:109 msgctxt "Singular" msgid "Time series" msgstr "Χρονοσειρά" -#: enhydris/models/timeseries.py:92 +#: enhydris/models/timeseries.py:110 msgctxt "Plural" msgid "Time series" msgstr "Χρονοσειρές" -#: enhydris/models/timeseries.py:311 +#: enhydris/models/timeseries.py:330 msgid "Timestamp" msgstr "Χρονοσφραγίδα" -#: enhydris/models/timeseries.py:312 +#: enhydris/models/timeseries.py:331 msgid "Value" msgstr "Τιμή" -#: enhydris/models/timeseries.py:313 +#: enhydris/models/timeseries.py:332 msgid "Flags" msgstr "Σημαίες" -#: enhydris/models/timeseries.py:316 +#: enhydris/models/timeseries.py:335 msgid "Time series record" msgstr "Εγγραφή χρονοσειράς" -#: enhydris/models/timeseries.py:317 +#: enhydris/models/timeseries.py:336 msgid "Time series records" msgstr "Εγγραφές χρονοσειράς" @@ -721,20 +741,20 @@ msgstr "" msgid "Enhydris" msgstr "Ενυδρίς" -#: enhydris/templates/enhydris/base/navbar-default.html:15 +#: enhydris/templates/enhydris/base/navbar-default.html:16 msgid "dashboard" msgstr "πίνακας ελέγχου" -#: enhydris/templates/enhydris/base/navbar-default.html:16 +#: enhydris/templates/enhydris/base/navbar-default.html:17 #, python-format msgid "logout %(username)s " msgstr "έξοδος %(username)s " -#: enhydris/templates/enhydris/base/navbar-default.html:19 +#: enhydris/templates/enhydris/base/navbar-default.html:20 msgid "register" msgstr "εγγραφή" -#: enhydris/templates/enhydris/base/navbar-default.html:21 +#: enhydris/templates/enhydris/base/navbar-default.html:22 msgid "login" msgstr "είσοδος" @@ -814,6 +834,7 @@ msgstr "" " " #: enhydris/templates/enhydris/station_detail/dates.html:7 +#, python-format msgid "" "\n" "

Period of operation: %(start_date)s - %(end_date)s

\n" @@ -824,6 +845,7 @@ msgstr "" " " #: enhydris/templates/enhydris/station_detail/dates.html:11 +#, python-format msgid "" "\n" "

Start of operation: %(start_date)s

\n" @@ -834,6 +856,7 @@ msgstr "" " " #: enhydris/templates/enhydris/station_detail/dates.html:15 +#, python-format msgid "" "\n" "

End of operation: %(end_date)s

\n" @@ -844,6 +867,7 @@ msgstr "" " " #: enhydris/templates/enhydris/station_detail/dates.html:21 +#, python-format msgid "" "\n" "

Last update: %(last_update)s (%(offset)s)

\n" @@ -913,20 +937,16 @@ msgid "Details:" msgstr "Λεπτομέρειες:" #: enhydris/templates/enhydris/station_detail/tabs/gentity_events.html:43 -#: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:41 +#: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:37 msgid "No data available" msgstr "Δεν υπάρχουν διαθέσιμα δεδομένα" #: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:11 #: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:28 -#: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:30 +#: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:29 msgid "Download" msgstr "Λήψη" -#: enhydris/templates/enhydris/station_detail/tabs/gentity_files.html:32 -msgid "Unauthorized" -msgstr "Δεν έχετε δικαίωμα λήψης" - #: enhydris/templates/enhydris/station_detail/tabs/timeseries_groups.html:11 #: enhydris/templates/enhydris/station_detail/tabs/timeseries_groups.html:31 msgid "Start date" @@ -965,9 +985,9 @@ msgstr "Ιδιοκτήτης:" msgid "Last update:" msgstr "Τελευταία ενημέρωση:" -#: enhydris/templates/enhydris/timeseries_group_detail/download.html:16 -msgid "You don't have permission to download the data." -msgstr "Δεν έχετε δικαίωμα πρόσβασης στα δεδομένα." +#: enhydris/templates/enhydris/timeseries_group_detail/download.html:13 +msgid "No data is available for downloading." +msgstr "Δεν υπάρχουν διαθέσιμα δεδομένα." #: enhydris/templates/enhydris/timeseries_group_detail/download_button.html:31 msgid "download" @@ -1092,3 +1112,6 @@ msgstr "Εγγραφή" #, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Η μέθοδος \"{method}\" δεν επιτρέπεται." + +#~ msgid "Unauthorized" +#~ msgstr "Δεν έχετε δικαίωμα λήψης" diff --git a/enhydris/migrations/0115_timeseries_publicly_available.py b/enhydris/migrations/0115_timeseries_publicly_available.py new file mode 100644 index 00000000..f773b52d --- /dev/null +++ b/enhydris/migrations/0115_timeseries_publicly_available.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.18 on 2023-04-14 13:13 + +from django.conf import settings +from django.db import migrations, models + +import enhydris.models.timeseries + + +class Migration(migrations.Migration): + + dependencies = [ + ("enhydris", "0114_delete_timezone_model"), + ] + + operations = [ + migrations.AddField( + model_name="timeseries", + name="publicly_available", + field=models.BooleanField( + default=enhydris.models.timeseries.get_default_publicly_available, + help_text=( + "Whether users who have not logged on have permission to download " + "the time series data." + ), + verbose_name="Publicly available", + ), + ), + migrations.AddField( + model_name="station", + name="timeseries_data_viewers", + field=models.ManyToManyField( + blank=True, + related_name="viewing_stations", + to=settings.AUTH_USER_MODEL, + verbose_name="Time series data viewers", + help_text="Users with permission to view time series data", + ), + ), + ] diff --git a/enhydris/models/gentity.py b/enhydris/models/gentity.py index 7f6c9ff5..f5ca0c26 100644 --- a/enhydris/models/gentity.py +++ b/enhydris/models/gentity.py @@ -229,6 +229,13 @@ class Station(Gpoint): related_name="maintaining_stations", verbose_name=_("Maintainers"), ) + timeseries_data_viewers = models.ManyToManyField( + User, + blank=True, + related_name="viewing_stations", + verbose_name=_("Time series data viewers"), + help_text=_("Users with permission to view time series data"), + ) objects = models.Manager() on_site = CurrentSiteManager() diff --git a/enhydris/models/timeseries.py b/enhydris/models/timeseries.py index fd8fdaf9..e5dc2e2a 100644 --- a/enhydris/models/timeseries.py +++ b/enhydris/models/timeseries.py @@ -64,6 +64,10 @@ class DataNotInCache(Exception): pass +def get_default_publicly_available(): + return settings.ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE + + class Timeseries(models.Model): INITIAL = 100 CHECKED = 200 @@ -75,6 +79,7 @@ class Timeseries(models.Model): (REGULARIZED, _("Regularized")), (AGGREGATED, _("Aggregated")), ) + last_modified = models.DateTimeField(default=now, null=True, editable=False) timeseries_group = models.ForeignKey(TimeseriesGroup, on_delete=models.CASCADE) type = models.PositiveSmallIntegerField( @@ -91,6 +96,14 @@ class Timeseries(models.Model): ), verbose_name=_("Time step"), ) + publicly_available = models.BooleanField( + default=get_default_publicly_available, + verbose_name=_("Publicly available"), + help_text=_( + "Whether users who have not logged on have permission to download the time " + "series data." + ), + ) class Meta: verbose_name = pgettext_lazy("Singular", "Time series") diff --git a/enhydris/rules.py b/enhydris/rules.py index e99cbce6..27f5e4fd 100644 --- a/enhydris/rules.py +++ b/enhydris/rules.py @@ -60,6 +60,11 @@ def is_object_station_maintainer(user, obj): return user in get_object_station(obj).maintainers.all() +@rules.predicate +def is_object_station_timeseries_data_viewer(user, obj): + return user in get_object_station(obj).timeseries_data_viewers.all() + + @rules.predicate def is_superuser(user, obj): return user.is_superuser @@ -81,8 +86,13 @@ def users_can_add_content(user, obj): @rules.predicate -def open_content(user, obj): - return settings.ENHYDRIS_OPEN_CONTENT +def timeseries_data_viewers_enabled(user, obj): + return settings.ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS + + +@rules.predicate +def timeseries_is_publicly_available(user, timeseries): + return timeseries.publicly_available @rules.predicate @@ -143,5 +153,16 @@ def model_backend_can_edit_timeseries(user, obj): & (model_backend_can_edit_station | is_new_object | is_station_creator), ) -rules.add_perm("enhydris.view_timeseries_data", open_content | is_active) -rules.add_perm("enhydris.view_gentityfile_content", open_content | is_active) +rules.add_perm( + "enhydris.view_timeseries_data", + timeseries_is_publicly_available + | ( + is_active + & ( + ~timeseries_data_viewers_enabled + | is_object_station_creator + | is_object_station_maintainer + | is_object_station_timeseries_data_viewer + ) + ), +) diff --git a/enhydris/templates/enhydris/station_detail/tabs/gentity_files.html b/enhydris/templates/enhydris/station_detail/tabs/gentity_files.html index 215bcd91..b18c0068 100644 --- a/enhydris/templates/enhydris/station_detail/tabs/gentity_files.html +++ b/enhydris/templates/enhydris/station_detail/tabs/gentity_files.html @@ -26,11 +26,7 @@ {% trans 'Download' %} - {% if can_view_file_data %} - - {% else %} - {% trans 'Unauthorized' %} - {% endif %} + diff --git a/enhydris/templates/enhydris/timeseries_group_detail/download.html b/enhydris/templates/enhydris/timeseries_group_detail/download.html index c4772647..2f23ca21 100644 --- a/enhydris/templates/enhydris/timeseries_group_detail/download.html +++ b/enhydris/templates/enhydris/timeseries_group_detail/download.html @@ -2,18 +2,15 @@ {% load rules %}
- {% has_perm "enhydris.view_timeseries_data" request.user timeseries as can_view_timeseries_data %} - {% if can_view_timeseries_data %} -
- {% if object.timeseries_set.exists %} + {% if timeseries_set %} +

Download data

{% include "enhydris/timeseries_group_detail/download_button.html" %} - {% endif %} -
+
{% else %} {% endif %}
diff --git a/enhydris/tests/__init__.py b/enhydris/tests/__init__.py index e5070211..9ca7ae06 100644 --- a/enhydris/tests/__init__.py +++ b/enhydris/tests/__init__.py @@ -36,7 +36,7 @@ def tearDownClass(cls, *args, **kwargs): class TestTimeseriesMixin(ClearCacheMixin): @classmethod - def _create_test_timeseries(cls, data=""): + def _create_test_timeseries(cls, data="", publicly_available=None): cls.station = mommy.make( models.Station, name="Celduin", @@ -54,18 +54,22 @@ def _create_test_timeseries(cls, data=""): precision=1, remarks="This timeseries group rocks", ) + more_kwargs = {} + if publicly_available is not None: + more_kwargs["publicly_available"] = publicly_available cls.timeseries = mommy.make( models.Timeseries, timeseries_group=cls.timeseries_group, type=models.Timeseries.INITIAL, time_step="H", + **more_kwargs, ) cls.timeseries.set_data(StringIO(data), default_timezone="Etc/GMT-2") class TimeseriesDataMixin(ClearCacheMixin): @classmethod - def create_timeseries(cls): + def create_timeseries(cls, publicly_available=None): cls.timezone = "Etc/GMT-2" cls.tzinfo = ZoneInfo(cls.timezone) cls.htimeseries = HTimeseries() @@ -95,10 +99,14 @@ def create_timeseries(cls): variable=cls.variable, unit_of_measurement__symbol="beauton", ) + more_kwargs = {} + if publicly_available is not None: + more_kwargs["publicly_available"] = publicly_available cls.timeseries = mommy.make( models.Timeseries, type=models.Timeseries.INITIAL, timeseries_group=cls.timeseries_group, + **more_kwargs, ) cls.timeseries.set_data(cls.htimeseries.data, default_timezone=cls.timezone) diff --git a/enhydris/tests/admin/test_station.py b/enhydris/tests/admin/test_station.py index b2cca43b..e858a974 100644 --- a/enhydris/tests/admin/test_station.py +++ b/enhydris/tests/admin/test_station.py @@ -229,6 +229,25 @@ def test_add_station_has_no_creator_or_maintainers_for_user_with_model_perms(sel self.assertNotContains(response, "Maintainers") +class TimeseriesDataViewersTestCase(StationPermsTestCaseBase): + def _get_response(self): + return self.client.get(f"/admin/enhydris/station/{self.azanulbizar.id}/change/") + + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=True) + def test_station_detail_shows_timeseries_data_viewers_if_enabled(self): + self.client.login(username="alice", password="topsecret") + response = self._get_response() + assert response.status_code == 200 + self.assertContains(response, "Time series data viewers") + + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=False) + def test_station_detail_does_not_show_timeseries_data_viewers_if_disabled(self): + self.client.login(username="alice", password="topsecret") + response = self._get_response() + assert response.status_code == 200 + self.assertNotContains(response, "Time series data viewers") + + @override_settings(ENHYDRIS_USERS_CAN_ADD_CONTENT=True) class StationCreateSetsCreatorTestCase(TestCase): def setUp(self): diff --git a/enhydris/tests/test_rules.py b/enhydris/tests/test_rules.py index 6843eb0e..a47df438 100644 --- a/enhydris/tests/test_rules.py +++ b/enhydris/tests/test_rules.py @@ -1,9 +1,6 @@ from django.contrib.auth.models import AnonymousUser, Permission, User from django.test import TestCase, override_settings -from model_mommy import mommy - -from enhydris import models from enhydris.tests import TestTimeseriesMixin @@ -227,74 +224,79 @@ def test_maintainer_is_irrelevant_for_delete_timeseries(self): ) -class ContentRulesTestCaseBase(TestCase, TestTimeseriesMixin): +class ContentRulesTestCase(TestCase, TestTimeseriesMixin): """Test case base for time series data and file content.""" @classmethod - def setUpClass(cls): - super().setUpClass() + def setUpTestData(cls): + super().setUpTestData() cls.alice = User.objects.create_user(username="alice") cls.bob = User.objects.create_user(username="bob") cls.charlie = User.objects.create_user(username="charlie") cls.david = User.objects.create_user(username="david") cls.anonymous = AnonymousUser() - cls._create_test_timeseries() - cls.gentityfile = mommy.make(models.GentityFile, gentity=cls.station) + def _setup_test_stuff(self, publicly_available=None): + kwargs = {} + if publicly_available is not None: + kwargs["publicly_available"] = publicly_available + self._create_test_timeseries(**kwargs) po = Permission.objects - cls.charlie.user_permissions.add( - po.get(content_type__app_label="enhydris", codename="view_station") - ) - cls.charlie.user_permissions.add( - po.get(content_type__app_label="enhydris", codename="change_station") - ) - cls.charlie.user_permissions.add( - po.get(content_type__app_label="enhydris", codename="delete_station") - ) - cls.charlie.user_permissions.add( - po.get(content_type__app_label="enhydris", codename="change_timeseries") - ) - cls.charlie.user_permissions.add( - po.get(content_type__app_label="enhydris", codename="delete_timeseries") - ) - - -@override_settings(ENHYDRIS_OPEN_CONTENT=True) -class ContentRulesWhenContentIsOpenTestCase(ContentRulesTestCaseBase): - def test_anonymous_can_download_timeseries(self): + addprm = self.charlie.user_permissions.add + addprm(po.get(content_type__app_label="enhydris", codename="view_station")) + addprm(po.get(content_type__app_label="enhydris", codename="change_station")) + addprm(po.get(content_type__app_label="enhydris", codename="delete_station")) + addprm(po.get(content_type__app_label="enhydris", codename="change_timeseries")) + addprm(po.get(content_type__app_label="enhydris", codename="delete_timeseries")) + + def test_anonymous_can_download_publicly_available_timeseries(self): + self._setup_test_stuff(publicly_available=True) self.assertTrue( self.anonymous.has_perm("enhydris.view_timeseries_data", self.timeseries) ) - def test_anonymous_can_download_gentity_file(self): - self.assertTrue( - self.anonymous.has_perm( - "enhydris.view_gentityfile_content", self.timeseries - ) - ) - - -@override_settings(ENHYDRIS_OPEN_CONTENT=False) -class ContentRulesWhenContentIsNotOpen(ContentRulesTestCaseBase): - def test_anonymous_cannot_download_timeseries(self): + def test_anonymous_cannot_download_publicly_unavailable_timeseries(self): + self._setup_test_stuff(publicly_available=False) self.assertFalse( self.anonymous.has_perm("enhydris.view_timeseries_data", self.timeseries) ) - def test_anonymous_cannot_download_gentity_file(self): + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=True) + def test_users_cannot_download_timeseries_when_data_viewers_is_enabled(self): + self._setup_test_stuff(publicly_available=False) self.assertFalse( - self.anonymous.has_perm( - "enhydris.view_gentityfile_content", self.timeseries - ) + self.david.has_perm("enhydris.view_timeseries_data", self.timeseries) ) - def test_logged_on_can_download_timeseries(self): + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=True) + def test_creator_can_download_timeseries_when_data_viewers_is_enabled(self): + self._setup_test_stuff(publicly_available=False) + self.station.creator = self.david + self.station.save() self.assertTrue( self.david.has_perm("enhydris.view_timeseries_data", self.timeseries) ) - def test_logged_on_can_download_gentity_file(self): + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=True) + def test_maintainer_can_download_timeseries_when_data_viewers_is_enabled(self): + self._setup_test_stuff(publicly_available=False) + self.station.maintainers.add(self.david) self.assertTrue( - self.david.has_perm("enhydris.view_gentityfile_content", self.timeseries) + self.david.has_perm("enhydris.view_timeseries_data", self.timeseries) + ) + + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=True) + def test_viewer_can_download_timeseries_when_data_viewers_is_enabled(self): + self._setup_test_stuff(publicly_available=False) + self.station.timeseries_data_viewers.add(self.david) + self.assertTrue( + self.david.has_perm("enhydris.view_timeseries_data", self.timeseries) + ) + + @override_settings(ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS=False) + def test_logged_on_can_download_nonpublic_timeseries_when_viewers_disabled(self): + self._setup_test_stuff(publicly_available=False) + self.assertTrue( + self.david.has_perm("enhydris.view_timeseries_data", self.timeseries) ) diff --git a/enhydris/tests/test_views/test_station.py b/enhydris/tests/test_views/test_station.py index 629a6542..c747bcf5 100644 --- a/enhydris/tests/test_views/test_station.py +++ b/enhydris/tests/test_views/test_station.py @@ -222,16 +222,10 @@ def setUp(self): '' ).format(self.station.id, self.gentityfile.id) - @override_settings(ENHYDRIS_OPEN_CONTENT=True) - def test_contains_download_link_when_site_content_is_free(self): + def test_contains_download_link(self): response = self.client.get("/stations/{}/".format(self.station.id)) self.assertContains(response, self.link) - @override_settings(ENHYDRIS_OPEN_CONTENT=False) - def test_has_no_download_link_when_site_content_is_restricted(self): - response = self.client.get("/stations/{}/".format(self.station.id)) - self.assertNotContains(response, self.link) - class StationEditRedirectTestCase(TestCase): def setUp(self): diff --git a/enhydris/tests/test_views/test_timeseries.py b/enhydris/tests/test_views/test_timeseries.py index 88d2bdbd..0940f4a4 100644 --- a/enhydris/tests/test_views/test_timeseries.py +++ b/enhydris/tests/test_views/test_timeseries.py @@ -1,11 +1,10 @@ -from django.test import TestCase, override_settings +from django.test import TestCase from enhydris.tests import TimeseriesDataMixin class TimeseriesDownloadButtonTestCase(TimeseriesDataMixin, TestCase): def setUp(self): - self.create_timeseries() self.download_button = ( '' ) @@ -15,30 +14,30 @@ def _get_response(self): f"/stations/{self.station.id}/timeseriesgroups/{self.timeseries_group.id}/" ) - @override_settings(ENHYDRIS_OPEN_CONTENT=True) - def test_contains_download_button_when_site_content_is_free(self): + def test_contains_download_button_when_timeseries_data_is_available(self): + self.create_timeseries(publicly_available=True) self._get_response() self.assertContains(self.response, self.download_button) - @override_settings(ENHYDRIS_OPEN_CONTENT=False) - def test_has_no_download_link_when_site_content_is_restricted(self): + def test_has_no_download_link_when_timeseries_data_is_not_available(self): + self.create_timeseries(publicly_available=False) self._get_response() self.assertNotContains(self.response, self.download_button) - @override_settings(ENHYDRIS_OPEN_CONTENT=True) - def test_has_no_permission_denied_message_when_site_content_is_free(self): + def test_has_no_unavailability_message_when_timeseries_data_is_available(self): + self.create_timeseries(publicly_available=True) self._get_response() - self.assertNotContains(self.response, "You don't have permission to download") + self.assertNotContains(self.response, "No data is available for downloading") - @override_settings(ENHYDRIS_OPEN_CONTENT=False) - def test_shows_permission_denied_message_when_site_content_is_restricted(self): + def test_shows_unavailability_message_when_timeseries_data_is_unavailable(self): + self.create_timeseries(publicly_available=False) self._get_response() - self.assertContains(self.response, "You don't have permission to download") + self.assertContains(self.response, "No data is available for downloading") class DownloadDataTestCase(TimeseriesDataMixin, TestCase): def setUp(self): - self.create_timeseries() + self.create_timeseries(publicly_available=True) def _make_request(self, station_id, timeseries_group_id, timeseries_id): self.response = self.client.get( diff --git a/enhydris/tests/test_views/test_timeseries_group.py b/enhydris/tests/test_views/test_timeseries_group.py index adf7a722..0aa7ce83 100644 --- a/enhydris/tests/test_views/test_timeseries_group.py +++ b/enhydris/tests/test_views/test_timeseries_group.py @@ -1,4 +1,4 @@ -from django.test import TestCase, override_settings +from django.test import TestCase from model_mommy import mommy @@ -27,37 +27,49 @@ def test_old_timeseries_url_for_nonexistent_timeseries_returns_404(self): self.assertEqual(r.status_code, 404) -@override_settings(ENHYDRIS_OPEN_CONTENT=True) class TimeseriesGroupDetailTestCase(TimeseriesDataMixin, TestCase): - def setUp(self): - self.create_timeseries() + @classmethod + def setUpTestData(cls): + super().setUpTestData() + cls.create_timeseries(publicly_available=True) + + def _get_response(self): self.response = self.client.get( f"/stations/{self.station.id}/timeseriesgroups/{self.timeseries_group.id}/" ) def test_timeseries_group_without_timeseries(self): self.timeseries_group.timeseries_set.all().delete() - self.response = self.client.get( - f"/stations/{self.station.id}/timeseriesgroups/{self.timeseries_group.id}/" - ) + self._get_response() self.assertNotContains(self.response, "form-item-download") self.assertContains(self.response, "alert-info") # "No data" message def test_timeseries_group_with_timeseries(self): + self._get_response() self.assertContains(self.response, "form-item-download") self.assertNotContains(self.response, "alert-info") # "No data" message + def test_timeseries_group_with_all_timeseries_inaccessible(self): + self.timeseries.publicly_available = False + self.timeseries.save() + self._get_response() + self.assertNotContains(self.response, "form-item-download") + self.assertContains(self.response, "alert-info") # "No data" message + def test_title(self): + self._get_response() self.assertContains( self.response, "Beauty — Komboti — Enhydris", html=True ) def test_heading(self): + self._get_response() self.assertContains( self.response, "

Beauty (beauton)

", html=True ) def test_download_form(self): + self._get_response() self.assertContains( self.response, '', html=True ) diff --git a/enhydris/views/timeseries_group.py b/enhydris/views/timeseries_group.py index 791e5a8a..a8f58f22 100644 --- a/enhydris/views/timeseries_group.py +++ b/enhydris/views/timeseries_group.py @@ -16,8 +16,16 @@ def get_context_data(self, *args, **kwargs): context["download_data_form"] = forms.DownloadDataForm( timeseries_group=self.object ) + context["timeseries_set"] = self._get_timeseries_set() return context + def _get_timeseries_set(self): + result = [] + for timeseries in self.object.timeseries_set.all(): + if self.request.user.has_perm("enhydris.view_timeseries_data", timeseries): + result.append(timeseries) + return result + class OldTimeseriesDetailRedirectView(RedirectView): permanent = True diff --git a/enhydris_project/settings/__init__.py b/enhydris_project/settings/__init__.py index a8e4fce3..38c898dc 100644 --- a/enhydris_project/settings/__init__.py +++ b/enhydris_project/settings/__init__.py @@ -123,7 +123,8 @@ CAPTCHA_TEST_MODE = len(sys.argv) > 1 and sys.argv[1] == "test" ENHYDRIS_USERS_CAN_ADD_CONTENT = False -ENHYDRIS_OPEN_CONTENT = False +ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE = True +ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS = False ENHYDRIS_MAP_BASE_LAYERS = { "Open Street Map": r"""