admin.py 4.6 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 utils.translation import TranslatedModelAdmin
14
from .models import Album, Photo
15

16

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


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

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

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

    class Meta:
        exclude = ['dirname']

44

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

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

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

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

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

82

83
class AlbumAdmin(TranslatedModelAdmin):
84
    list_display = ('title', 'date', 'hidden', 'shareable')
Joost Rijneveld's avatar
Joost Rijneveld committed
85
    fields = ('title', 'slug', 'date', 'hidden', 'shareable', 'album_archive',
86
              '_cover')
87
88
89
    search_fields = ('title', 'date')
    list_filter = ('hidden', 'shareable')
    date_hierarchy = 'date'
90
    prepopulated_fields = {'slug': ('date', 'title_en',)}
91
92
    form = AlbumForm

93
94
    def save_model(self, request, obj, form, change):
        obj.save()
95

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

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

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


class PhotoAdmin(admin.ModelAdmin):
123
    list_display = ('__str__', 'album', 'hidden')
124
125
    search_fields = ('file',)
    list_filter = ('album', 'hidden')
126
    exclude = ('_digest',)
127

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

Thom Wiggers's avatar
Thom Wiggers committed
134

135
admin.site.register(Album, AlbumAdmin)
136
admin.site.register(Photo, PhotoAdmin)