localcosmos-app-kit 0.9.10__py3-none-any.whl → 0.9.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app_kit/appbuilder/JSONBuilders/TaxonProfilesJSONBuilder.py +2 -3
- app_kit/appbuilder/JSONBuilders/__pycache__/TaxonProfilesJSONBuilder.cpython-313.pyc +0 -0
- app_kit/appbuilder/TaxonBuilder.py +15 -12
- app_kit/appbuilder/__pycache__/TaxonBuilder.cpython-313.pyc +0 -0
- app_kit/features/backbonetaxonomy/templates/backbonetaxonomy/update_taxon_references.html +7 -0
- app_kit/management/commands/export_all_nature_guides.py +43 -4
- app_kit/management/commands/import_all_nature_guides.py +64 -0
- app_kit/taxonomy/sources/TaxonSourceManager.py +14 -6
- app_kit/taxonomy/sources/algaebase/AlgaebaseManager.py +765 -448
- app_kit/utils.py +7 -0
- {localcosmos_app_kit-0.9.10.dist-info → localcosmos_app_kit-0.9.11.dist-info}/METADATA +2 -2
- {localcosmos_app_kit-0.9.10.dist-info → localcosmos_app_kit-0.9.11.dist-info}/RECORD +15 -14
- {localcosmos_app_kit-0.9.10.dist-info → localcosmos_app_kit-0.9.11.dist-info}/WHEEL +0 -0
- {localcosmos_app_kit-0.9.10.dist-info → localcosmos_app_kit-0.9.11.dist-info}/licenses/LICENCE +0 -0
- {localcosmos_app_kit-0.9.10.dist-info → localcosmos_app_kit-0.9.11.dist-info}/top_level.txt +0 -0
|
@@ -99,7 +99,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
|
|
|
99
99
|
|
|
100
100
|
def get_vernacular_name_from_nature_guides(self, lazy_taxon):
|
|
101
101
|
if lazy_taxon.name_uuid in self.vernacular_names_from_nature_guide_cache:
|
|
102
|
-
return self.vernacular_names_from_nature_guide_cache[lazy_taxon.name_uuid]
|
|
102
|
+
return self.vernacular_names_from_nature_guide_cache[str(lazy_taxon.name_uuid)]
|
|
103
103
|
|
|
104
104
|
return lazy_taxon.get_primary_locale_vernacular_name_from_nature_guides(self.meta_app)
|
|
105
105
|
|
|
@@ -158,7 +158,6 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
|
|
|
158
158
|
'metaDescription': None,
|
|
159
159
|
},
|
|
160
160
|
'externalMedia': [],
|
|
161
|
-
'templateContents': [],
|
|
162
161
|
'morphotypeProfiles': [],
|
|
163
162
|
'isFeatured': is_featured,
|
|
164
163
|
})
|
|
@@ -433,7 +432,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
|
|
|
433
432
|
if vernacular_start_letter not in start_letters['vernacular'][language_code]:
|
|
434
433
|
start_letters['vernacular'][language_code].append(vernacular_start_letter)
|
|
435
434
|
|
|
436
|
-
included_taxa.append(lazy_taxon.name_uuid)
|
|
435
|
+
included_taxa.append(str(lazy_taxon.name_uuid))
|
|
437
436
|
|
|
438
437
|
# sort the localited registries
|
|
439
438
|
for language_code, localized_registry in localized_registries.items():
|
|
Binary file
|
|
@@ -111,19 +111,19 @@ class TaxonSerializer:
|
|
|
111
111
|
|
|
112
112
|
def serialize(self):
|
|
113
113
|
|
|
114
|
-
if self.lazy_taxon.name_uuid in self.taxa_builder.cache['simple']:
|
|
115
|
-
taxon_json = self.taxa_builder.cache['simple'][self.lazy_taxon.name_uuid]
|
|
114
|
+
if str(self.lazy_taxon.name_uuid) in self.taxa_builder.cache['simple']:
|
|
115
|
+
taxon_json = self.taxa_builder.cache['simple'][str(self.lazy_taxon.name_uuid)]
|
|
116
116
|
|
|
117
117
|
else:
|
|
118
118
|
taxon_json = {
|
|
119
119
|
'taxonLatname' : self.lazy_taxon.taxon_latname,
|
|
120
120
|
'taxonAuthor' : self.lazy_taxon.taxon_author,
|
|
121
121
|
'taxonSource' : self.lazy_taxon.taxon_source,
|
|
122
|
-
'nameUuid' : self.lazy_taxon.name_uuid,
|
|
122
|
+
'nameUuid' : str(self.lazy_taxon.name_uuid),
|
|
123
123
|
'taxonNuid' : self.lazy_taxon.taxon_nuid,
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
self.taxa_builder.cache['simple'][self.lazy_taxon.name_uuid] = taxon_json
|
|
126
|
+
self.taxa_builder.cache['simple'][str(self.lazy_taxon.name_uuid)] = taxon_json
|
|
127
127
|
|
|
128
128
|
taxon_json_copy = copy.deepcopy(taxon_json)
|
|
129
129
|
|
|
@@ -173,8 +173,8 @@ class TaxonSerializer:
|
|
|
173
173
|
|
|
174
174
|
def serialize_extended(self):
|
|
175
175
|
|
|
176
|
-
if self.lazy_taxon.name_uuid in self.taxa_builder.cache['extended']:
|
|
177
|
-
taxon_json = self.taxa_builder.cache['extended'][self.lazy_taxon.name_uuid]
|
|
176
|
+
if str(self.lazy_taxon.name_uuid) in self.taxa_builder.cache['extended']:
|
|
177
|
+
taxon_json = self.taxa_builder.cache['extended'][str(self.lazy_taxon.name_uuid)]
|
|
178
178
|
|
|
179
179
|
else:
|
|
180
180
|
|
|
@@ -201,7 +201,7 @@ class TaxonSerializer:
|
|
|
201
201
|
taxon_json['shortProfile'] = taxon_profile.short_profile
|
|
202
202
|
|
|
203
203
|
|
|
204
|
-
self.taxa_builder.cache['extended'][self.lazy_taxon.name_uuid] = taxon_json
|
|
204
|
+
self.taxa_builder.cache['extended'][str(self.lazy_taxon.name_uuid)] = taxon_json
|
|
205
205
|
|
|
206
206
|
taxon_json_copy = copy.deepcopy(taxon_json)
|
|
207
207
|
|
|
@@ -222,8 +222,8 @@ class TaxonSerializer:
|
|
|
222
222
|
|
|
223
223
|
def serialize_images(self):
|
|
224
224
|
|
|
225
|
-
if self.lazy_taxon.name_uuid in self.taxa_builder.cache['images']:
|
|
226
|
-
taxon_images = self.taxa_builder.cache['images'][self.lazy_taxon.name_uuid]
|
|
225
|
+
if str(self.lazy_taxon.name_uuid) in self.taxa_builder.cache['images']:
|
|
226
|
+
taxon_images = self.taxa_builder.cache['images'][str(self.lazy_taxon.name_uuid)]
|
|
227
227
|
|
|
228
228
|
else:
|
|
229
229
|
|
|
@@ -314,7 +314,7 @@ class TaxonSerializer:
|
|
|
314
314
|
collected_content_image_ids.add(taxon_image.id)
|
|
315
315
|
collected_image_store_ids.add(taxon_image.image_store.id)
|
|
316
316
|
|
|
317
|
-
self.taxa_builder.cache['images'][self.lazy_taxon.name_uuid] = taxon_images
|
|
317
|
+
self.taxa_builder.cache['images'][str(self.lazy_taxon.name_uuid)] = taxon_images
|
|
318
318
|
|
|
319
319
|
taxon_images_copy = copy.deepcopy(taxon_images)
|
|
320
320
|
|
|
@@ -334,6 +334,9 @@ class TaxonSerializer:
|
|
|
334
334
|
if not accepted_name_uuid:
|
|
335
335
|
accepted_name_uuid = self.lazy_taxon.name_uuid
|
|
336
336
|
|
|
337
|
+
if accepted_name_uuid:
|
|
338
|
+
accepted_name_uuid = str(accepted_name_uuid)
|
|
339
|
+
|
|
337
340
|
has_taxon_profile = False
|
|
338
341
|
taxon_profile = self.get_taxon_profile()
|
|
339
342
|
if taxon_profile:
|
|
@@ -358,7 +361,7 @@ class TaxonSerializer:
|
|
|
358
361
|
|
|
359
362
|
def serialize_as_registry_taxon(self, languages, name_type, name, is_preferred_name, accepted_name_uuid=None):
|
|
360
363
|
|
|
361
|
-
if self.lazy_taxon.name_uuid in self.taxa_builder.cache['registry']:
|
|
364
|
+
if str(self.lazy_taxon.name_uuid) in self.taxa_builder.cache['registry']:
|
|
362
365
|
registry_taxon_json = self.taxa_builder.cache['registry']
|
|
363
366
|
|
|
364
367
|
else:
|
|
@@ -376,7 +379,7 @@ class TaxonSerializer:
|
|
|
376
379
|
if preferred_vernacular_name:
|
|
377
380
|
registry_taxon_json['vernacularNames'][language_code] = preferred_vernacular_name
|
|
378
381
|
|
|
379
|
-
self.taxa_builder.cache['registry'][self.lazy_taxon.name_uuid] = registry_taxon_json
|
|
382
|
+
self.taxa_builder.cache['registry'][str(self.lazy_taxon.name_uuid)] = registry_taxon_json
|
|
380
383
|
|
|
381
384
|
registry_taxon_json_copy = copy.deepcopy(registry_taxon_json)
|
|
382
385
|
|
|
Binary file
|
|
@@ -40,6 +40,13 @@
|
|
|
40
40
|
</div>
|
|
41
41
|
{% endfor %}
|
|
42
42
|
<div>
|
|
43
|
+
{% if updated %}
|
|
44
|
+
<div class="col-12">
|
|
45
|
+
<div class="alert alert-success">
|
|
46
|
+
{% trans 'Taxon references have been updated successfully.' %}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
{% endif %}
|
|
43
50
|
<div class="col-12">
|
|
44
51
|
<form id="update-taxa-form" method="POST" action="{% url 'update_taxon_references' meta_app.id %}">
|
|
45
52
|
{% csrf_token %}
|
|
@@ -4,9 +4,14 @@ from django.core.management.base import BaseCommand, CommandError
|
|
|
4
4
|
from django.conf import settings
|
|
5
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
6
|
from django_tenants.utils import schema_context # Assuming django-tenants for multi-tenancy
|
|
7
|
+
from django.core import serializers
|
|
8
|
+
|
|
9
|
+
from app_kit.features.nature_guides.models import (NatureGuide, MetaNode, NatureGuidesTaxonTree, NatureGuideCrosslinks, MatrixFilterSpace,
|
|
10
|
+
NatureGuidesTaxonSynonym, NatureGuidesTaxonLocale, MatrixFilter, NodeFilterSpace,
|
|
11
|
+
MatrixFilterRestriction)
|
|
12
|
+
|
|
13
|
+
from app_kit.models import ContentImage, ImageStore
|
|
7
14
|
|
|
8
|
-
from app_kit.features.nature_guides.models import NatureGuide, MetaNode, NatureGuidesTaxonTree, MatrixFilterSpace
|
|
9
|
-
from app_kit.models import ContentImage
|
|
10
15
|
|
|
11
16
|
class Command(BaseCommand):
|
|
12
17
|
help = 'Export all Nature Guides as ZIP files for a specific tenant schema'
|
|
@@ -39,7 +44,41 @@ class Command(BaseCommand):
|
|
|
39
44
|
)
|
|
40
45
|
|
|
41
46
|
# get all full filepaths of those images
|
|
42
|
-
image_filepaths = [
|
|
43
|
-
|
|
47
|
+
image_filepaths = []
|
|
48
|
+
for ci in content_images:
|
|
49
|
+
if ci.image_store and ci.image_store.source_image:
|
|
50
|
+
image_filepaths.append(ci.image_store.source_image.path)
|
|
51
|
+
|
|
52
|
+
# zip all those images into a single zip file
|
|
53
|
+
zip_filename = os.path.join(export_path, f'nature_guides_images_{schema_name}.zip')
|
|
54
|
+
with zipfile.ZipFile(zip_filename, 'w') as zipf:
|
|
55
|
+
for filepath in image_filepaths:
|
|
56
|
+
arcname = os.path.relpath(filepath, settings.MEDIA_ROOT)
|
|
57
|
+
zipf.write(filepath, arcname)
|
|
58
|
+
|
|
59
|
+
# Collect all objects to export
|
|
60
|
+
all_objects = []
|
|
61
|
+
nature_guide_models = [NatureGuide, MetaNode, NatureGuidesTaxonTree, NatureGuideCrosslinks, MatrixFilterSpace,
|
|
62
|
+
NatureGuidesTaxonSynonym, NatureGuidesTaxonLocale, MatrixFilter, NodeFilterSpace,
|
|
63
|
+
MatrixFilterRestriction]
|
|
64
|
+
for model in nature_guide_models:
|
|
65
|
+
all_objects.extend(model.objects.all())
|
|
66
|
+
all_objects.extend(content_images) # The filtered ContentImage instances
|
|
67
|
+
|
|
68
|
+
# Collect related ImageStore instances
|
|
69
|
+
image_stores = [ci.image_store for ci in content_images if ci.image_store]
|
|
70
|
+
all_objects.extend(image_stores)
|
|
71
|
+
|
|
72
|
+
# Serialize to JSON
|
|
73
|
+
data_filename = os.path.join(export_path, 'nature_guides_data.json')
|
|
74
|
+
with open(data_filename, 'w') as f:
|
|
75
|
+
serializers.serialize('json', all_objects, stream=f)
|
|
76
|
+
|
|
77
|
+
# Add the data file to the ZIP
|
|
78
|
+
with zipfile.ZipFile(zip_filename, 'a') as zipf:
|
|
79
|
+
zipf.write(data_filename, 'nature_guides_data.json')
|
|
80
|
+
|
|
81
|
+
# Optionally, remove the temporary data file after adding to ZIP
|
|
82
|
+
os.remove(data_filename)
|
|
44
83
|
|
|
45
84
|
self.stdout.write(f'All Nature Guides exported to: {export_path}')
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zipfile
|
|
3
|
+
import tempfile
|
|
4
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django_tenants.utils import schema_context
|
|
7
|
+
from django.core.management import call_command
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Command(BaseCommand):
|
|
11
|
+
help = 'Import all Nature Guides from a ZIP file for a specific tenant schema'
|
|
12
|
+
|
|
13
|
+
def add_arguments(self, parser):
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
'schema_name',
|
|
16
|
+
type=str,
|
|
17
|
+
help='The tenant schema name to import Nature Guides into (required).',
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'--zip-path',
|
|
21
|
+
type=str,
|
|
22
|
+
help='Path to the ZIP file to import (optional, defaults to the export path).',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def handle(self, *args, **options):
|
|
26
|
+
schema_name = options['schema_name']
|
|
27
|
+
zip_path = options.get('zip_path')
|
|
28
|
+
|
|
29
|
+
if not zip_path:
|
|
30
|
+
export_path = os.path.join(settings.MEDIA_ROOT, 'nature_guides_exports', schema_name)
|
|
31
|
+
zip_path = os.path.join(export_path, f'nature_guides_images_{schema_name}.zip')
|
|
32
|
+
|
|
33
|
+
if not os.path.exists(zip_path):
|
|
34
|
+
raise CommandError(f'ZIP file not found: {zip_path}')
|
|
35
|
+
|
|
36
|
+
# Use schema_context to switch to the tenant's schema
|
|
37
|
+
with schema_context(schema_name):
|
|
38
|
+
# Create a temporary directory to extract files
|
|
39
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
40
|
+
# Extract the ZIP file
|
|
41
|
+
with zipfile.ZipFile(zip_path, 'r') as zipf:
|
|
42
|
+
zipf.extractall(temp_dir)
|
|
43
|
+
|
|
44
|
+
# Find the data JSON file
|
|
45
|
+
data_filename = os.path.join(temp_dir, 'nature_guides_data.json')
|
|
46
|
+
if not os.path.exists(data_filename):
|
|
47
|
+
raise CommandError('nature_guides_data.json not found in ZIP')
|
|
48
|
+
|
|
49
|
+
# Load the data using loaddata
|
|
50
|
+
call_command('loaddata', data_filename, verbosity=1)
|
|
51
|
+
|
|
52
|
+
# Extract images to MEDIA_ROOT
|
|
53
|
+
for root, dirs, files in os.walk(temp_dir):
|
|
54
|
+
for file in files:
|
|
55
|
+
if file.endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff')): # Add more extensions if needed
|
|
56
|
+
src_path = os.path.join(root, file)
|
|
57
|
+
# Calculate the relative path from temp_dir
|
|
58
|
+
rel_path = os.path.relpath(src_path, temp_dir)
|
|
59
|
+
dest_path = os.path.join(settings.MEDIA_ROOT, rel_path)
|
|
60
|
+
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
61
|
+
# Move or copy the file
|
|
62
|
+
os.rename(src_path, dest_path) # Use shutil.move if needed
|
|
63
|
+
|
|
64
|
+
self.stdout.write(f'All Nature Guides imported successfully for schema: {schema_name}')
|
|
@@ -22,6 +22,8 @@ from django.db import transaction
|
|
|
22
22
|
# Python 3
|
|
23
23
|
import logging, html
|
|
24
24
|
|
|
25
|
+
DEBUG = False
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
'''
|
|
27
29
|
base
|
|
@@ -208,7 +210,7 @@ class SourceTreeTaxon(SourceTaxon):
|
|
|
208
210
|
def set_nuid_from_db(self):
|
|
209
211
|
|
|
210
212
|
if self.nuid is None:
|
|
211
|
-
print('setting nuid from db')
|
|
213
|
+
#print('setting nuid from db')
|
|
212
214
|
db_entry = self.TreeModel.objects.filter(source_id=self.source_id).first()
|
|
213
215
|
if db_entry:
|
|
214
216
|
self.nuid = db_entry.taxon_nuid
|
|
@@ -764,7 +766,9 @@ class TaxonSourceManager:
|
|
|
764
766
|
last_child = self._climb_down(start_taxon)
|
|
765
767
|
|
|
766
768
|
message = 'last_child: {0}, nuid: {1}'.format(last_child.latname, last_child.get_nuid())
|
|
767
|
-
|
|
769
|
+
|
|
770
|
+
if DEBUG == True:
|
|
771
|
+
print(message)
|
|
768
772
|
# self.logger.info(message)
|
|
769
773
|
|
|
770
774
|
# search siblings of this childless taxon, or parent siblings if no siblings available
|
|
@@ -782,8 +786,10 @@ class TaxonSourceManager:
|
|
|
782
786
|
if next_parent:
|
|
783
787
|
start_taxon = next_parent
|
|
784
788
|
message = 'starting nuid (next_parent): {0}'.format(start_taxon.get_nuid())
|
|
785
|
-
|
|
786
|
-
|
|
789
|
+
|
|
790
|
+
if DEBUG == True:
|
|
791
|
+
print(message)
|
|
792
|
+
# self.logger.info(message)
|
|
787
793
|
|
|
788
794
|
else:
|
|
789
795
|
continue_climbing = False
|
|
@@ -1063,8 +1069,10 @@ class TaxonSourceManager:
|
|
|
1063
1069
|
def _climb_down(self, parent_taxon):
|
|
1064
1070
|
|
|
1065
1071
|
message = 'climb down: {0}'.format(parent_taxon.latname)
|
|
1066
|
-
|
|
1067
|
-
|
|
1072
|
+
|
|
1073
|
+
if DEBUG == True:
|
|
1074
|
+
print(message)
|
|
1075
|
+
# self.logger.info(message)
|
|
1068
1076
|
|
|
1069
1077
|
# if no nuid is found, it might be a duplicate
|
|
1070
1078
|
is_duplicate = self._check_taxon_duplicate(parent_taxon)
|