localcosmos-app-kit 0.9.17__py3-none-any.whl → 0.10.0__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.
Files changed (27) hide show
  1. app_kit/appbuilder/AppReleaseBuilder.py +111 -53
  2. app_kit/appbuilder/JSONBuilders/TaxonProfilesJSONBuilder.py +38 -15
  3. app_kit/appbuilder/JSONBuilders/__pycache__/TaxonProfilesJSONBuilder.cpython-313.pyc +0 -0
  4. app_kit/appbuilder/TaxonBuilder.py +35 -28
  5. app_kit/appbuilder/__pycache__/AppReleaseBuilder.cpython-313.pyc +0 -0
  6. app_kit/appbuilder/__pycache__/TaxonBuilder.cpython-313.pyc +0 -0
  7. app_kit/features/backbonetaxonomy/templates/backbonetaxonomy/manage_taxon.html +1 -1
  8. app_kit/features/taxon_profiles/forms.py +12 -37
  9. app_kit/features/taxon_profiles/models.py +29 -36
  10. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/create_taxon_profile.html +1 -1
  11. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/manage_taxon_profile_form.html +35 -54
  12. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/manage_taxon_profile_morphotype.html +2 -1
  13. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/nature_guide_taxonlist.html +2 -2
  14. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/non_nature_guide_taxonlist.html +1 -1
  15. app_kit/features/taxon_profiles/templates/taxon_profiles/manage_taxon_profile.html +4 -4
  16. app_kit/features/taxon_profiles/urls.py +3 -0
  17. app_kit/features/taxon_profiles/views.py +44 -32
  18. app_kit/taxonomy/sources/custom/management/commands/__pycache__/import_custom_species_csv.cpython-313.pyc +0 -0
  19. app_kit/taxonomy/sources/custom/management/commands/__pycache__/import_custom_taxonomy_csv.cpython-313.pyc +0 -0
  20. app_kit/taxonomy/sources/custom/management/commands/import_custom_species_csv.py +177 -0
  21. app_kit/taxonomy/sources/custom/management/commands/import_custom_taxonomy_csv.py +177 -0
  22. {localcosmos_app_kit-0.9.17.dist-info → localcosmos_app_kit-0.10.0.dist-info}/METADATA +1 -1
  23. {localcosmos_app_kit-0.9.17.dist-info → localcosmos_app_kit-0.10.0.dist-info}/RECORD +26 -23
  24. app_kit/tests/TESTS_ROOT/media_for_tests/test/imagestore/31/a6a11b61d65ee19c4c22caa0682288ff.jpg +0 -0
  25. {localcosmos_app_kit-0.9.17.dist-info → localcosmos_app_kit-0.10.0.dist-info}/WHEEL +0 -0
  26. {localcosmos_app_kit-0.9.17.dist-info → localcosmos_app_kit-0.10.0.dist-info}/licenses/LICENCE +0 -0
  27. {localcosmos_app_kit-0.9.17.dist-info → localcosmos_app_kit-0.10.0.dist-info}/top_level.txt +0 -0
@@ -1959,6 +1959,63 @@ class AppReleaseBuilder(AppBuilderBase):
1959
1959
  localized_long_text = self.get_localized_taxonprofile_text(text_dict['longTextKey'], glossarized_locale, app_locale)
1960
1960
 
1961
1961
  return localized_short_text, localized_long_text
1962
+
1963
+ def get_localized_taxon_profile(self, profile_json, language_code):
1964
+
1965
+ glossarized_locale_filepath = self._app_glossarized_locale_filepath(language_code)
1966
+
1967
+ glossarized_locale = {}
1968
+
1969
+ if os.path.isfile(glossarized_locale_filepath):
1970
+ with open(glossarized_locale_filepath, 'r') as f:
1971
+ glossarized_locale = json.loads(f.read())
1972
+
1973
+ app_locale = self.meta_app.localizations[language_code]
1974
+
1975
+ localized_profile_json = profile_json.copy()
1976
+
1977
+ localized_short_profile = self.get_localized_taxonprofile_text(localized_profile_json['shortProfile'], glossarized_locale, app_locale)
1978
+ localized_profile_json['shortProfile'] = localized_short_profile
1979
+
1980
+ for index, text_dict in enumerate(profile_json['texts'], 0):
1981
+
1982
+ localized_short_text, localized_long_text = self.get_localized_taxonprofile_taxon_text(text_dict, glossarized_locale, app_locale)
1983
+
1984
+ localized_profile_json['texts'][index]['shortText'] = localized_short_text
1985
+ localized_profile_json['texts'][index]['longText'] = localized_long_text
1986
+
1987
+
1988
+ for c_index, category in enumerate(profile_json['categorizedTexts'], 0):
1989
+
1990
+ localized_category = category.copy()
1991
+
1992
+ localized_category_name = self.get_localized_taxonprofile_text(category['category'], glossarized_locale, app_locale)
1993
+
1994
+ if localized_category:
1995
+ localized_category['category'] = localized_category_name
1996
+
1997
+ for index, text_dict in enumerate(category['texts'], 0):
1998
+ localized_short_text, localized_long_text = self.get_localized_taxonprofile_taxon_text(text_dict, glossarized_locale, app_locale)
1999
+
2000
+ localized_category['texts'][index]['shortText'] = localized_short_text
2001
+ localized_category['texts'][index]['longText'] = localized_long_text
2002
+
2003
+ localized_profile_json['categorizedTexts'][c_index] = localized_category
2004
+
2005
+ # localize seo, no glossarized locale
2006
+ localized_seo = localized_profile_json['seo'].copy()
2007
+ title = localized_seo['title']
2008
+ meta_description = localized_seo['metaDescription']
2009
+
2010
+ if title and title in app_locale:
2011
+ localized_seo = app_locale[title]
2012
+
2013
+ if meta_description and meta_description in app_locale:
2014
+ localized_seo['metaDescription'] = app_locale[meta_description]
2015
+
2016
+ localized_profile_json['seo'] = localized_seo
2017
+
2018
+ return localized_profile_json
1962
2019
 
1963
2020
  def _build_TaxonProfiles(self, app_generic_content):
1964
2021
 
@@ -1993,6 +2050,11 @@ class AppReleaseBuilder(AppBuilderBase):
1993
2050
 
1994
2051
  if not os.path.isdir(app_absolute_taxonprofiles_path):
1995
2052
  os.makedirs(app_absolute_taxonprofiles_path)
2053
+
2054
+ # morphotype paths
2055
+ app_relative_morphotype_profiles_folder = os.path.join(app_relative_taxonprofiles_folder, 'morphotypes')
2056
+ self.build_features[generic_content_type]['localizedMorphotypeFiles'] = {}
2057
+ app_absolute_morphotype_profiles_path = os.path.join(app_absolute_taxonprofiles_path, 'morphotypes')
1996
2058
 
1997
2059
 
1998
2060
  collected_taxa = taxon_profiles.collected_taxa(published_only=True)
@@ -2081,8 +2143,10 @@ class AppReleaseBuilder(AppBuilderBase):
2081
2143
  self.build_features[generic_content_type]['localizedFiles'] = {}
2082
2144
 
2083
2145
  for profile_taxon in active_collected_taxa:
2146
+
2147
+ morphotype = None
2084
2148
 
2085
- profile_json = jsonbuilder.build_taxon_profile(profile_taxon,
2149
+ profile_json = jsonbuilder.build_taxon_profile(profile_taxon, morphotype,
2086
2150
  languages=self.meta_app.languages())
2087
2151
 
2088
2152
  if profile_json is not None:
@@ -2101,64 +2165,13 @@ class AppReleaseBuilder(AppBuilderBase):
2101
2165
  # localized taxon profiles for faster language load
2102
2166
  for language_code in self.meta_app.languages():
2103
2167
 
2104
- glossarized_locale_filepath = self._app_glossarized_locale_filepath(language_code)
2105
-
2106
- glossarized_locale = {}
2107
-
2108
- if os.path.isfile(glossarized_locale_filepath):
2109
- with open(glossarized_locale_filepath, 'r') as f:
2110
- glossarized_locale = json.loads(f.read())
2168
+ localized_profile_json = self.get_localized_taxon_profile(profile_json, language_code)
2111
2169
 
2112
2170
  relative_localized_taxonprofiles_folder = os.path.join(
2113
2171
  app_relative_taxonprofiles_folder, language_code)
2114
2172
 
2115
2173
  if language_code not in self.build_features[generic_content_type]['localizedFiles']:
2116
2174
  self.build_features[generic_content_type]['localizedFiles'][language_code] = '/{0}'.format(relative_localized_taxonprofiles_folder)
2117
-
2118
- app_locale = self.meta_app.localizations[language_code]
2119
-
2120
- localized_profile_json = profile_json.copy()
2121
-
2122
- localized_short_profile = self.get_localized_taxonprofile_text(localized_profile_json['shortProfile'], glossarized_locale, app_locale)
2123
- localized_profile_json['shortProfile'] = localized_short_profile
2124
-
2125
- for index, text_dict in enumerate(profile_json['texts'], 0):
2126
-
2127
- localized_short_text, localized_long_text = self.get_localized_taxonprofile_taxon_text(text_dict, glossarized_locale, app_locale)
2128
-
2129
- localized_profile_json['texts'][index]['shortText'] = localized_short_text
2130
- localized_profile_json['texts'][index]['longText'] = localized_long_text
2131
-
2132
-
2133
- for c_index, category in enumerate(profile_json['categorizedTexts'], 0):
2134
-
2135
- localized_category = category.copy()
2136
-
2137
- localized_category_name = self.get_localized_taxonprofile_text(category['category'], glossarized_locale, app_locale)
2138
-
2139
- if localized_category:
2140
- localized_category['category'] = localized_category_name
2141
-
2142
- for index, text_dict in enumerate(category['texts'], 0):
2143
- localized_short_text, localized_long_text = self.get_localized_taxonprofile_taxon_text(text_dict, glossarized_locale, app_locale)
2144
-
2145
- localized_category['texts'][index]['shortText'] = localized_short_text
2146
- localized_category['texts'][index]['longText'] = localized_long_text
2147
-
2148
- localized_profile_json['categorizedTexts'][c_index] = localized_category
2149
-
2150
- # localize seo, no glossarized locale
2151
- localized_seo = localized_profile_json['seo'].copy()
2152
- title = localized_seo['title']
2153
- meta_description = localized_seo['metaDescription']
2154
-
2155
- if title and title in app_locale:
2156
- localized_seo = app_locale[title]
2157
-
2158
- if meta_description and meta_description in app_locale:
2159
- localized_seo['metaDescription'] = app_locale[meta_description]
2160
-
2161
- localized_profile_json['seo'] = localized_seo
2162
2175
 
2163
2176
  absolute_localized_taxonprofiles_folder = os.path.join(
2164
2177
  app_absolute_taxonprofiles_path, language_code)
@@ -2177,7 +2190,52 @@ class AppReleaseBuilder(AppBuilderBase):
2177
2190
 
2178
2191
  with open(localized_profile_filepath, 'w', encoding='utf-8') as f:
2179
2192
  json.dump(localized_profile_json, f, indent=4, ensure_ascii=False)
2193
+
2194
+ # build morphotype profiles
2195
+ morphotypes = []
2196
+ taxon_profile = TaxonProfile.objects.filter(taxon_profiles=taxon_profiles,
2197
+ taxon_source=profile_taxon.taxon_source, taxon_latname=profile_taxon.taxon_latname,
2198
+ taxon_author=profile_taxon.taxon_author).first()
2199
+
2200
+ if taxon_profile and taxon_profile.morphotype_profiles:
2201
+ morphotypes = taxon_profile.morphotype_profiles.values_list('morphotype', flat=True)
2180
2202
 
2203
+
2204
+ for morphotype in morphotypes:
2205
+
2206
+ morphotype_profile_json = jsonbuilder.build_taxon_profile(profile_taxon, morphotype,
2207
+ languages=self.meta_app.languages())
2208
+
2209
+ if morphotype_profile_json is not None:
2210
+
2211
+ for language_code in self.meta_app.languages():
2212
+
2213
+ localized_profile_json = self.get_localized_taxon_profile(morphotype_profile_json, language_code)
2214
+
2215
+ relative_localized_morphotype_profiles_folder = os.path.join(
2216
+ app_relative_morphotype_profiles_folder, language_code)
2217
+
2218
+ if language_code not in self.build_features[generic_content_type]['localizedMorphotypeFiles']:
2219
+ self.build_features[generic_content_type]['localizedMorphotypeFiles'][language_code] = '/{0}'.format(relative_localized_morphotype_profiles_folder)
2220
+
2221
+
2222
+ absolute_localized_morphotype_profiles_folder = os.path.join(
2223
+ app_absolute_morphotype_profiles_path, language_code)
2224
+
2225
+ if not os.path.isdir(absolute_localized_morphotype_profiles_folder):
2226
+ os.makedirs(absolute_localized_morphotype_profiles_folder)
2227
+
2228
+ localized_source_folder = os.path.join(absolute_localized_morphotype_profiles_folder,
2229
+ profile_taxon.taxon_source)
2230
+
2231
+ if not os.path.isdir(localized_source_folder):
2232
+ os.makedirs(localized_source_folder)
2233
+
2234
+ localized_morphotype_profile_filepath = os.path.join(localized_source_folder,
2235
+ '{0}_{1}.json'.format(profile_taxon.name_uuid, morphotype))
2236
+
2237
+ with open(localized_morphotype_profile_filepath, 'w', encoding='utf-8') as f:
2238
+ json.dump(morphotype_profile_json, f, indent=4, ensure_ascii=False)
2181
2239
 
2182
2240
 
2183
2241
  # build search index and registry
@@ -52,7 +52,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
52
52
  installed_taxonomic_sources = [s[0] for s in settings.TAXONOMY_DATABASES]
53
53
  return installed_taxonomic_sources
54
54
 
55
-
55
+ '''
56
56
  def collect_node_traits(self, node):
57
57
 
58
58
  #self.app_release_builder.logger.info('collecting node traits for {0}'.format(node.meta_node.name))
@@ -95,6 +95,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
95
95
  #self.app_release_builder.logger.info('finished collecting')
96
96
 
97
97
  return node_traits
98
+ '''
98
99
 
99
100
 
100
101
  def get_vernacular_name_from_nature_guides(self, lazy_taxon):
@@ -109,7 +110,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
109
110
  return template_contents
110
111
 
111
112
  # languages is for the vernacular name only, the rest are keys for translation
112
- def build_taxon_profile(self, profile_taxon, languages):
113
+ def build_taxon_profile(self, profile_taxon, morphotype, languages):
113
114
 
114
115
  lazy_taxon = LazyTaxon(instance=profile_taxon)
115
116
 
@@ -118,7 +119,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
118
119
  # get the profile
119
120
  db_profile = TaxonProfile.objects.filter(taxon_profiles=self.generic_content,
120
121
  taxon_source=profile_taxon.taxon_source, taxon_latname=profile_taxon.taxon_latname,
121
- taxon_author=profile_taxon.taxon_author).first()
122
+ taxon_author=profile_taxon.taxon_author, morphotype=morphotype).first()
122
123
 
123
124
 
124
125
  taxon_profile_json = self.app_release_builder.taxa_builder.serialize_taxon_extended(lazy_taxon)
@@ -126,8 +127,9 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
126
127
  # if the taxonomic db got updated, still use the old taxon latname and author here
127
128
  taxon_profile_json['taxonLatname'] = lazy_taxon.taxon_latname
128
129
  taxon_profile_json['taxonAuthor'] = lazy_taxon.taxon_author
130
+ images = []
129
131
 
130
- images = self.app_release_builder.taxa_builder.serialize_taxon_images(lazy_taxon)
132
+ images = self.app_release_builder.taxa_builder.serialize_taxon_images(lazy_taxon, morphotype=morphotype)
131
133
 
132
134
  is_featured = False
133
135
  if db_profile:
@@ -139,6 +141,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
139
141
 
140
142
  taxon_profile_json.update({
141
143
  'taxonProfileId': db_profile.id if db_profile else None,
144
+ 'morphotype': db_profile.morphotype if db_profile else None,
142
145
  'vernacular' : {},
143
146
  'allVernacularNames' : {},
144
147
  'nodeNames' : [], # if the taxon occurs in a nature guide, primary_language only
@@ -151,7 +154,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
151
154
  'synonyms' : [],
152
155
  'templateContents' : [],
153
156
  'genericForms' : self.collect_usable_generic_forms(profile_taxon),
154
- 'taxonRelationships': self.collect_taxon_relationships(profile_taxon),
157
+ 'taxonRelationships': self.collect_taxon_relationships(profile_taxon) if not morphotype else [],
155
158
  'tags' : [],
156
159
  'seo': {
157
160
  'title': None,
@@ -162,14 +165,15 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
162
165
  'isFeatured': is_featured,
163
166
  })
164
167
 
165
- synonyms = profile_taxon.synonyms()
166
- for synonym in synonyms:
167
- synonym_entry = {
168
- 'taxonLatname' : synonym.taxon_latname,
169
- 'taxonAuthor' : synonym.taxon_author,
170
- }
168
+ if not morphotype:
169
+ synonyms = profile_taxon.synonyms()
170
+ for synonym in synonyms:
171
+ synonym_entry = {
172
+ 'taxonLatname' : synonym.taxon_latname,
173
+ 'taxonAuthor' : synonym.taxon_author,
174
+ }
171
175
 
172
- taxon_profile_json['synonyms'].append(synonym_entry)
176
+ taxon_profile_json['synonyms'].append(synonym_entry)
173
177
 
174
178
  for language_code in languages:
175
179
 
@@ -186,6 +190,24 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
186
190
 
187
191
  # template contents
188
192
  taxon_profile_json['templateContents'] = self.get_taxon_profile_template_content_links(profile_taxon, language_code)
193
+
194
+ # respect additional languages for vernacular names as defined in taxon profiles options
195
+ include_vernacular_names_languages_option = self.generic_content.get_option(self.meta_app,
196
+ 'include_vernacular_names_languages')
197
+ if include_vernacular_names_languages_option:
198
+ additional_languages = [lang.strip() for lang in include_vernacular_names_languages_option.split(',') if lang.strip()]
199
+ for language_code in additional_languages:
200
+ if language_code not in languages:
201
+ preferred_vernacular_name = lazy_taxon.get_preferred_vernacular_name(language_code,
202
+ self.meta_app)
203
+
204
+ taxon_profile_json['vernacular'][language_code] = preferred_vernacular_name
205
+
206
+ all_vernacular_names = profile_taxon.all_vernacular_names(self.meta_app,
207
+ languages=[language_code])
208
+
209
+ names_list = [name_reference['name'] for name_reference in all_vernacular_names]
210
+ taxon_profile_json['allVernacularNames'][language_code] = names_list
189
211
 
190
212
  # get taxon_profile_images
191
213
  if db_profile:
@@ -207,7 +229,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
207
229
  # get information (traits, node_names) from nature guides if possible
208
230
  # collect node images
209
231
  # only use occurrences in nature guides of this app
210
- node_occurrences = self.app_release_builder.taxa_builder.get_nature_guide_occurrences(lazy_taxon)
232
+ node_occurrences = self.app_release_builder.taxa_builder.get_nature_guide_occurrences(lazy_taxon, morphotype=morphotype)
211
233
 
212
234
  # collect traits of upward branch in tree (higher taxa)
213
235
  parent_nuids = set([])
@@ -287,7 +309,8 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
287
309
 
288
310
  if db_profile:
289
311
 
290
- taxon_profile_json['morphotypeProfiles'] = self.collect_morphotype_profiles(db_profile, languages)
312
+ if not morphotype:
313
+ taxon_profile_json['morphotypeProfiles'] = self.collect_morphotype_profiles(db_profile, languages)
291
314
 
292
315
  taxon_profile_json['shortProfile'] = db_profile.short_profile
293
316
 
@@ -469,7 +492,7 @@ class TaxonProfilesJSONBuilder(JSONBuilder):
469
492
  'taxonProfileId': morphotype.id,
470
493
  'parentTaxonProfileId': taxon_profile.id,
471
494
  'morphotype': morphotype.morphotype,
472
- 'taxon': self.app_release_builder.taxa_builder.serialize_taxon(lazy_taxon),
495
+ 'taxon': self.app_release_builder.taxa_builder.serialize_taxon_extended(lazy_taxon),
473
496
  'vernacular' : {},
474
497
  'image': image_entry,
475
498
  }
@@ -41,8 +41,7 @@ class TaxaBuilder(ContentImagesJSONBuilder):
41
41
 
42
42
  def serialize_taxon(self, lazy_taxon):
43
43
  taxon_serializer = TaxonSerializer(lazy_taxon, self)
44
- return taxon_serializer.serialize()
45
-
44
+ return taxon_serializer.serialize()
46
45
 
47
46
  def serialize_taxon_extended(self, lazy_taxon):
48
47
  taxon_serializer = TaxonSerializer(lazy_taxon, self)
@@ -66,9 +65,9 @@ class TaxaBuilder(ContentImagesJSONBuilder):
66
65
  accepted_name_uuid)
67
66
 
68
67
 
69
- def serialize_taxon_images(self, lazy_taxon):
68
+ def serialize_taxon_images(self, lazy_taxon, morphotype=None):
70
69
  taxon_serializer = TaxonSerializer(lazy_taxon, self)
71
- return taxon_serializer.serialize_images()
70
+ return taxon_serializer.serialize_images(morphotype=morphotype)
72
71
 
73
72
 
74
73
  def get_nature_guide_ids(self):
@@ -80,7 +79,7 @@ class TaxaBuilder(ContentImagesJSONBuilder):
80
79
  return self.nature_guide_ids
81
80
 
82
81
 
83
- def get_nature_guide_occurrences(self, lazy_taxon):
82
+ def get_nature_guide_occurrences(self, lazy_taxon, morphotype=None):
84
83
  nature_guide_ids = self.get_nature_guide_ids()
85
84
 
86
85
  if lazy_taxon.taxon_source in self.installed_taxonomic_sources:
@@ -88,6 +87,7 @@ class TaxaBuilder(ContentImagesJSONBuilder):
88
87
  meta_nodes = MetaNode.objects.filter(
89
88
  nature_guide_id__in=nature_guide_ids,
90
89
  node_type='result',
90
+ morphotype=morphotype,
91
91
  name_uuid = lazy_taxon.name_uuid).values_list('pk', flat=True)
92
92
 
93
93
  node_occurrences = NatureGuidesTaxonTree.objects.filter(nature_guide_id__in=nature_guide_ids,
@@ -211,12 +211,13 @@ class TaxonSerializer:
211
211
 
212
212
  return taxon_json_copy
213
213
 
214
- def get_taxon_profile(self):
214
+ def get_taxon_profile(self, morphotype=None):
215
215
  taxon_profile = None
216
216
 
217
217
  taxon_profile_qry = TaxonProfile.objects.filter(
218
218
  taxon_profiles=self.taxa_builder.taxon_profiles,
219
- name_uuid=self.lazy_taxon.name_uuid).first()
219
+ name_uuid=self.lazy_taxon.name_uuid,
220
+ morphotype=morphotype).first()
220
221
 
221
222
  if taxon_profile_qry and taxon_profile_qry.publication_status != 'draft':
222
223
  taxon_profile = taxon_profile_qry
@@ -224,19 +225,23 @@ class TaxonSerializer:
224
225
  return taxon_profile
225
226
 
226
227
 
227
- def serialize_images(self):
228
+ def serialize_images(self, morphotype=None):
228
229
 
229
230
  name_uuid_str = str(self.lazy_taxon.name_uuid)
231
+ cache_key=name_uuid_str
232
+
233
+ if morphotype:
234
+ cache_key = name_uuid_str + '_' + morphotype
230
235
 
231
- if name_uuid_str in self.taxa_builder.cache['images']:
232
- taxon_images = self.taxa_builder.cache['images'][name_uuid_str]
236
+ if cache_key in self.taxa_builder.cache['images']:
237
+ taxon_images = self.taxa_builder.cache['images'][cache_key]
233
238
 
234
239
  else:
235
240
 
236
241
  collected_content_image_ids = set([])
237
242
  collected_image_store_ids = set([])
238
243
 
239
- taxon_profile = self.get_taxon_profile()
244
+ taxon_profile = self.get_taxon_profile(morphotype=morphotype)
240
245
 
241
246
  taxon_images = {
242
247
  'primary': None,
@@ -272,8 +277,9 @@ class TaxonSerializer:
272
277
  taxon_images['primary'] = image_entry
273
278
 
274
279
 
280
+
275
281
  # images from nature guides
276
- node_occurrences = self.taxa_builder.get_nature_guide_occurrences(self.lazy_taxon)
282
+ node_occurrences = self.taxa_builder.get_nature_guide_occurrences(self.lazy_taxon, morphotype=morphotype)
277
283
 
278
284
  for node in node_occurrences:
279
285
 
@@ -297,30 +303,31 @@ class TaxonSerializer:
297
303
 
298
304
  if taxon_images['primary'] == None:
299
305
  taxon_images['primary'] = image_entry
300
-
306
+
301
307
 
302
308
  # get taxonomic images
303
- content_images_taxon = ContentImage.objects.filter(image_store__taxon_source=self.lazy_taxon.taxon_source,
304
- image_store__taxon_latname=self.lazy_taxon.taxon_latname,
305
- image_store__taxon_author=self.lazy_taxon.taxon_author).exclude(
306
- pk__in=list(collected_content_image_ids))
309
+ if not morphotype:
310
+ content_images_taxon = ContentImage.objects.filter(image_store__taxon_source=self.lazy_taxon.taxon_source,
311
+ image_store__taxon_latname=self.lazy_taxon.taxon_latname,
312
+ image_store__taxon_author=self.lazy_taxon.taxon_author).exclude(
313
+ pk__in=list(collected_content_image_ids))
307
314
 
308
- #self.app_release_builder.logger.info('Found {0} images for {1}'.format(taxon_images.count(), profile_taxon.taxon_latname))
315
+ #self.app_release_builder.logger.info('Found {0} images for {1}'.format(taxon_images.count(), profile_taxon.taxon_latname))
309
316
 
310
- for taxon_image in content_images_taxon:
317
+ for taxon_image in content_images_taxon:
311
318
 
312
- if taxon_image is not None and taxon_image.id not in collected_content_image_ids and taxon_image.image_store.id not in collected_image_store_ids:
319
+ if taxon_image is not None and taxon_image.id not in collected_content_image_ids and taxon_image.image_store.id not in collected_image_store_ids:
313
320
 
314
- image_entry = self.taxa_builder.get_image_json(taxon_image)
315
- taxon_images['taxonImages'].append(image_entry)
316
-
317
- if taxon_images['primary'] == None:
318
- taxon_images['primary'] = image_entry
321
+ image_entry = self.taxa_builder.get_image_json(taxon_image)
322
+ taxon_images['taxonImages'].append(image_entry)
323
+
324
+ if taxon_images['primary'] == None:
325
+ taxon_images['primary'] = image_entry
319
326
 
320
- collected_content_image_ids.add(taxon_image.id)
321
- collected_image_store_ids.add(taxon_image.image_store.id)
327
+ collected_content_image_ids.add(taxon_image.id)
328
+ collected_image_store_ids.add(taxon_image.image_store.id)
322
329
 
323
- self.taxa_builder.cache['images'][name_uuid_str] = taxon_images
330
+ self.taxa_builder.cache['images'][cache_key] = taxon_images
324
331
 
325
332
  taxon_images_copy = copy.deepcopy(taxon_images)
326
333
 
@@ -29,7 +29,7 @@
29
29
  <div class="mt-5">
30
30
  <h3>{% trans 'Taxon Profile' %}</h3>
31
31
  {% if taxon_profile %}
32
- <a href="{% url 'manage_taxon_profile' meta_app.id taxon_profiles.id taxon.taxon_source taxon.name_uuid %}">{{ taxon }}</a>
32
+ <a href="{% url 'manage_taxon_profile' meta_app.id taxon_profile.id %}">{{ taxon }}</a>
33
33
  {% else %}
34
34
  {% trans 'This taxon does not have a taxon profile' %}
35
35
  {% endif %}
@@ -29,6 +29,9 @@ class TaxonProfilesOptionsForm(GenericFormChoicesMixin, GenericContentOptionsFor
29
29
 
30
30
  enable_taxonomic_navigation = forms.BooleanField(required=False, label=_('Enable Taxonomic Navigation'))
31
31
 
32
+ include_vernacular_names_languages = forms.CharField(required=False, label=_('Include Vernacular Names Languages'),
33
+ help_text=_('A comma-separated list of language codes (e.g. "en,de,fr") to include vernacular names from. Leave empty to include all available languages.'))
34
+
32
35
  version = forms.CharField(help_text=_('You can manually set you own version here. This will not affect the automated versioning.'), required=False)
33
36
 
34
37
 
@@ -94,8 +97,6 @@ class ManageTaxonTextsForm(LocalizeableForm):
94
97
 
95
98
  self.layoutable_simple_fields = []
96
99
 
97
- self.has_categories = False
98
-
99
100
  super().__init__(*args, **kwargs)
100
101
 
101
102
  categories = [None] + list(TaxonTextTypeCategory.objects.filter(taxon_profiles=taxon_profiles))
@@ -105,34 +106,12 @@ class ManageTaxonTextsForm(LocalizeableForm):
105
106
  if taxon_profile.taxon_text_set:
106
107
  allowed_text_types = taxon_profile.taxon_text_set.text_types.values_list('pk', flat=True)
107
108
 
108
- if len(categories) > 1:
109
- self.has_categories = True
110
-
111
- for category_index, category in enumerate(categories, 1):
112
-
113
- types = TaxonTextType.objects.filter(taxon_profiles=taxon_profiles, category=category, pk__in=allowed_text_types).order_by('category', 'position')
114
-
115
- category_label = 'uncategorized'
116
- if category:
117
- category_label = category.name
118
-
119
- category_helper_field = forms.CharField(widget=forms.HiddenInput(), label=category_label, required=False)
120
- category_helper_field.category = category
121
- category_helper_field.is_category_field = True
122
- category_helper_field.text_type_count = types.count()
123
- category_helper_field.is_first_category = False
124
- category_helper_field.is_last = False
125
-
126
- if category_index == 2:
127
- category_helper_field.is_first_category = True
128
-
129
- if not types:
130
- category_helper_field.is_last = True
109
+ for category in categories:
131
110
 
132
- self.fields[category_label] = category_helper_field
111
+ types = TaxonTextType.objects.filter(taxon_profiles=taxon_profiles, category=category, pk__in=allowed_text_types).order_by('category', 'position')
133
112
 
134
113
  for field_index, text_type in enumerate(types, 1):
135
-
114
+
136
115
  short_text_field_name = text_type.text_type
137
116
 
138
117
  self.text_type_map[short_text_field_name] = text_type
@@ -143,14 +122,17 @@ class ManageTaxonTextsForm(LocalizeableForm):
143
122
  required=False, label=short_text_field_label, validators=[json_compatible])
144
123
  short_text_field.taxon_text_type = text_type
145
124
  short_text_field.is_short_version = True
146
- short_text_field.is_last = False
147
125
  short_text_field.taxon_text = None
126
+ short_text_field.category = None
148
127
 
149
128
  self.fields[short_text_field_name] = short_text_field
150
129
  self.localizeable_fields.append(short_text_field_name)
151
130
  self.fields[short_text_field_name].language = self.language
152
131
  self.layoutable_simple_fields.append(short_text_field_name)
153
132
 
133
+ if field_index == 1:
134
+ short_text_field.category = category
135
+
154
136
 
155
137
  if settings.APP_KIT_ENABLE_TAXON_PROFILES_LONG_TEXTS == True:
156
138
  long_text_field_name = self.get_long_text_form_field_name(text_type)
@@ -162,19 +144,12 @@ class ManageTaxonTextsForm(LocalizeableForm):
162
144
  required=False, label=long_text_field_label, validators=[json_compatible])
163
145
  long_text_field.taxon_text_type = text_type
164
146
  long_text_field.is_short_version = False
165
- long_text_field.is_last = False
166
147
 
167
148
  self.fields[long_text_field_name] = long_text_field
168
149
  self.localizeable_fields.append(long_text_field_name)
169
150
  self.fields[long_text_field_name].language = self.language
170
151
  self.layoutable_simple_fields.append(long_text_field_name)
171
-
172
- if field_index == len(types):
173
- if settings.APP_KIT_ENABLE_TAXON_PROFILES_LONG_TEXTS == True:
174
- long_text_field.is_last = True
175
- else:
176
- short_text_field.is_last = True
177
-
152
+
178
153
  if taxon_profile:
179
154
  content = TaxonText.objects.filter(taxon_text_type=text_type,
180
155
  taxon_profile=taxon_profile).first()
@@ -185,7 +160,7 @@ class ManageTaxonTextsForm(LocalizeableForm):
185
160
  if settings.APP_KIT_ENABLE_TAXON_PROFILES_LONG_TEXTS == True:
186
161
  long_text_field.initial = content.long_text
187
162
  long_text_field.taxon_text = content
188
-
163
+
189
164
 
190
165
  def get_long_text_form_field_name(self, text_type):
191
166