admin.py 3.92 KB
Newer Older
1
import magic
2
import os
3
4
import tarfile
from zipfile import ZipFile, is_zipfile, ZipInfo
5

6
from django import forms
7
from django.contrib import admin
8
from django.contrib import messages
9
from django.core.exceptions import ValidationError
10
from django.core.files.base import ContentFile
11
from django.utils.translation import ugettext_lazy as _
12

13
from .models import Album, Photo
14

15

16
17
def validate_uploaded_archive(uploaded_file):
    types = ['application/gzip', 'application/zip']
18
    if magic.from_buffer(uploaded_file.read(), mime=True) not in types:
19
20
21
        raise ValidationError("Only zip and tar files are allowed.")


22
class AlbumForm(forms.ModelForm):
23
24
25
26
27
28
29
30
31

    # Excuse my french
    # https://stackoverflow.com/questions/4391776/django-admin-inline-forms-limit-foreign-key-queryset-to-a-set-of-values#4392047
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if 'instance' in kwargs:
            self.fields['_cover'].queryset = Photo.objects.filter(
                album=self.instance)

32
33
    album_archive = forms.FileField(
        required=False,
34
35
        help_text=_("Uploading a zip or tar file adds all contained images as "
                    "photos."),
36
        validators=[validate_uploaded_archive]
37
    )
38
39
40
41

    class Meta:
        exclude = ['dirname']

42

43
44
45
46
47
48
49
50
51
def save_photo(request, archive_file, photo, album):
    # zipfile and tarfile are inconsistent
    if isinstance(photo, ZipInfo):
        photo_filename = photo.filename
        extract_file = archive_file.open
    elif isinstance(photo, tarfile.TarInfo):
        photo_filename = photo.name
        extract_file = archive_file.extractfile
    else:
52
        raise TypeError("'photo' must be a ZipInfo or TarInfo object.")
53
54
55
56
57
58
59
60
61
62
63
64

    # Ignore directories
    if not os.path.basename(photo_filename):
        return

    photo_obj = Photo()
    photo_obj.album = album
    try:
        with extract_file(photo) as f:
            photo_obj.file.save(photo_filename, ContentFile(f.read()))
    except (OSError, AttributeError):
        messages.add_message(request, messages.WARNING,
65
                             _("Ignoring {}").format(photo_filename))
66
67
68
69
    else:
        photo_obj.save()


70
class AlbumAdmin(admin.ModelAdmin):
71
72
73
74
    list_display = ('title', 'date', 'hidden', 'shareable')
    search_fields = ('title', 'date')
    list_filter = ('hidden', 'shareable')
    date_hierarchy = 'date'
75
76
77
    prepopulated_fields = {'slug': ('date', 'title',)}
    form = AlbumForm

78
79
    def save_model(self, request, obj, form, change):
        obj.save()
80

81
        archive = form.cleaned_data.get('album_archive', None)
82
83
        if archive is None:
            return
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

        iszipfile = is_zipfile(archive)
        archive.seek(0)

        if iszipfile:
            with ZipFile(archive) as zip_file:
                for photo in zip_file.infolist():
                    save_photo(request, zip_file, photo, obj)
        else:
            # is_tarfile only supports filenames, so we cannot use that
            try:
                with tarfile.open(fileobj=archive) as tar_file:
                    for photo in tar_file.getmembers():
                        save_photo(request, tar_file, photo, obj)
            except tarfile.ReadError:
99
100
                raise ValueError(_("The uploaded file is not a zip or tar "
                                 "file."))
101

102
        messages.add_message(request, messages.WARNING,
103
104
                             _("Full-sized photos will not be saved "
                               "on the Thalia-website."))
105
106
107


class PhotoAdmin(admin.ModelAdmin):
108
    list_display = ('__str__', 'album', 'hidden')
109
110
111
    search_fields = ('file',)
    list_filter = ('album', 'hidden')

112
113
114
    def save_model(self, request, obj, form, change):
        obj.save()
        messages.add_message(request, messages.WARNING,
115
116
                             _("Full-sized photos will not be saved "
                               "on the Thalia-website."))
117

118
admin.site.register(Album, AlbumAdmin)
119
admin.site.register(Photo, PhotoAdmin)