localcosmos-app-kit 0.9.16__py3-none-any.whl → 0.9.17__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.
@@ -352,6 +352,10 @@ class TestTaxonProfilesZipImporter(WithMedia, WithTaxonProfiles, WithUser, WithM
352
352
  taxon_latname='Fraxinus excelsior', morphotype='leaf')
353
353
  self.assertEqual(fraxinus_excielior_leaf_profile.short_profile,'Fraxinus excelsior morphotype leaf short profile')
354
354
 
355
+
356
+
357
+
358
+
355
359
  @test_settings
356
360
  def test_partial_import(self):
357
361
  importer = self.get_zip_importer()
@@ -570,6 +570,180 @@ class GenericContentZipImporter:
570
570
  'valid_media_types': ', '.join(AVAILABLE_EXTERNAL_MEDIA_TYPES),
571
571
  }
572
572
  self.add_cell_error(self.workbook_filename, sheet_name, 'B', row_index, message)
573
+
574
+ if external_media_data['media_type']:
575
+ # call specific validator method for each media type
576
+ validator_method_name = 'validate_external_media_type_{0}'.format(
577
+ external_media_data['media_type'].lower()
578
+ )
579
+ validator_method = getattr(self, validator_method_name, None)
580
+ if validator_method:
581
+ validator_method(external_media_data, sheet_name, row_index)
582
+
583
+
584
+
585
+ '''
586
+ image, youTube, vimeo, mp3, wav, pdf, website, file
587
+ '''
588
+ def validate_external_media_type_image(self, external_media_data, sheet_name, row_index):
589
+ # check that the url ends with a valid image extension
590
+ image_extension = os.path.splitext(external_media_data['url'])[1].lower()
591
+ if image_extension not in VALID_IMAGE_FORMATS:
592
+ message = _('Invalid image format in URL: %(cell_value)s. Valid formats are: %(valid_formats)s') % {
593
+ 'cell_value': image_extension,
594
+ 'valid_formats': ', '.join(VALID_IMAGE_FORMATS),
595
+ }
596
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
597
+
598
+
599
+
600
+ def validate_external_media_type_youtube(self, external_media_data, sheet_name, row_index):
601
+ url = (external_media_data.get('url') or '').strip()
602
+ if not url:
603
+ message = _('Invalid YouTube URL: empty value')
604
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
605
+ return
606
+
607
+ try:
608
+ from urllib.parse import urlparse, parse_qs
609
+ parsed = urlparse(url)
610
+ netloc = (parsed.netloc or '').lower()
611
+ path = parsed.path or ''
612
+ query = parse_qs(parsed.query or '')
613
+ except Exception:
614
+ message = _('Invalid YouTube URL: %(cell_value)s') % {'cell_value': url}
615
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
616
+ return
617
+
618
+ if parsed.scheme not in ('http', 'https'):
619
+ message = _('Invalid YouTube URL (scheme must be http/https): %(cell_value)s') % {'cell_value': url}
620
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
621
+ return
622
+
623
+ video_id = None
624
+
625
+ # youtu.be/<id>
626
+ if netloc.endswith('youtu.be'):
627
+ candidate = path.lstrip('/')
628
+ m = re.match(r'^([A-Za-z0-9_-]{11})(?:$|[/?#])', candidate)
629
+ if m:
630
+ video_id = m.group(1)
631
+
632
+ # *.youtube.com/... variants
633
+ if not video_id and netloc.endswith('youtube.com'):
634
+ # watch?v=<id>
635
+ vvals = query.get('v')
636
+ if vvals:
637
+ v = vvals[0]
638
+ if re.fullmatch(r'[A-Za-z0-9_-]{11}', v or ''):
639
+ video_id = v
640
+ # /embed/<id>, /v/<id>, /shorts/<id>
641
+ if not video_id:
642
+ m = re.search(r'/(?:embed|v|shorts)/([A-Za-z0-9_-]{11})(?:$|[/?#])', path)
643
+ if m:
644
+ video_id = m.group(1)
645
+
646
+ if not video_id:
647
+ message = _('Invalid YouTube URL or video id not found: %(cell_value)s') % {'cell_value': url}
648
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
649
+ return
650
+
651
+ # Optionally: could normalize/store the ID if needed
652
+ # external_media_data['video_id'] = video_id
653
+
654
+ def validate_external_media_type_vimeo(self, external_media_data, sheet_name, row_index):
655
+ url = (external_media_data.get('url') or '').strip()
656
+ if not url:
657
+ message = _('Invalid Vimeo URL: empty value')
658
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
659
+ return
660
+
661
+ try:
662
+ from urllib.parse import urlparse
663
+ parsed = urlparse(url)
664
+ netloc = (parsed.netloc or '').lower()
665
+ path = parsed.path or ''
666
+ except Exception:
667
+ message = _('Invalid Vimeo URL: %(cell_value)s') % {'cell_value': url}
668
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
669
+ return
670
+
671
+ if parsed.scheme not in ('http', 'https'):
672
+ message = _('Invalid Vimeo URL (scheme must be http/https): %(cell_value)s') % {'cell_value': url}
673
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
674
+ return
675
+
676
+ video_id = None
677
+
678
+ if netloc.endswith('vimeo.com'):
679
+ # player.vimeo.com/video/<id>
680
+ m = re.search(r'/video/(\d+)(?:$|[/?#])', path)
681
+ if m:
682
+ video_id = m.group(1)
683
+ # vimeo.com/<id> or any path ending with /<digits>
684
+ if not video_id:
685
+ m = re.search(r'/(\d+)(?:$|[/?#])', path)
686
+ if m:
687
+ video_id = m.group(1)
688
+
689
+ if not video_id:
690
+ message = _('Invalid Vimeo URL or video id not found: %(cell_value)s') % {'cell_value': url}
691
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
692
+ return
693
+
694
+ # Optionally: could normalize/store the ID if needed
695
+ # external_media_data['video_id'] = video_id
696
+
697
+
698
+ def validate_external_media_type_mp3(self, external_media_data, sheet_name, row_index):
699
+ # has to end with .mp3
700
+ if not external_media_data['url'].lower().endswith('.mp3'):
701
+ message = _('Invalid mp3 format in URL: %(cell_value)s. URL has to end with .mp3') % {
702
+ 'cell_value': external_media_data['url'],
703
+ }
704
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
705
+
706
+ def validate_external_media_type_wav(self, external_media_data, sheet_name, row_index):
707
+ # has to end with .wav
708
+ if not external_media_data['url'].lower().endswith('.wav'):
709
+ message = _('Invalid wav format in URL: %(cell_value)s. URL has to end with .wav') % {
710
+ 'cell_value': external_media_data['url'],
711
+ }
712
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
713
+
714
+ def validate_external_media_type_pdf(self, external_media_data, sheet_name, row_index):
715
+ # has to end with .pdf
716
+ if not external_media_data['url'].lower().endswith('.pdf'):
717
+ message = _('Invalid pdf format in URL: %(cell_value)s. URL has to end with .pdf') % {
718
+ 'cell_value': external_media_data['url'],
719
+ }
720
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
721
+
722
+
723
+ def validate_external_media_type_website(self, external_media_data, sheet_name, row_index):
724
+ # Only flag as invalid if the PATH (not the domain) ends with a file extension
725
+ try:
726
+ from urllib.parse import urlparse
727
+ parsed = urlparse((external_media_data.get('url') or '').strip())
728
+ path = parsed.path or ''
729
+ except Exception:
730
+ path = ''
731
+
732
+ # Detect file-like endings in path (e.g., /file.jpg, /index.html)
733
+ if re.search(r'/[^/]+\.[a-zA-Z0-9]{2,5}$', path):
734
+ message = _('Invalid website format in URL: %(cell_value)s. URL should not end with a file extension.') % {
735
+ 'cell_value': external_media_data['url'],
736
+ }
737
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
738
+
739
+
740
+ def validate_external_media_type_file(self, external_media_data, sheet_name, row_index):
741
+ # file endings: check if the url has a file extension
742
+ if not re.search(r'\.[a-zA-Z0-9]{2,5}($|\?)', external_media_data['url']):
743
+ message = _('Invalid file format in URL: %(cell_value)s. URL has to end with a file extension.') % {
744
+ 'cell_value': external_media_data['url'],
745
+ }
746
+ self.add_cell_error(self.workbook_filename, sheet_name, 'A', row_index, message)
573
747
 
574
748
 
575
749
  def add_cell_error(self, filename, sheet_name, column, row, message):
@@ -13,6 +13,7 @@ TAXON_RANK_CHOICES = (
13
13
  ('', '-----'),
14
14
  ('kingdom', _('Kingdom')),
15
15
  ('phylum', _('Phylum')),
16
+ ('subphylum', _('Subphylum')),
16
17
  ('class', _('Class')),
17
18
  ('order', _('Order')),
18
19
  ('family', _('Family')),
app_kit/taxonomy/views.py CHANGED
@@ -79,7 +79,7 @@ class TaxonTreeView(TemplateView):
79
79
  if self.taxon:
80
80
  children_nuid_length = len(self.taxon.taxon_nuid) + 3
81
81
  taxa = self.models.TaxonTreeModel.objects.annotate(nuid_len=Length('taxon_nuid')).filter(
82
- taxon_nuid__startswith=self.taxon.taxon_nuid, nuid_len=children_nuid_length)
82
+ taxon_nuid__startswith=self.taxon.taxon_nuid, nuid_len=children_nuid_length).order_by('taxon_latname')
83
83
 
84
84
  else:
85
85
  taxa = self.get_root_taxa()
@@ -320,7 +320,7 @@ class TestGenericContentZipImporter(WithUser, WithMetaApp, TenantTestCase):
320
320
  self.assertEqual(licence.licence, 'CC BY-NC')
321
321
  self.assertEqual(licence.licence_version, '4.0')
322
322
  self.assertEqual(licence.source_link, 'https://imageworld.com/lacerta-agilis.jpg')
323
- self.assertEqual(licence.creator_name, 'Art Vandeley')
323
+ self.assertEqual(licence.creator_name, 'New Author')
324
324
  self.assertEqual(content_image.alt_text, 'A new alt text')
325
325
  self.assertEqual(content_image.text, 'A new caption')
326
326
  self.assertEqual(content_image.title, 'Lizard')
@@ -421,6 +421,146 @@ class TestGenericContentZipImporter(WithUser, WithMetaApp, TenantTestCase):
421
421
  # No match for wrong author
422
422
  matches = importer.get_taxa_with_taxon_author_tolerance(taxon_source, taxon_latname, 'Smith')
423
423
  self.assertEqual(len(matches), 0)
424
+
425
+ # External Media URL validation
426
+ @test_settings
427
+ def test_validate_external_media_type_youtube_valid_urls(self):
428
+ importer = self.get_zip_importer()
429
+ importer.load_workbook()
430
+ importer.errors = []
431
+
432
+ valid_urls = [
433
+ 'https://youtu.be/dQw4w9WgXcQ',
434
+ 'http://youtu.be/dQw4w9WgXcQ',
435
+ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
436
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ',
437
+ 'https://www.youtube.com/embed/dQw4w9WgXcQ',
438
+ 'https://www.youtube.com/v/dQw4w9WgXcQ',
439
+ 'https://www.youtube.com/shorts/dQw4w9WgXcQ',
440
+ ]
441
+
442
+ for url in valid_urls:
443
+ importer.validate_external_media_type_youtube({'url': url}, importer.external_media_sheet_name, 2)
444
+ self.assertEqual(importer.errors, [])
445
+
446
+ @test_settings
447
+ def test_validate_external_media_type_youtube_invalid_urls(self):
448
+ importer = self.get_zip_importer()
449
+ importer.load_workbook()
450
+ importer.errors = []
451
+
452
+ # Empty URL
453
+ importer.validate_external_media_type_youtube({'url': ''}, importer.external_media_sheet_name, 2)
454
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid YouTube URL: empty value', importer.errors)
455
+
456
+ # Invalid scheme
457
+ bad_url = 'ftp://youtu.be/dQw4w9WgXcQ'
458
+ importer.validate_external_media_type_youtube({'url': bad_url}, importer.external_media_sheet_name, 2)
459
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid YouTube URL (scheme must be http/https): ftp://youtu.be/dQw4w9WgXcQ', importer.errors)
460
+
461
+ # No video id
462
+ bad_url = 'https://www.youtube.com/watch?v='
463
+ importer.validate_external_media_type_youtube({'url': bad_url}, importer.external_media_sheet_name, 2)
464
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid YouTube URL or video id not found: https://www.youtube.com/watch?v=', importer.errors)
465
+
466
+ @test_settings
467
+ def test_validate_external_media_type_vimeo_valid_urls(self):
468
+ importer = self.get_zip_importer()
469
+ importer.load_workbook()
470
+ importer.errors = []
471
+
472
+ valid_urls = [
473
+ 'https://vimeo.com/123456789',
474
+ 'http://vimeo.com/123456789',
475
+ 'https://player.vimeo.com/video/123456789',
476
+ 'https://www.vimeo.com/123456789',
477
+ ]
478
+
479
+ for url in valid_urls:
480
+ importer.validate_external_media_type_vimeo({'url': url}, importer.external_media_sheet_name, 2)
481
+ self.assertEqual(importer.errors, [])
482
+
483
+ @test_settings
484
+ def test_validate_external_media_type_vimeo_invalid_urls(self):
485
+ importer = self.get_zip_importer()
486
+ importer.load_workbook()
487
+ importer.errors = []
488
+
489
+ # Empty URL
490
+ importer.validate_external_media_type_vimeo({'url': ''}, importer.external_media_sheet_name, 2)
491
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid Vimeo URL: empty value', importer.errors)
492
+
493
+ # Invalid scheme
494
+ bad_url = 'ftp://vimeo.com/123456789'
495
+ importer.validate_external_media_type_vimeo({'url': bad_url}, importer.external_media_sheet_name, 2)
496
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid Vimeo URL (scheme must be http/https): ftp://vimeo.com/123456789', importer.errors)
497
+
498
+ # No video id
499
+ bad_url = 'https://vimeo.com/'
500
+ importer.validate_external_media_type_vimeo({'url': bad_url}, importer.external_media_sheet_name, 2)
501
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid Vimeo URL or video id not found: https://vimeo.com/', importer.errors)
502
+
503
+ @test_settings
504
+ def test_validate_external_media_type_image_url_extension(self):
505
+ importer = self.get_zip_importer()
506
+ importer.load_workbook()
507
+ importer.errors = []
508
+
509
+ # Valid image URL
510
+ importer.validate_external_media_type_image({'url': 'https://example.com/pic.jpg'}, importer.external_media_sheet_name, 2)
511
+ # Invalid image URL extension
512
+ importer.validate_external_media_type_image({'url': 'https://example.com/pic.bmp'}, importer.external_media_sheet_name, 2)
513
+
514
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid image format in URL: .bmp. Valid formats are: .jpg, .jpeg, .png, .webp, .gif', importer.errors)
515
+
516
+ @test_settings
517
+ def test_validate_external_media_type_audio_pdf(self):
518
+ importer = self.get_zip_importer()
519
+ importer.load_workbook()
520
+ importer.errors = []
521
+
522
+ # mp3
523
+ importer.validate_external_media_type_mp3({'url': 'https://example.com/audio.txt'}, importer.external_media_sheet_name, 2)
524
+ importer.validate_external_media_type_mp3({'url': 'https://example.com/audio.mp3'}, importer.external_media_sheet_name, 2)
525
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid mp3 format in URL: https://example.com/audio.txt. URL has to end with .mp3', importer.errors)
526
+
527
+ # wav
528
+ importer.validate_external_media_type_wav({'url': 'https://example.com/audio.txt'}, importer.external_media_sheet_name, 2)
529
+ importer.validate_external_media_type_wav({'url': 'https://example.com/audio.wav'}, importer.external_media_sheet_name, 2)
530
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid wav format in URL: https://example.com/audio.txt. URL has to end with .wav', importer.errors)
531
+
532
+ # pdf
533
+ importer.validate_external_media_type_pdf({'url': 'https://example.com/doc.txt'}, importer.external_media_sheet_name, 2)
534
+ importer.validate_external_media_type_pdf({'url': 'https://example.com/doc.pdf'}, importer.external_media_sheet_name, 2)
535
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid pdf format in URL: https://example.com/doc.txt. URL has to end with .pdf', importer.errors)
536
+
537
+ @test_settings
538
+ def test_validate_external_media_type_website(self):
539
+ importer = self.get_zip_importer()
540
+ importer.load_workbook()
541
+ importer.errors = []
542
+
543
+ # Invalid: ends with file extension
544
+ importer.validate_external_media_type_website({'url': 'https://example.com/image.jpg'}, importer.external_media_sheet_name, 2)
545
+ # Valid: no file extension
546
+ importer.validate_external_media_type_website({'url': 'https://example.com/some/path?query=x'}, importer.external_media_sheet_name, 2)
547
+ # Valid: domain-only with TLD
548
+ importer.validate_external_media_type_website({'url': 'https://example.com'}, importer.external_media_sheet_name, 2)
549
+
550
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid website format in URL: https://example.com/image.jpg. URL should not end with a file extension.', importer.errors)
551
+
552
+ @test_settings
553
+ def test_validate_external_media_type_file(self):
554
+ importer = self.get_zip_importer()
555
+ importer.load_workbook()
556
+ importer.errors = []
557
+
558
+ # Invalid: no file extension
559
+ importer.validate_external_media_type_file({'url': 'https://example.com/download/'}, importer.external_media_sheet_name, 2)
560
+ # Valid: has file extension
561
+ importer.validate_external_media_type_file({'url': 'https://example.com/download.zip'}, importer.external_media_sheet_name, 2)
562
+
563
+ self.assertIn('[Generic Content.xlsx][Sheet:External Media][cell:A3] Invalid file format in URL: https://example.com/download/. URL has to end with a file extension.', importer.errors)
424
564
 
425
565
  # real world test
426
566
  # algaebase entry is : Desmarestia viridis (O.F.Müller) J.V.Lamouroux 1813
@@ -587,4 +727,7 @@ class TestGenericContentZipImporterInvalidData(WithUser, WithMetaApp, TenantTest
587
727
  ignorin_importer.load_workbook()
588
728
  ignorin_importer.errors = []
589
729
  ignorin_importer.validate_listing_in_images_sheet('unlisted.jpg', 'A', 2)
590
- self.assertEqual(ignorin_importer.errors, [])
730
+ self.assertEqual(ignorin_importer.errors, [])
731
+
732
+
733
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localcosmos_app_kit
3
- Version: 0.9.16
3
+ Version: 0.9.17
4
4
  Summary: LocalCosmos App Kit. Web Portal to build Android and iOS apps
5
5
  Home-page: https://github.com/localcosmos/app-kit
6
6
  Author: Thomas Uher
@@ -14,7 +14,7 @@ Classifier: Operating System :: OS Independent
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENCE
17
- Requires-Dist: localcosmos-server==0.24.10
17
+ Requires-Dist: localcosmos-server==0.24.11
18
18
  Requires-Dist: localcosmos-cordova-builder==0.9.6
19
19
  Requires-Dist: django-tenants==3.7.0
20
20
  Requires-Dist: django-cleanup==9.0.0
@@ -7,7 +7,7 @@ app_kit/definitions.py,sha256=qPfyqMKr71qvBgOj8M7j_7ZbMlFo5sdvfQUUAL0IBBU,82
7
7
  app_kit/forms.py,sha256=09xz8t7j7opzAXkBAkSjpjAUFygXHXsSjD62515gRxI,20281
8
8
  app_kit/generic.py,sha256=NZDUpyX9Il3NN-kIXwDJGd30s_keRjt2dlAXHEuWfNE,5224
9
9
  app_kit/generic_content_validation.py,sha256=d9zM35OaOV382GMRSnIBQXzRcqiPDIaE03OAWtoHgQ4,1146
10
- app_kit/generic_content_zip_import.py,sha256=LOkLgSNHEZDJHqa2P2ngRVinFefPGQFWKyEZyf4A6uQ,38495
10
+ app_kit/generic_content_zip_import.py,sha256=sVIo5KQeohVTyWMbEyIlr5DDhQ1oXaWiMQycMptJ9vM,46523
11
11
  app_kit/global_urls.py,sha256=9y2vQnaD5T4lzma2S0iXWGajZdq5xzq0CsOch7u9SYc,2432
12
12
  app_kit/middleware.py,sha256=tav9GDjZlGVsxCsIXUb6Tkibrc1nGnNIK3ScZanDBE8,1278
13
13
  app_kit/models.py,sha256=o3JqclCHD64h7Pg3MVX45pZDLYY8EyVT8-83d5FJidE,36764
@@ -464,7 +464,7 @@ app_kit/features/taxon_profiles/tests/common.py,sha256=r27zMWP3LhUCLNv2nSnbLZSb2
464
464
  app_kit/features/taxon_profiles/tests/test_forms.py,sha256=tQ4NHGxZz2dzae16tu0JXazTqEskzdv473Ni_ncUaUA,16374
465
465
  app_kit/features/taxon_profiles/tests/test_models.py,sha256=-gj-EHBWuFG1mxoOMnwSY2lo0azevO00p0yS-txKcHI,33605
466
466
  app_kit/features/taxon_profiles/tests/test_views.py,sha256=8jgvwNe2j79CcoR-FXBI6x9ZnzzxhZLTbX_LGZDgauQ,85993
467
- app_kit/features/taxon_profiles/tests/test_zip_import.py,sha256=IkW_Of1Dgb3j0aIKoWsUMQajPAX2aQeR31vCUn7N3d4,27185
467
+ app_kit/features/taxon_profiles/tests/test_zip_import.py,sha256=NS5yAOgpedod8qkAMA47NF-Xau_N23kEI5jw88Ee0TE,27217
468
468
  app_kit/features/taxon_profiles/tests/__pycache__/common.cpython-311.pyc,sha256=VPg2JQrdcN4hlBj-fGZvvyBb1F79FRk9QI5Wd3HlN04,2573
469
469
  app_kit/features/taxon_profiles/tests/__pycache__/common.cpython-313.pyc,sha256=mZa0QDG_CtG8ZTIiQMWSqeBuRzSo8kKqAumsGzZUEus,2397
470
470
  app_kit/features/taxon_profiles/tests/__pycache__/test_forms.cpython-311.pyc,sha256=KNorIkGEM4kigyBcxo-1mNhiTVcFMOMA5khcVuTCSuE,16758
@@ -473,7 +473,7 @@ app_kit/features/taxon_profiles/tests/__pycache__/test_models.cpython-311.pyc,sh
473
473
  app_kit/features/taxon_profiles/tests/__pycache__/test_models.cpython-313.pyc,sha256=Yh5pGcitUJjxGCfpgLknzn_9pBxK2iBoHXz8jXmtQyY,37911
474
474
  app_kit/features/taxon_profiles/tests/__pycache__/test_views.cpython-311.pyc,sha256=LP0Ax_996RBcO7iKOAXLfuPYBkescBUMQage7UNx6eU,94047
475
475
  app_kit/features/taxon_profiles/tests/__pycache__/test_views.cpython-313.pyc,sha256=v10_fLVPmVHlQusn-wEobwC5JstZLzfnevMJB7k9VkI,124656
476
- app_kit/features/taxon_profiles/tests/__pycache__/test_zip_import.cpython-313.pyc,sha256=nfMAbWoVuc43-Gn2dHkmGbvu1J0mppy_9X82vXDUsGw,35276
476
+ app_kit/features/taxon_profiles/tests/__pycache__/test_zip_import.cpython-313.pyc,sha256=wstYyGH-rg8nev8rgokM40R3A4ZgRdxAr_FUACsYTCQ,35276
477
477
  app_kit/locale/de/LC_MESSAGES/django.mo,sha256=tSX2dudjFlnL9Vgr8ueLZQ3AxyAzFR6vQtkSRTd3FTs,89194
478
478
  app_kit/locale/de/LC_MESSAGES/django.po,sha256=4gLbkP8aCbPWx4stAUFce2OadMQtk_CcNnXdE8MMXnQ,179096
479
479
  app_kit/locale/en/LC_MESSAGES/django.mo,sha256=N1pb17IfLd0ASiKO8d68-B4ygSpDkhKOCs8YTzMXQo0,380
@@ -1757,7 +1757,7 @@ app_kit/taxonomy/models.py,sha256=3d5RCnD3OdcnEkGaXhP_HvCBCEz20tq2qWbuBOyYV2M,10
1757
1757
  app_kit/taxonomy/signals.py,sha256=FZHsEUkWN0h7rQN22e8I8MTeP6HfBAzChBTko2PQEHE,1193
1758
1758
  app_kit/taxonomy/urls.py,sha256=q7kE4C7cvgnKH40cUJ1q26MeuAmS7w5fFIJfQiIAgTo,668
1759
1759
  app_kit/taxonomy/utils.py,sha256=Yqu6vuZk3JuUWFKQo9wMS1hhzem5m43SwWKOQmzec64,3181
1760
- app_kit/taxonomy/views.py,sha256=QQtfVkTgskISOh-nad7irG6dP47jG79AufPF_bkepms,6518
1760
+ app_kit/taxonomy/views.py,sha256=B--5qstiJcUK2SzVFN6nDX87yLFMqX02God3N-W465I,6544
1761
1761
  app_kit/taxonomy/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1762
1762
  app_kit/taxonomy/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1763
1763
  app_kit/taxonomy/management/commands/taxonomy_recreate_indices.py,sha256=KiK_Wh7lF01Z03jrWz692aTCfDQhEU98CoTDTg-UTjk,7625
@@ -1792,7 +1792,7 @@ app_kit/taxonomy/sources/col/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
1792
1792
  app_kit/taxonomy/sources/custom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1793
1793
  app_kit/taxonomy/sources/custom/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
1794
1794
  app_kit/taxonomy/sources/custom/apps.py,sha256=0pJS9BI3enVGwVS-WVMv1LTq5KjOT6QHZ0IaJQVpPw4,104
1795
- app_kit/taxonomy/sources/custom/forms.py,sha256=aPpEIytg4xuIMo0RDUlkCeH2cLE3cayETrmAvB_JW_A,2576
1795
+ app_kit/taxonomy/sources/custom/forms.py,sha256=EQW33U3jJjuBN-tSob0Y3heHECPJ-T4critROShFU00,2611
1796
1796
  app_kit/taxonomy/sources/custom/models.py,sha256=4ExGyy2l_mYnGlsBhLndS7lQL5QSFGbE2JSTqx1tPNw,2140
1797
1797
  app_kit/taxonomy/sources/custom/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
1798
1798
  app_kit/taxonomy/sources/custom/urls.py,sha256=0P71XKmzS7mLeYIMcn0EE6Vi0pRV1fu-l_d317Kpnug,1032
@@ -1885,7 +1885,7 @@ app_kit/tests/cases.py,sha256=OGjZnENc5aIxPB0T2LxZagCD87om0hImU-aDF5WlBU8,2693
1885
1885
  app_kit/tests/common.py,sha256=KxGYN73HxNPyMrJxelXsN0NA5btQkleWoqTu87jUue8,12293
1886
1886
  app_kit/tests/mixins.py,sha256=K0iR7WSgPOKj3nFRDrm6b61EztaRcdunLlf4pEWshVw,18210
1887
1887
  app_kit/tests/test_forms.py,sha256=5gVJHn6FJrucfq5jitv16TNGjmpkXcAdgfjQ2gdxSok,23822
1888
- app_kit/tests/test_generic_content_zip_import.py,sha256=d309mIKMB1hFtwdPCUWyS6f03PFMY0R78cr_7Z1mJ9w,24313
1888
+ app_kit/tests/test_generic_content_zip_import.py,sha256=nmTgtVxtS0PPQbzuWOvup2oLlL-C9DyW3PWKjdb8RfA,31910
1889
1889
  app_kit/tests/test_models.py,sha256=gvTKCpJtNxuOyGgocMIZtLkvO2CnHXJ7jAZgKIcrNNE,48837
1890
1890
  app_kit/tests/test_utils.py,sha256=LQejfDjJw6BRoRUrGjNz0_Ap-1NJhCM6uWyEk8XR9p8,10270
1891
1891
  app_kit/tests/test_views.py,sha256=WKzXwBKlJTtYS2m5oo94DkOUs5cJtwWiJouEM6SkqFQ,85640
@@ -1899,6 +1899,7 @@ app_kit/tests/TESTS_ROOT/images/app-background.jpg,sha256=bmW6cudSfmcRqGSK9P2Sc5
1899
1899
  app_kit/tests/TESTS_ROOT/images/localcosmos-logo.svg,sha256=4-MHj0FjED9eTgmUHnAP-DHCQRqc557HH9akHaoQxs0,409549
1900
1900
  app_kit/tests/TESTS_ROOT/images/test-image-2560-1440.jpg,sha256=BOP-1ZlA9wq-E1sUkOpyqUowpFCdSoF965oKgajjSq0,156278
1901
1901
  app_kit/tests/TESTS_ROOT/ipa_for_tests/TestApp.ipa,sha256=YdGWqMepeBwvpkch3DSVMlnNkf8RuGwENWuwHtoY1Do,78900
1902
+ app_kit/tests/TESTS_ROOT/media_for_tests/test/imagestore/31/a6a11b61d65ee19c4c22caa0682288ff.jpg,sha256=dXT6yUqaMJ_91rwQMHT22VjDGb69KRCmm4eboxjtnxM,49349
1902
1903
  app_kit/tests/TESTS_ROOT/templates/neobiota.html,sha256=3ag2VGCuaZ-FoAu25GMUb3s4qMsY7AyhNoDf7CSZJ_w,147
1903
1904
  app_kit/tests/TESTS_ROOT/xlsx_for_testing/BackboneTaxonomy/invalid/Backbone taxonomy.xlsx,sha256=cMkmCtGBR1wwhpDSbjrscNSvLMGnb6YbaliQqws-l7M,8121
1904
1905
  app_kit/tests/TESTS_ROOT/xlsx_for_testing/BackboneTaxonomy/invalid_content_type/Backbone taxonomy.xlsx,sha256=aYRrPI-UABxQUZF-bWvbrD_31ZQgEijdKCzBGOXzGRk,7996
@@ -1928,14 +1929,14 @@ app_kit/tests/__pycache__/common.cpython-313.pyc,sha256=29krHmMxnGBzLReM-dBcU1cR
1928
1929
  app_kit/tests/__pycache__/mixins.cpython-311.pyc,sha256=BpQ9iHqtXMRgIqYh79R-qCPvV7mCxFZD16NTHrkzBLo,30160
1929
1930
  app_kit/tests/__pycache__/mixins.cpython-313.pyc,sha256=4vbTAvCLui4d_00inAU4A01awXhCfVjvXaC4hI6sx9Q,27548
1930
1931
  app_kit/tests/__pycache__/test_forms.cpython-311.pyc,sha256=5EkfnDI_YzuHGlq-Bse7C7XGZi32GgfTzbMg1fY87pI,37496
1931
- app_kit/tests/__pycache__/test_generic_content_zip_import.cpython-313.pyc,sha256=qqzff_gvP29eYod6WNQLyy0g9XjP0Xjo0T61Zn-AV2E,29539
1932
+ app_kit/tests/__pycache__/test_generic_content_zip_import.cpython-313.pyc,sha256=G-U45KVf1TwXsMIfzOBntFEJo5QZFvSxpZxaCrZJT0Q,38945
1932
1933
  app_kit/tests/__pycache__/test_models.cpython-311.pyc,sha256=4cb_Zopv6eCuBpVnv-8mIue3uUntAiDxbJZxzFMMDCw,75511
1933
1934
  app_kit/tests/__pycache__/test_models.cpython-313.pyc,sha256=Lmv3BfjLs5Fg-olJeMll5l3f5-hf-X-9fYPhjuXmd-4,82590
1934
1935
  app_kit/tests/__pycache__/test_utils.cpython-313.pyc,sha256=GX3REqZygi2eO_A2F2_KtYi7hg54X5QPtCTCGWuxGpM,14054
1935
1936
  app_kit/tests/__pycache__/test_views.cpython-311.pyc,sha256=NDJR40TcMm-bXXC-wV7OgH1sGR3N7psSWYiUirkkrjU,133242
1936
1937
  app_kit/tests/__pycache__/test_views.cpython-313.pyc,sha256=q851UqIZFCCTfQb1lF4SVxV1j_Vu1hJdOlpckmrGX28,125363
1937
- localcosmos_app_kit-0.9.16.dist-info/licenses/LICENCE,sha256=VnxALPSxXoU59rlNeRdJtwS_nU79IFpVWsZZCQUM4Mw,1086
1938
- localcosmos_app_kit-0.9.16.dist-info/METADATA,sha256=dFda2jrSES7m8CBqR8DaaQnUQIfY-n_-Jseazqzr--Y,1388
1939
- localcosmos_app_kit-0.9.16.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
1940
- localcosmos_app_kit-0.9.16.dist-info/top_level.txt,sha256=F6H4pEBkCvUR_iwQHIy4K1iby-jzfWg3CTym5XJKeys,8
1941
- localcosmos_app_kit-0.9.16.dist-info/RECORD,,
1938
+ localcosmos_app_kit-0.9.17.dist-info/licenses/LICENCE,sha256=VnxALPSxXoU59rlNeRdJtwS_nU79IFpVWsZZCQUM4Mw,1086
1939
+ localcosmos_app_kit-0.9.17.dist-info/METADATA,sha256=e8ksAKCmQ153iA3vmv2B_1ILF-cHvCiN4JbKU19a1XA,1388
1940
+ localcosmos_app_kit-0.9.17.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
1941
+ localcosmos_app_kit-0.9.17.dist-info/top_level.txt,sha256=F6H4pEBkCvUR_iwQHIy4K1iby-jzfWg3CTym5XJKeys,8
1942
+ localcosmos_app_kit-0.9.17.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5