Commit cf5f5699 authored by Joost Rijneveld's avatar Joost Rijneveld
Browse files

Merge branch '263-tar-albums' into 'master'

Added support for uploading albums as tar files

Closes #263

See merge request !341
parents 2ffaf11c 86371868
import os
from zipfile import ZipFile
import tarfile
from zipfile import ZipFile, is_zipfile, ZipInfo
from django import forms
from django.conf import settings
from django.contrib import admin
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from .models import Album, Photo
def validate_uploaded_archive(uploaded_file):
types = ['application/gzip', 'application/zip']
if uploaded_file.content_type not in types:
raise ValidationError("Only zip and tar files are allowed.")
class AlbumForm(forms.ModelForm):
# Excuse my french
......@@ -22,45 +29,70 @@ class AlbumForm(forms.ModelForm):
album_archive = forms.FileField(
help_text="Uploading a zip file adds all contained images as photos.",
help_text="Uploading a zip or tar file adds all contained images as "
class Meta:
exclude = ['dirname']
def save_photo(request, archive_file, photo, album):
# zipfile and tarfile are inconsistent
if isinstance(photo, ZipInfo):
photo_filename = photo.filename
extract_file =
elif isinstance(photo, tarfile.TarInfo):
photo_filename =
extract_file = archive_file.extractfile
raise TypeError("'photo' must be a ZipInfo or TarInfo object.")
# Ignore directories
if not os.path.basename(photo_filename):
photo_obj = Photo()
photo_obj.album = album
with extract_file(photo) as f:, ContentFile(
except (OSError, AttributeError):
messages.add_message(request, messages.WARNING,
"Ignoring {}".format(photo_filename))
class AlbumAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('date', 'title',)}
form = AlbumForm
def save_model(self, request, obj, form, change):
archive = form.cleaned_data.get('album_archive', None)
if archive is None:
with ZipFile(archive) as zip_file:
path = os.path.join(settings.MEDIA_ROOT, 'photos', obj.dirname)
os.makedirs(path, exist_ok=True)
# Notably, this can also be used to add photos to existing albums
for photo in zip_file.namelist():
# TODO this may still need to be a bit more robust
# e.g. duplicate names cause overwriting (but are unlikely)
# Flatten any subdirectories
photo_filename = os.path.basename(photo)
# Skip directories (which do not have a basename)
if not photo_filename:
# Cannot use .extract as that would recreate directory paths
photo_obj = Photo()
photo_obj.album = obj
with as f:,
except OSError:
messages.add_message(request, messages.WARNING,
"Ignoring {}".format(
iszipfile = is_zipfile(archive)
if iszipfile:
with ZipFile(archive) as zip_file:
for photo in zip_file.infolist():
save_photo(request, zip_file, photo, obj)
# is_tarfile only supports filenames, so we cannot use that
with as tar_file:
for photo in tar_file.getmembers():
save_photo(request, tar_file, photo, obj)
except tarfile.ReadError:
raise ValueError("The uploaded file is not a zip or tar "
messages.add_message(request, messages.WARNING,
"Full-sized photos will not be saved on the "
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment