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.
- app_kit/features/taxon_profiles/tests/__pycache__/test_zip_import.cpython-313.pyc +0 -0
- app_kit/features/taxon_profiles/tests/test_zip_import.py +4 -0
- app_kit/generic_content_zip_import.py +174 -0
- app_kit/taxonomy/sources/custom/forms.py +1 -0
- app_kit/taxonomy/views.py +1 -1
- app_kit/tests/TESTS_ROOT/media_for_tests/test/imagestore/31/a6a11b61d65ee19c4c22caa0682288ff.jpg +0 -0
- app_kit/tests/__pycache__/test_generic_content_zip_import.cpython-313.pyc +0 -0
- app_kit/tests/test_generic_content_zip_import.py +145 -2
- {localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/METADATA +2 -2
- {localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/RECORD +13 -12
- {localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/WHEEL +1 -1
- {localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/licenses/LICENCE +0 -0
- {localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -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):
|
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()
|
app_kit/tests/TESTS_ROOT/media_for_tests/test/imagestore/31/a6a11b61d65ee19c4c22caa0682288ff.jpg
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -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, '
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
1938
|
-
localcosmos_app_kit-0.9.
|
|
1939
|
-
localcosmos_app_kit-0.9.
|
|
1940
|
-
localcosmos_app_kit-0.9.
|
|
1941
|
-
localcosmos_app_kit-0.9.
|
|
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,,
|
{localcosmos_app_kit-0.9.16.dist-info → localcosmos_app_kit-0.9.17.dist-info}/licenses/LICENCE
RENAMED
|
File without changes
|
|
File without changes
|