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( + '
' + '' + '
{}' + '
', 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( + '
' + 'Audio File' + '
{}' + '
', 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( + '
' + 'Document File' + '
{}' + '
', 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( + '
' + 'File' + '
{}' + '
', 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( + '
' + 'File' + '
{}' + '
', 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 @@ +
+ {% csrf_token %} + {{ form.as_p }} + +
\ 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), + ), + ]