From 5ad726f5e2c914e6295805e9c673f875030081d4 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 15 Aug 2024 11:25:27 -0400 Subject: [PATCH 01/10] Update data export manage commands to honor verbosity zero option --- mep/common/management/export.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mep/common/management/export.py b/mep/common/management/export.py index 74187920..dda7030c 100644 --- a/mep/common/management/export.py +++ b/mep/common/management/export.py @@ -58,6 +58,8 @@ class BaseExport(BaseCommand): :meth:`get_queryset`. """ + #: default verbosity + v_normal = 1 #: specify a model here or override get_queryset() for more control model = None @@ -80,6 +82,7 @@ def add_arguments(self, parser): def handle(self, *args, **kwargs): """Export all model data into a CSV file and JSON file.""" + self.verbosity = kwargs.get("verbosity", self.v_normal) # check that CSV export fields are defined before running if self.csv_fields is None: raise ImproperlyConfigured( @@ -94,7 +97,8 @@ def handle(self, *args, **kwargs): # get stream array / generator of data for export data = self.get_data(kwargs.get("max")) - self.stdout.write("Exporting JSON and CSV") + if self.verbosity >= self.v_normal: + self.stdout.write("Exporting JSON and CSV") # ensure directory exists (useful to allow command line user to specify dated dir) base_dir = os.path.dirname(base_filename) if base_dir: From bfb4f0fb7156b124565cc4bb8e13e15e8afda00c Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 5 Sep 2024 17:22:45 -0400 Subject: [PATCH 02/10] Rename new exports to make it clear what they belong with --- mep/accounts/management/commands/export_addresses.py | 4 ++-- mep/people/management/commands/export_creators.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mep/accounts/management/commands/export_addresses.py b/mep/accounts/management/commands/export_addresses.py index beb06d3e..0233cbfb 100644 --- a/mep/accounts/management/commands/export_addresses.py +++ b/mep/accounts/management/commands/export_addresses.py @@ -47,8 +47,8 @@ def get_queryset(self): ) def get_base_filename(self): - """set export filename to 'addresses.csv'""" - return "addresses" + """set export filename to 'member_addresses.csv'""" + return "member_addresses" def get_object_data(self, addr): """ diff --git a/mep/people/management/commands/export_creators.py b/mep/people/management/commands/export_creators.py index 1ad1756d..9c7faa1b 100644 --- a/mep/people/management/commands/export_creators.py +++ b/mep/people/management/commands/export_creators.py @@ -46,5 +46,5 @@ def get_queryset(self): ) def get_base_filename(self): - """set the filename to "creators.csv" since it's a subset of people""" - return "creators" + """set the filename to 'book_creators.csv' to indicate connection to book data""" + return "book_creators" From f917146fd87c58bcb2e3a3636b82a157fcbd970d Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 5 Sep 2024 18:27:20 -0400 Subject: [PATCH 03/10] Exclude address dates by default --- .../management/commands/export_addresses.py | 29 ++++++++++++++++++- mep/accounts/tests/test_accounts_commands.py | 23 ++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/mep/accounts/management/commands/export_addresses.py b/mep/accounts/management/commands/export_addresses.py index 0233cbfb..a7426eb6 100644 --- a/mep/accounts/management/commands/export_addresses.py +++ b/mep/accounts/management/commands/export_addresses.py @@ -6,6 +6,7 @@ start and end dates for the address, since some members have multiple addresses. """ +import argparse from django.db.models import Prefetch from mep.common.management.export import BaseExport @@ -20,7 +21,7 @@ class Command(BaseExport): model = Address - csv_fields = [ + _csv_fields = [ "member_ids", # member slug "member_uris", "care_of_person_id", # c/o person slug @@ -35,6 +36,29 @@ class Command(BaseExport): "longitude", "latitude", ] + include_dates = False + + def add_arguments(self, parser): + super().add_arguments(parser) + # in addition to base options, add a param to control date inclusion + parser.add_argument( + "--dates", + action=argparse.BooleanOptionalAction, + default=self.include_dates, + help="Include start and end dates from export?", + ) + + @property + def csv_fields(self): + if not self.include_dates: + return [f for f in self._csv_fields if not f.endswith("_date")] + else: + return self._csv_fields + + def handle(self, *args, **kwargs): + """Export all model data into a CSV file and JSON file.""" + self.include_dates = kwargs.get("dates", self.include_dates) + super().handle(*args, **kwargs) def get_queryset(self): """ @@ -76,6 +100,9 @@ def get_object_data(self, addr): country=loc.country.name if loc.country else None, arrondissement=loc.arrondissement(), ) + if not self.include_dates: + del data["start_date"] + del data["end_date"] # filter out unset values so we don't get unnecessary content in json return {k: v for k, v in data.items() if v is not None} diff --git a/mep/accounts/tests/test_accounts_commands.py b/mep/accounts/tests/test_accounts_commands.py index bb6566a5..6b58b25a 100644 --- a/mep/accounts/tests/test_accounts_commands.py +++ b/mep/accounts/tests/test_accounts_commands.py @@ -578,6 +578,7 @@ def test_get_object_data(self): end_date__isnull=False, subscription__category__isnull=True, ).first() + data = self.cmd.get_object_data(event) assert data["event_type"] == event.event_label assert data["currency"] == "FRF" @@ -638,11 +639,20 @@ def test_get_queryset(self): assert address.location == location assert member in set(address.account.persons.all()) + def test_csv_fields(self): + self.cmd.include_dates = True + assert self.cmd.csv_fields == self.cmd._csv_fields + self.cmd.include_dates = False + assert len(self.cmd.csv_fields) != len(self.cmd._csv_fields) + assert "start_date" not in self.cmd.csv_fields + assert "end_date" not in self.cmd.csv_fields + def test_get_object_data(self): # fetch some example people from fixture & call get_object_data address = Address.objects.get(pk=236) + # with dates included + self.cmd.include_dates = True gay_data = self.cmd.get_object_data(address) - # check some basic data # slug is 'gay' in sample_people, 'gay-francisque' in db @@ -661,3 +671,14 @@ def test_get_object_data(self): assert gay_data["start_date"] == "1919-01-01" assert gay_data["end_date"] == "1930-01-01" assert gay_data["care_of_person_id"] == "hemingway" + + # without dates + self.cmd.include_dates = False + gay_data = self.cmd.get_object_data(address) + # doesn't include dates + assert "start_date" not in gay_data + assert "end_date" not in gay_data + # does include other data + assert gay_data["member"]["ids"] == ["gay"] + assert gay_data["member"]["uris"] == ["https://example.com/members/gay/"] + assert gay_data["care_of_person_id"] == "hemingway" From 2128f4854ecd8982f9f8fd02d778d61c8cd6f5cc Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 10 Sep 2024 12:21:53 -0400 Subject: [PATCH 04/10] Tweak address export fields; nest member data in json output ref #831 --- .../management/commands/export_addresses.py | 46 ++++++++++--------- mep/accounts/tests/test_accounts_commands.py | 13 ++++-- mep/common/management/export.py | 9 +++- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/mep/accounts/management/commands/export_addresses.py b/mep/accounts/management/commands/export_addresses.py index a7426eb6..cb895882 100644 --- a/mep/accounts/management/commands/export_addresses.py +++ b/mep/accounts/management/commands/export_addresses.py @@ -6,9 +6,9 @@ start and end dates for the address, since some members have multiple addresses. """ + import argparse -from django.db.models import Prefetch from mep.common.management.export import BaseExport from mep.common.utils import absolutize_url from mep.accounts.models import Address @@ -23,9 +23,12 @@ class Command(BaseExport): _csv_fields = [ "member_ids", # member slug + "member_names", + "member_sort_names", "member_uris", "care_of_person_id", # c/o person slug - "care_of_person", # c/o person name + "care_of_person_name", # c/o person name + "location_name", # location name if there is one "street_address", "postal_code", "city", @@ -64,7 +67,8 @@ def get_queryset(self): """ prefetch account, location and account persons """ - return Address.objects.prefetch_related( + # skip any addresses not associated with a member + return Address.objects.filter(account__persons__isnull=False).prefetch_related( "account", "location", "account__persons", @@ -74,23 +78,22 @@ def get_base_filename(self): """set export filename to 'member_addresses.csv'""" return "member_addresses" - def get_object_data(self, addr): + def get_object_data(self, obj): """ Generate dictionary of data to export for a single :class:`~mep.people.models.Person` """ - loc = addr.location - persons = addr.account.persons.all() + loc = obj.location # required properties data = dict( # Member info - member=self.member_info(addr), + members=self.member_info(obj), # Address data - start_date=addr.partial_start_date, - end_date=addr.partial_end_date, - care_of_person_id=addr.care_of_person.slug if addr.care_of_person else None, - care_of_person=addr.care_of_person.name if addr.care_of_person else None, + start_date=obj.partial_start_date, + end_date=obj.partial_end_date, + care_of_person_id=obj.care_of_person.slug if obj.care_of_person else None, + care_of_person_name=obj.care_of_person.name if obj.care_of_person else None, # Location data street_address=loc.street_address, city=loc.city, @@ -108,14 +111,15 @@ def get_object_data(self, addr): def member_info(self, location): """Event about member(s) associated with this location""" - # adapted from event export logic - # NOTE: would be nicer and more logical if each member had their own - # dict entry, but that doesn't work with current flatting logic for csv + # adapted from event export logic; returns a list of dictionaries + # with member id, uri, name and sort name members = location.account.persons.all() - return dict( - ids=[m.slug for m in members], - uris=[absolutize_url(m.get_absolute_url()) for m in members], - # useful to include or too redundant? - # ("names", [m.name for m in members]), - # ("sort_names", [m.sort_name for m in members]), - ) + return [ + dict( + id=m.slug, + uri=absolutize_url(m.get_absolute_url()), + name=m.name, + sort_name=m.sort_name, + ) + for m in members + ] diff --git a/mep/accounts/tests/test_accounts_commands.py b/mep/accounts/tests/test_accounts_commands.py index 6b58b25a..fb14ee6f 100644 --- a/mep/accounts/tests/test_accounts_commands.py +++ b/mep/accounts/tests/test_accounts_commands.py @@ -656,8 +656,8 @@ def test_get_object_data(self): # check some basic data # slug is 'gay' in sample_people, 'gay-francisque' in db - assert gay_data["member"]["ids"] == ["gay"] - assert gay_data["member"]["uris"] == ["https://example.com/members/gay/"] + assert gay_data["members"][0]["id"] == "gay" + assert gay_data["members"][0]["uri"] == "https://example.com/members/gay/" # check addresses & coordinates assert "3 Rue Garancière" == gay_data["street_address"] @@ -678,7 +678,10 @@ def test_get_object_data(self): # doesn't include dates assert "start_date" not in gay_data assert "end_date" not in gay_data - # does include other data - assert gay_data["member"]["ids"] == ["gay"] - assert gay_data["member"]["uris"] == ["https://example.com/members/gay/"] + # other member data + assert gay_data["members"][0]["id"] == "gay" + assert gay_data["members"][0]["uri"] == "https://example.com/members/gay/" + assert gay_data["members"][0]["name"] == "Francisque Gay" + assert gay_data["members"][0]["sort_name"] == "Gay, Francisque" assert gay_data["care_of_person_id"] == "hemingway" + assert gay_data["care_of_person_name"] == "Ernest Hemingway" diff --git a/mep/common/management/export.py b/mep/common/management/export.py index dda7030c..b8efc508 100644 --- a/mep/common/management/export.py +++ b/mep/common/management/export.py @@ -190,8 +190,15 @@ def flatten_dict(data): # get a list of all keys present in any of the dictionaries subkeys = set(chain.from_iterable(i.keys() for i in val)) # flatten each field into a list of values + csv_key = key + suffix = "" + # special case: if the key ends with s, move that to the sub key, + # e.g. {members : [{id: 'xxx'}]} becomes member_ids + if key.endswith("s"): + csv_key = key[:-1] + suffix = "s" for subkey in subkeys: - flat_data["_".join([key, subkey])] = ";".join( + flat_data[f"{csv_key}_{subkey}{suffix}"] = ";".join( [str(v.get(subkey, "")) for v in val] ) From f3be33c24bb3385790b9a5f863c78bbb15d7b56e Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 10 Sep 2024 12:52:06 -0400 Subject: [PATCH 05/10] Minor improvements to event and book exports --- .../management/commands/export_events.py | 49 ++++++++----------- mep/accounts/tests/test_accounts_commands.py | 15 +++--- mep/books/management/commands/export_books.py | 13 ++--- 3 files changed, 33 insertions(+), 44 deletions(-) diff --git a/mep/accounts/management/commands/export_events.py b/mep/accounts/management/commands/export_events.py index c00709f3..acb4fb26 100644 --- a/mep/accounts/management/commands/export_events.py +++ b/mep/accounts/management/commands/export_events.py @@ -12,7 +12,6 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.functions import Coalesce from django.db.models.query import Prefetch -from djiffy.models import Manifest from mep.accounts.models import Event from mep.books.models import Creator @@ -94,18 +93,15 @@ def get_object_data(self, obj): """Generate a dictionary of data to export for a single :class:`~mep.accounts.models.Event`""" event_type = obj.event_type - data = OrderedDict( - [ - # use event label instead of type for more detail on some generics - ("event_type", obj.event_label), - ("start_date", obj.partial_start_date or ""), - ("end_date", obj.partial_end_date or ""), - ("member", OrderedDict()), - ] + data = dict( + # use event label instead of type for more detail on some generics + event_type=obj.event_label, + start_date=obj.partial_start_date or "", + end_date=obj.partial_end_date or "", ) member_info = self.member_info(obj) if member_info: - data["member"] = member_info + data["members"] = member_info currency = None @@ -150,14 +146,15 @@ def member_info(self, event): if not members: return - return OrderedDict( - [ - ("ids", [m.slug for m in members]), - ("uris", [absolutize_url(m.get_absolute_url()) for m in members]), - ("names", [m.name for m in members]), - ("sort_names", [m.sort_name for m in members]), - ] - ) + return [ + dict( + id=m.slug, + uri=absolutize_url(m.get_absolute_url()), + name=m.name, + sort_name=m.sort_name, + ) + for m in members + ] def subscription_info(self, event): """subscription details for an event""" @@ -187,11 +184,9 @@ def subscription_info(self, event): def item_info(self, event): """associated work details for an event""" if event.work: - item_info = OrderedDict( - [ - ("uri", absolutize_url(event.work.get_absolute_url())), - ("title", event.work.title), - ] + item_info = dict( + uri=absolutize_url(event.work.get_absolute_url()), + title=event.work.title, ) if event.edition: item_info["volume"] = event.edition.display_text() @@ -206,11 +201,9 @@ def item_info(self, event): def source_info(self, footnote): """source details from a footnote""" - source_info = OrderedDict( - [ - ("type", footnote.bibliography.source_type.name), - ("citation", footnote.bibliography.bibliographic_note), - ] + source_info = dict( + type=footnote.bibliography.source_type.name, + citation=footnote.bibliography.bibliographic_note, ) if footnote.bibliography.manifest: source_info["manifest"] = footnote.bibliography.manifest.uri diff --git a/mep/accounts/tests/test_accounts_commands.py b/mep/accounts/tests/test_accounts_commands.py index fb14ee6f..8ae9dddc 100644 --- a/mep/accounts/tests/test_accounts_commands.py +++ b/mep/accounts/tests/test_accounts_commands.py @@ -437,24 +437,23 @@ def test_get_data(self): assert data.total == Event.objects.count() event_data = list(data) assert len(event_data) == Event.objects.count() - assert isinstance(event_data[0], OrderedDict) + assert isinstance(event_data[0], dict) def test_member_info(self): # test single member data event = Event.objects.filter(account__persons__name__contains="Brue").first() person = event.account.persons.first() member_info = self.cmd.member_info(event) - assert member_info["sort_names"][0] == person.sort_name - assert member_info["names"][0] == person.name - assert member_info["uris"][0] == absolutize_url(person.get_absolute_url()) + assert member_info[0]["sort_name"] == person.sort_name + assert member_info[0]["name"] == person.name + assert member_info[0]["uri"] == absolutize_url(person.get_absolute_url()) # event with two members; fixture includes Edel joint account event = Event.objects.filter(account__persons__name__contains="Edel").first() member_info = self.cmd.member_info(event) - # each field should have two values - for field in ("sort_names", "names", "uris"): - assert len(member_info[field]) == 2 + # we should have two members + assert len(member_info) == 2 # test event with account but no person nomember = Event.objects.filter(account__persons__isnull=True).first() @@ -582,7 +581,7 @@ def test_get_object_data(self): data = self.cmd.get_object_data(event) assert data["event_type"] == event.event_label assert data["currency"] == "FRF" - assert "member" in data + assert "member" not in data # we don't want an empty member dict assert "subscription" in data # test separate payment event includes subscription info diff --git a/mep/books/management/commands/export_books.py b/mep/books/management/commands/export_books.py index 45123c4b..caffb8d6 100644 --- a/mep/books/management/commands/export_books.py +++ b/mep/books/management/commands/export_books.py @@ -7,7 +7,6 @@ """ -from collections import OrderedDict from django.db.models import F, Prefetch from mep.books.models import CreatorType, Work from mep.common.management.export import BaseExport @@ -76,12 +75,10 @@ def get_object_data(self, work): :class:`~mep.books.models.Work` """ # required properties - data = OrderedDict( - [ - ("id", work.slug), - ("uri", absolutize_url(work.get_absolute_url())), - ("title", work.title), - ] + data = dict( + id=work.slug, + uri=absolutize_url(work.get_absolute_url()), + title=work.title, ) data.update(self.creator_info(work)) if work.year: @@ -122,7 +119,7 @@ def get_object_data(self, work): def creator_info(self, work): """Add information about authors, editors, etc based on creators associated with this work.""" - info = OrderedDict() + info = {} for creator_type in self.creator_types: creators = work.creator_by_type(creator_type) if creators: From 151de1e91c452ec3ecff5250a6a90840c90bbf0c Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Oct 2024 11:56:14 -0400 Subject: [PATCH 06/10] Set version to 1.7 and document data export changes in next release --- CHANGELOG.rst | 11 +++++++++++ mep/__init__.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8562cb68..a0e9d6a5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,17 @@ CHANGELOG ========= +1.7 +--- + +Revisions to data exports in preparation for 2.0 dataset publication: + +- Address export files renamed `member_addresses` (both csv and json) +- Creator export files renamed `book_creators` (both csv and json) +- Address export manage command now does not include start and end dates by default, +with an optional flag to include them +- Address export includes care of person id and name, location name, and member names, sort names, and uris + 1.6.2 ----- diff --git a/mep/__init__.py b/mep/__init__.py index c99688b4..00d8fe3d 100644 --- a/mep/__init__.py +++ b/mep/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.7.0.dev0" +__version__ = "1.7.0" # context processor to add version to the template environment From 8dc37f0f920d7649e588391712aaede559ab519b Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Oct 2024 12:04:18 -0400 Subject: [PATCH 07/10] Update npm dependencies with npm audit fix --- package-lock.json | 322 +++++++++++++++++++++++----------------------- 1 file changed, 159 insertions(+), 163 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb81498d..83fbc22d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2349,13 +2349,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2370,20 +2370,20 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -2392,9 +2392,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2564,24 +2564,6 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.1.tgz", "integrity": "sha512-5j/AnefKAhCw4HpITmLDTPlf4vhi8o/dES+zbegfPb7LaGfNyqkLxBR6E+4yvTAgnJLmhe80EXFMzUs38fw4oA==" }, - "node_modules/@types/eslint": { - "version": "8.44.3", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", - "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz", - "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/esri-leaflet": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/@types/esri-leaflet/-/esri-leaflet-2.1.11.tgz", @@ -2591,9 +2573,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/express": { "version": "4.17.18", @@ -2839,9 +2821,9 @@ "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -2858,9 +2840,9 @@ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", @@ -2878,14 +2860,14 @@ "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -2910,26 +2892,26 @@ "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -2937,22 +2919,22 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -2961,11 +2943,11 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -3059,10 +3041,10 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "peerDependencies": { "acorn": "^8" } @@ -3517,9 +3499,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -3530,7 +3512,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4859,18 +4841,18 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5126,37 +5108,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -5239,13 +5221,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8186,10 +8168,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -8206,11 +8191,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -8430,10 +8415,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8653,9 +8641,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, "node_modules/path-type": { @@ -9423,12 +9411,12 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -9854,9 +9842,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -9892,6 +9880,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9985,15 +9982,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -10398,9 +10395,9 @@ } }, "node_modules/terser": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", - "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10415,15 +10412,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -11046,9 +11043,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -11076,33 +11073,32 @@ } }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -11211,9 +11207,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -11244,7 +11240,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { From 541ed499e8823af227b29a2f7ddbefac6e2cd4f2 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Oct 2024 12:14:51 -0400 Subject: [PATCH 08/10] Document another change in the data export functionality --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0e9d6a5..fdb73aab 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Revisions to data exports in preparation for 2.0 dataset publication: - Address export manage command now does not include start and end dates by default, with an optional flag to include them - Address export includes care of person id and name, location name, and member names, sort names, and uris +- Nested information in JSON data exports is now grouped by entity 1.6.2 ----- From 15441d32daffed97a058336e339a36d4ffb8d258 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Oct 2024 12:29:12 -0400 Subject: [PATCH 09/10] Document current tested versions of python dependencies --- requirements.lock | 143 +++++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/requirements.lock b/requirements.lock index d58cf2c1..4807a7d8 100644 --- a/requirements.lock +++ b/requirements.lock @@ -1,126 +1,127 @@ addict==2.4.0 -alabaster==0.7.13 +alabaster==0.7.16 anyascii==0.3.2 -asgiref==3.7.2 +asgiref==3.8.1 attrdict==2.0.1 attrdict3==2.0.2 -Babel==2.12.1 +babel==2.16.0 beautifulsoup4==4.8.2 -bleach==6.0.0 +bleach==6.1.0 cached-property==1.5.2 -certifi==2023.5.7 -cfgv==3.3.1 -charset-normalizer==3.2.0 -coverage==7.2.7 +certifi==2024.8.30 +cfgv==3.4.0 +charset-normalizer==3.3.2 +coverage==7.6.1 defusedxml==0.7.1 dictionaries==0.0.2 diff-match-patch==20230430 -distlib==0.3.7 -Django==3.2.20 +distlib==0.3.8 +Django==3.2.25 django-adminlogentries==0.1.3 django-apptemplates==1.5 -django-autocomplete-light==3.9.7 -django-cas-ng==4.3.0 -django-csp==3.7 -django-debug-toolbar==4.1.0 +django-autocomplete-light==3.11.0 +django-cas-ng==5.0.1 +django-debug-toolbar==4.3.0 django-filter==23.5 django-fullurl==1.4 -django-grappelli==3.0.6 +django-grappelli==4.0.1 django-import-export==3.3.9 django-markdownify==0.9.3 -django-modelcluster==6.2.1 +django-modelcluster==6.3 django-permissionedforms==0.1 django-tabular-export==1.1.0 -django-taggit==2.1.0 -django-treebeard==4.7 +django-taggit==4.0.0 +django-treebeard==4.7.1 django-webpack-loader==2.0.0 -django-widget-tweaks==1.4.12 -djangorestframework==3.14.0 +django-widget-tweaks==1.5.0 +django_csp==3.8 +djangorestframework==3.15.1 djiffy==0.8.0 -docutils==0.20.1 +docutils==0.21.2 draftjs-exporter==2.1.7 et-xmlfile==1.1.0 eulxml==1.1.3 -exceptiongroup==1.1.2 -factory-boy==3.3.0 -Faker==22.6.0 -filelock==3.12.2 +exceptiongroup==1.2.2 +factory_boy==3.3.1 +Faker==30.3.0 +filelock==3.16.1 filetype==1.2.0 html5lib==1.1 -identify==2.5.25 -idna==3.4 +identify==2.6.1 +idna==3.10 imagesize==1.4.1 -importlib-metadata==6.8.0 +importlib_metadata==8.5.0 iniconfig==2.0.0 isodate==0.6.1 -Jinja2==3.1.2 +Jinja2==3.1.4 jsonfield==2.1.1 l18n==2021.3 ldap3==2.9.1 -lxml==4.9.3 -Markdown==3.4.3 +lxml==5.3.0 +Markdown==3.7 MarkupPy==1.14 -MarkupSafe==2.1.3 -nodeenv==1.8.0 +MarkupSafe==3.0.0 +mep-django @ file:///Users/rkoeser/workarea/github/mep-django +nodeenv==1.9.1 oauthlib==3.2.2 odfpy==1.4.1 -openpyxl==3.1.2 -packaging==23.1 +openpyxl==3.1.5 +packaging==24.1 parasolr==0.9.2 piffle==0.4.0 -Pillow==9.5.0 -pillow-heif==0.14.0 -platformdirs==3.9.1 -pluggy==1.2.0 +pillow==10.4.0 +pillow_heif==0.18.0 +platformdirs==4.3.6 +pluggy==1.5.0 ply==3.11 -pre-commit==3.3.3 -progressbar2==4.2.0 -psycopg2-binary==2.8.6 +pre_commit==4.0.1 +progressbar2==4.5.0 +psycopg2-binary==2.9.9 pucas==0.8.0 py-flags==1.1.4 -pyasn1==0.5.0 -Pygments==2.15.1 +pyasn1==0.6.1 +Pygments==2.18.0 pymarc==4.2.2 -pyparsing==3.1.0 -pytest==7.4.0 -pytest-cov==4.1.0 -pytest-django==4.5.2 +pyparsing==3.1.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django==4.9.0 pytest-ordering==0.6 python-cas==1.6.0 -python-dateutil==2.8.2 -python-utils==3.7.0 -pytz==2023.3 -PyYAML==6.0.1 -rdflib==6.3.2 -requests==2.31.0 +python-dateutil==2.9.0.post0 +python-utils==3.9.0 +pytz==2024.2 +PyYAML==6.0.2 +rdflib==7.0.0 +requests==2.32.3 requests-oauthlib==1.3.1 six==1.16.0 snowballstemmer==2.2.0 -soupsieve==2.4.1 -Sphinx==7.0.1 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.1 +soupsieve==2.6 +Sphinx==7.4.7 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 -sqlparse==0.4.4 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +sqlparse==0.5.1 stop-words==2018.7.23 tablib==3.5.0 telepath==0.3.1 -tinycss2==1.1.1 -tomli==2.0.1 +tinycss2==1.2.1 +tomli==2.0.2 tweepy==4.14.0 -typing_extensions==4.7.1 -Unidecode==1.3.6 -urllib3==2.0.4 +typing_extensions==4.12.2 +Unidecode==1.3.8 +urllib3==2.2.3 viapy==0.3.0 -virtualenv==20.24.1 +virtualenv==20.26.6 wagtail==5.1.3 -wagtail-factories==4.1.0 +wagtail-factories==3.1.0 webencodings==0.5.1 Willow==1.6.3 xlrd==2.0.1 -XlsxWriter==3.1.2 +XlsxWriter==3.2.0 xlwt==1.3.0 -zipp==3.16.2 +zipp==3.20.2 From 2c9aab78af80ad96f244911987f320666904d876 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Oct 2024 14:35:56 -0400 Subject: [PATCH 10/10] Add support for plausible analytics --- CHANGELOG.rst | 1 + mep/context_processors.py | 6 ++++++ mep/local_settings.py.sample | 7 +++++++ templates/404.html | 8 ++++++++ templates/base.html | 1 + templates/snippets/plausible_analytics.html | 7 +++++++ 6 files changed, 30 insertions(+) create mode 100644 templates/snippets/plausible_analytics.html diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fdb73aab..d09169e7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Revisions to data exports in preparation for 2.0 dataset publication: with an optional flag to include them - Address export includes care of person id and name, location name, and member names, sort names, and uris - Nested information in JSON data exports is now grouped by entity +- Add support for Plausible analytics, configurable with PLAUSIBLE_ANALYTICS_SCRIPT and PLAUSIBLE_ANALYTICS_404s in django settings 1.6.2 ----- diff --git a/mep/context_processors.py b/mep/context_processors.py index f0099932..9096b3b3 100644 --- a/mep/context_processors.py +++ b/mep/context_processors.py @@ -15,5 +15,11 @@ def template_settings(request): "site": site, # needed for footer; easier to get here than in template "about_page": site.root_page.get_children().filter(slug="about").first(), + "PLAUSIBLE_ANALYTICS_SCRIPT": getattr( + settings, "PLAUSIBLE_ANALYTICS_SCRIPT", None + ), + "PLAUSIBLE_ANALYTICS_404s": getattr( + settings, "PLAUSIBLE_ANALYTICS_404s", False + ), } return context_extras diff --git a/mep/local_settings.py.sample b/mep/local_settings.py.sample index 1736c4c9..eedc3edd 100644 --- a/mep/local_settings.py.sample +++ b/mep/local_settings.py.sample @@ -143,6 +143,13 @@ LOGGING = { # https://support.google.com/analytics/answer/2709828 # GTAGS_ANALYTICS_ENV = '' +# INCLUDE_ANALYTICS = True + +# Configure to enable Plausible analytics in production with desired options +# PLAUSIBLE_ANALYTICS_SCRIPT = "https://plausible.io/js/script.file-downloads.hash.outbound-links.js" +# PLAUSIBLE_ANALYTICS_404s = True + + # secret settings for twitter api access 100 years twitter account TWITTER_100YEARS = { 'API': { diff --git a/templates/404.html b/templates/404.html index ffb672de..cc5a8324 100644 --- a/templates/404.html +++ b/templates/404.html @@ -13,4 +13,12 @@ src="{% static 'img/404-error-1x.png' %}" alt="beach notecard reading 'absent on business, will return shortly'" class="e404"> +{% endblock %} + + +{% block js %} +{{ block.super }} +{% if PLAUSIBLE_ANALYTICS_404s %} + +{% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index ac7b0665..d131379c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -33,6 +33,7 @@ {% render_bundle 'main' 'js' %} {% block js %}{% endblock %} + {% include "snippets/plausible_analytics.html" %} {% include 'snippets/test_banner.html' %} diff --git a/templates/snippets/plausible_analytics.html b/templates/snippets/plausible_analytics.html new file mode 100644 index 00000000..314e97e7 --- /dev/null +++ b/templates/snippets/plausible_analytics.html @@ -0,0 +1,7 @@ +{% if INCLUDE_ANALYTICS and PLAUSIBLE_ANALYTICS_SCRIPT %} +{# plausible analytics #} + +{% if PLAUSIBLE_ANALYTICS_404s %} + +{% endif %} +{% endif %} \ No newline at end of file