diff --git a/TEKDB/TEKDB/admin.py b/TEKDB/TEKDB/admin.py
index 41b7a5b3..a8d542d9 100644
--- a/TEKDB/TEKDB/admin.py
+++ b/TEKDB/TEKDB/admin.py
@@ -6,11 +6,14 @@
from django.utils.html import format_html
from django.utils.translation import ugettext, ugettext_lazy as _
from dal import autocomplete
+from mimetypes import guess_type
+from django.templatetags.static import static
# from moderation.admin import ModerationAdmin
import nested_admin
from ckeditor.widgets import CKEditorWidget
from reversion.admin import VersionAdmin
+from .forms import MediaBulkUploadForm
from .models import *
from TEKDB.settings import ADMIN_SITE_HEADER
@@ -440,6 +443,116 @@ class CitationsAdmin(RecordAdminProxy, RecordModelAdmin):
)
form = CitationsForm
+
+# * Bulk Media Upload Admin
+class MediaBulkUploadAdmin(admin.ModelAdmin):
+ form = MediaBulkUploadForm
+
+ list_display = ('mediabulkname','mediabulkdate','modifiedbydate','enteredbydate',)
+
+ def save_model(self, request, obj, form, change):
+ super().save_model(request, obj, form, change)
+ places = form.cleaned_data.get('places')
+ resources = form.cleaned_data.get('resources')
+ citations = form.cleaned_data.get('citations')
+ activities = form.cleaned_data.get('activities')
+ placeresources = form.cleaned_data.get('placeresources')
+
+ for file in request.FILES.getlist('files'):
+ media_instance = Media(
+ medianame=obj.mediabulkname,
+ mediadescription=obj.mediabulkdescription,
+ mediafile=file,
+ )
+ media_instance.save()
+ obj.mediabulkupload.add(media_instance)
+
+ # Add relationships
+ if places:
+ for place in places:
+ PlacesMediaEvents.objects.create(placeid=place, mediaid=media_instance)
+ if resources:
+ for resource in resources:
+ ResourcesMediaEvents.objects.create(resourceid=resource, mediaid=media_instance)
+ if citations:
+ for citation in citations:
+ MediaCitationEvents.objects.create(citationid=citation, mediaid=media_instance)
+ if activities:
+ for activity in activities:
+ ResourceActivityMediaEvents.objects.create(resourceactivityid=activity, mediaid=media_instance)
+ if placeresources:
+ for placeresource in placeresources:
+ PlaceResourceMediaEvents.objects.create(placeresourceid=placeresource, mediaid=media_instance)
+
+
+ def thumbnail_gallery(self, obj):
+ thumbnails = []
+ for media in obj.mediabulkupload.all():
+ # Guess the MIME type of the file
+ mime_type, _ = guess_type(media.mediafile.url)
+
+ file_name = media.mediafile.name
+
+ if mime_type:
+ if mime_type.startswith('image'):
+ thumbnails.append(format_html(
+ '
'
+ '
'
+ '
{} '
+ '
', media.mediafile.url, file_name))
+ elif mime_type.startswith('video'):
+ thumbnails.append(format_html(
+ ''
+ ''
+ ''
+ 'Your browser does not support the video tag.'
+ ' '
+ '{} '
+ '
', media.mediafile.url, mime_type, file_name))
+ elif mime_type.startswith('audio'):
+ generic_audio_icon = static('assets/audio-x-generic.svg')
+ thumbnails.append(format_html(
+ ''
+ '
'
+ '
{} '
+ '
', generic_audio_icon, file_name))
+ elif mime_type.startswith('text') or 'application/msword' in mime_type or 'application/vnd' in mime_type:
+ generic_doc_icon = static('assets/doc-text.svg')
+ thumbnails.append(format_html(
+ ''
+ '
'
+ '
{} '
+ '
', generic_doc_icon, file_name))
+ else:
+ # For unknown or other file types, show a generic file image
+ generic_file_icon = static('assets/unknown-mail.png')
+ thumbnails.append(format_html(
+ ''
+ '
'
+ '
{} '
+ '
', generic_file_icon, file_name))
+ else:
+ # In case the MIME type could not be determined, use a generic file icon
+ generic_file_icon = static('assets/unknown-mail.png')
+ thumbnails.append(format_html(
+ ''
+ '
'
+ '
{} '
+ '
', generic_file_icon, file_name))
+
+ return format_html(''.join(thumbnails))
+
+ thumbnail_gallery.short_description = 'Thumbnails'
+ readonly_fields = ('thumbnail_gallery',)
+ fieldsets = (
+ (None, {
+ 'fields': ('mediabulkname', 'mediabulkdescription', 'files', 'mediabulkdate', 'places', 'resources', 'citations', 'activities', 'placeresources', 'thumbnail_gallery')
+ }),
+ )
+
+admin.site.register(MediaBulkUpload, MediaBulkUploadAdmin)
+
+
class MediaAdmin(RecordAdminProxy, RecordModelAdmin):
readonly_fields = ('medialink',
'enteredbyname', 'enteredbytribe','enteredbytitle','enteredbydate',
diff --git a/TEKDB/TEKDB/forms.py b/TEKDB/TEKDB/forms.py
new file mode 100644
index 00000000..c53a696d
--- /dev/null
+++ b/TEKDB/TEKDB/forms.py
@@ -0,0 +1,35 @@
+from django import forms
+from .models import MediaBulkUpload, Media, Places, Resources, Citations, ResourcesActivityEvents, PlacesResourceMediaEvents
+from .widgets import ThumbnailFileInput
+
+class MultipleFileInput(forms.ClearableFileInput):
+ allow_multiple_selected = True
+
+class MultipleFileField(forms.FileField):
+ def __init__(self, *args, **kwargs):
+ kwargs.setdefault("widget", MultipleFileInput())
+ super().__init__(*args, **kwargs)
+
+ def clean(self, data, initial=None):
+ single_file_clean = super().clean
+ if isinstance(data, (list, tuple)):
+ result = [single_file_clean(d, initial) for d in data]
+ else:
+ result = [single_file_clean(data, initial)]
+ return result
+
+# class MultipleFileField(forms.FileField):
+# widget = ThumbnailFileInput
+
+class MediaBulkUploadForm(forms.ModelForm):
+ files = MultipleFileField()
+ mediabulkdate = forms.DateField(widget=forms.SelectDateWidget)
+ places = forms.ModelMultipleChoiceField(queryset=Places.objects.all(), required=False)
+ resources = forms.ModelMultipleChoiceField(queryset=Resources.objects.all(), required=False)
+ citations = forms.ModelMultipleChoiceField(queryset=Citations.objects.all(), required=False)
+ activities = forms.ModelMultipleChoiceField(queryset=ResourcesActivityEvents.objects.all(), required=False)
+ placeresources = forms.ModelMultipleChoiceField(queryset=PlacesResourceMediaEvents.objects.all(), required=False)
+
+ class Meta:
+ model = MediaBulkUpload
+ fields = ['mediabulkname', 'mediabulkdescription', 'files', 'mediabulkdate', 'places', 'resources', 'citations', 'activities', 'placeresources']
\ No newline at end of file
diff --git a/TEKDB/TEKDB/migrations/0013_auto_20241004_1547.py b/TEKDB/TEKDB/migrations/0013_auto_20241004_1547.py
new file mode 100644
index 00000000..750cb883
--- /dev/null
+++ b/TEKDB/TEKDB/migrations/0013_auto_20241004_1547.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.2.25 on 2024-10-04 22:47
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('Lookup', '0005_alter_lookupuserinfo_id'),
+ ('TEKDB', '0012_auto_20240926_1440'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MediaBulkUpload',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(blank=True, max_length=255, null=True)),
+ ('description', models.TextField(blank=True, null=True)),
+ ('date', models.DateField(blank=True, default=None, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True, null=True)),
+ ('updated_at', models.DateTimeField(auto_now=True, null=True)),
+ ('user', models.ForeignKey(blank=True, db_column='user', default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='Lookup.lookupuserinfo', verbose_name='user')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='media',
+ name='mediauploadevent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mediabulkupload', to='TEKDB.mediabulkupload'),
+ ),
+ ]
diff --git a/TEKDB/TEKDB/migrations/0014_alter_media_mediauploadevent.py b/TEKDB/TEKDB/migrations/0014_alter_media_mediauploadevent.py
new file mode 100644
index 00000000..0b511fb9
--- /dev/null
+++ b/TEKDB/TEKDB/migrations/0014_alter_media_mediauploadevent.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.25 on 2024-10-04 22:55
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('TEKDB', '0013_auto_20241004_1547'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='media',
+ name='mediauploadevent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mediauploadevent', to='TEKDB.mediabulkupload'),
+ ),
+ ]
diff --git a/TEKDB/TEKDB/migrations/0015_auto_20241004_1559.py b/TEKDB/TEKDB/migrations/0015_auto_20241004_1559.py
new file mode 100644
index 00000000..8ecc5443
--- /dev/null
+++ b/TEKDB/TEKDB/migrations/0015_auto_20241004_1559.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.25 on 2024-10-04 22:59
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('TEKDB', '0014_alter_media_mediauploadevent'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='media',
+ name='mediauploadevent',
+ ),
+ migrations.AddField(
+ model_name='media',
+ name='mediabulkupload',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mediabulkupload', to='TEKDB.mediabulkupload'),
+ ),
+ ]
diff --git a/TEKDB/TEKDB/migrations/0016_auto_20241016_1549.py b/TEKDB/TEKDB/migrations/0016_auto_20241016_1549.py
new file mode 100644
index 00000000..823f0def
--- /dev/null
+++ b/TEKDB/TEKDB/migrations/0016_auto_20241016_1549.py
@@ -0,0 +1,71 @@
+# Generated by Django 3.2.25 on 2024-10-16 22:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('TEKDB', '0015_auto_20241004_1559'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='mediabulkupload',
+ name='created_at',
+ ),
+ migrations.RemoveField(
+ model_name='mediabulkupload',
+ name='updated_at',
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='enteredbydate',
+ field=models.DateTimeField(auto_now_add=True, db_column='enteredbydate', null=True, verbose_name='entered by date'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='enteredbyname',
+ field=models.CharField(blank=True, db_column='enteredbyname', max_length=25, null=True, verbose_name='entered by name'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='enteredbytitle',
+ field=models.CharField(blank=True, db_column='enteredbytitle', max_length=100, null=True, verbose_name='entered by title'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='enteredbytribe',
+ field=models.CharField(blank=True, db_column='enteredbytribe', max_length=100, null=True, verbose_name='entered by tribe'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='modifiedbydate',
+ field=models.DateTimeField(auto_now=True, db_column='modifiedbydate', null=True, verbose_name='modified by date'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='modifiedbyname',
+ field=models.CharField(blank=True, db_column='modifiedbyname', max_length=25, null=True, verbose_name='modified by name'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='modifiedbytitle',
+ field=models.CharField(blank=True, db_column='modifiedbytitle', max_length=100, null=True, verbose_name='modified by title'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='modifiedbytribe',
+ field=models.CharField(blank=True, db_column='modifiedbytribe', max_length=100, null=True, verbose_name='modified by tribe'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='needsReview',
+ field=models.BooleanField(db_column='needsreview', default=True, verbose_name='Needs Review'),
+ ),
+ migrations.AddField(
+ model_name='mediabulkupload',
+ name='researchComments',
+ field=models.TextField(blank=True, db_column='researchcomments', default=None, null=True, verbose_name='Research Comments'),
+ ),
+ ]
diff --git a/TEKDB/TEKDB/migrations/0017_auto_20241016_1552.py b/TEKDB/TEKDB/migrations/0017_auto_20241016_1552.py
new file mode 100644
index 00000000..d1835fd1
--- /dev/null
+++ b/TEKDB/TEKDB/migrations/0017_auto_20241016_1552.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.2.25 on 2024-10-16 22:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('TEKDB', '0016_auto_20241016_1549'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='mediabulkupload',
+ old_name='date',
+ new_name='mediabulkdate',
+ ),
+ migrations.RenameField(
+ model_name='mediabulkupload',
+ old_name='description',
+ new_name='mediabulkdescription',
+ ),
+ migrations.RenameField(
+ model_name='mediabulkupload',
+ old_name='name',
+ new_name='mediabulkname',
+ ),
+ migrations.RemoveField(
+ model_name='mediabulkupload',
+ name='user',
+ ),
+ ]
diff --git a/TEKDB/TEKDB/models.py b/TEKDB/TEKDB/models.py
index 75b729a2..b461b4c7 100644
--- a/TEKDB/TEKDB/models.py
+++ b/TEKDB/TEKDB/models.py
@@ -512,10 +512,30 @@ def get_related_objects(self, object_id):
]
def __unicode__(self):
- return unicode('%s (%s)' % (self.indigenousplacename, self.englishplacename))
+ indigenous = self.indigenousplacename or ""
+ english = self.englishplacename or ""
+
+ if indigenous and english:
+ return u'%s (%s)' % (indigenous, english)
+ elif indigenous:
+ return unicode(indigenous)
+ elif english:
+ return unicode(english)
+ else:
+ return u'No Name Given'
def __str__(self):
- return "%s (%s)" % (self.indigenousplacename, self.englishplacename) or ''
+ indigenous = self.indigenousplacename or ""
+ english = self.englishplacename or ""
+
+ if indigenous and english:
+ return "%s (%s)" % (indigenous, english)
+ elif indigenous:
+ return indigenous
+ elif english:
+ return english
+ else:
+ return "No Name Given"
class LookupResourceGroup(DefaultModeratedModel, ModeratedModel):
id = models.AutoField(db_column='id', primary_key=True)
@@ -1830,6 +1850,20 @@ def __unicode__(self):
def __str__(self):
return self.username or ''
+
+# * Bulk Media Upload
+# formerly known as Media Collection
+class MediaBulkUpload(Reviewable, Queryable, Record, ModeratedModel):
+ mediabulkname = models.CharField(max_length=255, blank=True, null=True)
+ mediabulkdescription = models.TextField(blank=True, null=True)
+ mediabulkdate = models.DateField(blank=True, null=True, default=None)
+
+ # @property
+ # def count(self):
+ # number of media items uploaded
+
+ # Ability to edit Media
+
class Media(Reviewable, Queryable, Record, ModeratedModel):
mediaid = models.AutoField(db_column='mediaid', primary_key=True)
mediatype = models.ForeignKey(LookupMediaType, db_column='mediatype', max_length=255, blank=True, null=True, verbose_name='type', default=None, on_delete=models.SET_DEFAULT)
@@ -1839,6 +1873,9 @@ class Media(Reviewable, Queryable, Record, ModeratedModel):
mediafile = models.FileField(db_column='mediafile', max_length=255, blank=True, null=True, verbose_name='file')
limitedaccess = models.BooleanField(db_column='limitedaccess', null=True, default=False, verbose_name='limited access?')
+ # * Media Bulk Upload Event
+ mediabulkupload = models.ForeignKey(MediaBulkUpload, related_name='mediabulkupload', blank=True, null=True, on_delete=models.SET_NULL)
+
class Meta:
managed = MANAGED
db_table = 'media'
@@ -2880,4 +2917,4 @@ def save(self, *args, **kwargs):
self.is_superuser = True
else:
self.is_superuser = False
- super(Users, self).save(*args, **kwargs)
+ super(Users, self).save(*args, **kwargs)
\ No newline at end of file
diff --git a/TEKDB/TEKDB/static/assets/audio-x-generic-symbolic.svg b/TEKDB/TEKDB/static/assets/audio-x-generic-symbolic.svg
new file mode 100644
index 00000000..e62ffdf8
--- /dev/null
+++ b/TEKDB/TEKDB/static/assets/audio-x-generic-symbolic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/TEKDB/TEKDB/static/assets/doc-text.svg b/TEKDB/TEKDB/static/assets/doc-text.svg
new file mode 100644
index 00000000..0993a48a
--- /dev/null
+++ b/TEKDB/TEKDB/static/assets/doc-text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/TEKDB/TEKDB/static/assets/unknown-mail.png b/TEKDB/TEKDB/static/assets/unknown-mail.png
new file mode 100644
index 00000000..7620a662
Binary files /dev/null and b/TEKDB/TEKDB/static/assets/unknown-mail.png differ
diff --git a/TEKDB/TEKDB/static/assets/video-x-generic.svg b/TEKDB/TEKDB/static/assets/video-x-generic.svg
new file mode 100644
index 00000000..a208932d
--- /dev/null
+++ b/TEKDB/TEKDB/static/assets/video-x-generic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/TEKDB/TEKDB/templates/admin/MediaCollectionForm.html b/TEKDB/TEKDB/templates/admin/MediaCollectionForm.html
new file mode 100644
index 00000000..6f3dc194
--- /dev/null
+++ b/TEKDB/TEKDB/templates/admin/MediaCollectionForm.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/TEKDB/TEKDB/templates/widgets/thumbnail_file_input.html b/TEKDB/TEKDB/templates/widgets/thumbnail_file_input.html
new file mode 100644
index 00000000..04ba4993
--- /dev/null
+++ b/TEKDB/TEKDB/templates/widgets/thumbnail_file_input.html
@@ -0,0 +1,6 @@
+{% if widget.value %}
+
+ {{ widget.value }}
+
+{% endif %}
+
\ No newline at end of file
diff --git a/TEKDB/TEKDB/views.py b/TEKDB/TEKDB/views.py
index 1b826633..260251d7 100644
--- a/TEKDB/TEKDB/views.py
+++ b/TEKDB/TEKDB/views.py
@@ -9,7 +9,7 @@
from django.db.models import Q
from django.db.utils import OperationalError
from django.http import HttpResponse, Http404, FileResponse, JsonResponse
-from django.shortcuts import render
+from django.shortcuts import render, redirect
import io
import os
import shutil
diff --git a/TEKDB/TEKDB/widgets.py b/TEKDB/TEKDB/widgets.py
index a2a2d81a..b1ba4568 100644
--- a/TEKDB/TEKDB/widgets.py
+++ b/TEKDB/TEKDB/widgets.py
@@ -59,4 +59,16 @@ class Media:
js = (
'assets/openlayers6/ol.js',
'gis/js/OL6MapPolygonWidget.js',
- )
\ No newline at end of file
+ )
+
+
+from django.forms.widgets import ClearableFileInput
+from django.utils.safestring import mark_safe
+
+class ThumbnailFileInput(ClearableFileInput):
+ template_name = 'widgets/thumbnail_file_input.html'
+
+ def format_value(self, value):
+ if value and hasattr(value, 'url'):
+ return mark_safe(f' ')
+ return super().format_value(value)
\ No newline at end of file
diff --git a/TEKDB/configuration/migrations/0010_alter_configuration_preferredinitialismplacement.py b/TEKDB/configuration/migrations/0010_alter_configuration_preferredinitialismplacement.py
new file mode 100644
index 00000000..f3b087c8
--- /dev/null
+++ b/TEKDB/configuration/migrations/0010_alter_configuration_preferredinitialismplacement.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-09-27 22:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('configuration', '0009_configuration_preferredinitialismplacement'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='configuration',
+ name='preferredInitialismPlacement',
+ field=models.CharField(choices=[('default', 'Default'), ('before', 'Before'), ('after', 'After')], default=('default', 'Default'), help_text='Select the position of the preferred initialism in relative to the logo.', max_length=255),
+ ),
+ ]