admin.py 4.44 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
def validate_uploaded_archive(uploaded_file):
Thom Wiggers's avatar
Thom Wiggers committed
17
18
    types = ['application/gzip', 'application/zip',
             'application/x-gzip']
19
    if magic.from_buffer(uploaded_file.read(), mime=True) not in types:
20
21
22
        raise ValidationError("Only zip and tar files are allowed.")


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

    # 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)

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

    class Meta:
        exclude = ['dirname']

43

44
45
46
47
48
49
50
51
52
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:
53
        raise TypeError("'photo' must be a ZipInfo or TarInfo object.")
54
55
56
57
58

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

59
60
61
62
63
    # Generate unique filename
    num = album.photo_set.count()
    _, extension = os.path.splitext(photo_filename)
    new_filename = str(num).zfill(4) + extension

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

75
76
77
78
79
80
        if Photo.objects.filter(album=album, _digest=photo_obj._digest)\
                        .exclude(pk=photo_obj.pk).exists():
            messages.add_message(request, messages.WARNING,
                                 "{} is duplicate.".format(photo_filename))
            photo_obj.delete()

81

82
class AlbumAdmin(admin.ModelAdmin):
83
84
85
86
    list_display = ('title', 'date', 'hidden', 'shareable')
    search_fields = ('title', 'date')
    list_filter = ('hidden', 'shareable')
    date_hierarchy = 'date'
87
88
89
    prepopulated_fields = {'slug': ('date', 'title',)}
    form = AlbumForm

90
91
    def save_model(self, request, obj, form, change):
        obj.save()
92

93
        archive = form.cleaned_data.get('album_archive', None)
94
95
        if archive is None:
            return
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

        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:
111
112
                raise ValueError(_("The uploaded file is not a zip or tar "
                                 "file."))
113

114
        messages.add_message(request, messages.WARNING,
115
116
                             _("Full-sized photos will not be saved "
                               "on the Thalia-website."))
117
118
119


class PhotoAdmin(admin.ModelAdmin):
120
    list_display = ('__str__', 'album', 'hidden')
121
122
    search_fields = ('file',)
    list_filter = ('album', 'hidden')
123
    exclude = ('_digest',)
124

125
126
127
    def save_model(self, request, obj, form, change):
        obj.save()
        messages.add_message(request, messages.WARNING,
128
129
                             _("Full-sized photos will not be saved "
                               "on the Thalia-website."))
130

Thom Wiggers's avatar
Thom Wiggers committed
131

132
admin.site.register(Album, AlbumAdmin)
133
admin.site.register(Photo, PhotoAdmin)