1 | # -*- coding: utf-8 -*-
|
---|
2 |
|
---|
3 | from django import forms
|
---|
4 | from django.contrib.postgres.fields import ArrayField
|
---|
5 | from django.contrib.postgres.forms import SplitArrayField
|
---|
6 | from django.core.exceptions import ValidationError
|
---|
7 | from django.db.models.fields.files import FieldFile, File
|
---|
8 | from django.utils import six
|
---|
9 | from django.utils.translation import ugettext_lazy as _
|
---|
10 |
|
---|
11 | class MultiFileInput(forms.FileInput):
|
---|
12 |
|
---|
13 | def render(self, name, value, attrs={}):
|
---|
14 | attrs['multiple'] = 'multiple'
|
---|
15 | return super(MultiFileInput, self).render(name, None, attrs=attrs)
|
---|
16 |
|
---|
17 | def value_from_datadict(self, data, files, name):
|
---|
18 | if hasattr(files, 'getlist'):
|
---|
19 | return files.getlist(name)
|
---|
20 | else:
|
---|
21 | return [files.get(name)]
|
---|
22 |
|
---|
23 | class MultiFileField(forms.FileField):
|
---|
24 | widget = MultiFileInput
|
---|
25 | default_error_messages = {
|
---|
26 | 'min_num': u"Ensure at least %(min_num)s files are uploaded (received %(num_files)s).",
|
---|
27 | 'max_num': u"Ensure at most %(max_num)s files are uploaded (received %(num_files)s).",
|
---|
28 | 'file_size' : u"File: %(uploaded_file_name)s, exceeded maximum upload size."
|
---|
29 | }
|
---|
30 |
|
---|
31 | def __init__(self, *args, **kwargs):
|
---|
32 | self.min_num = kwargs.pop('min_num', 0)
|
---|
33 | self.max_num = kwargs.pop('max_num', None)
|
---|
34 | self.maximum_file_size = kwargs.pop('maximum_file_size', None)
|
---|
35 | super(MultiFileField, self).__init__(*args, **kwargs)
|
---|
36 |
|
---|
37 | def to_python(self, data):
|
---|
38 | ret = []
|
---|
39 | for item in data:
|
---|
40 | ret.append(super(MultiFileField, self).to_python(item))
|
---|
41 | return ret
|
---|
42 |
|
---|
43 | def validate(self, data):
|
---|
44 | super(MultiFileField, self).validate(data)
|
---|
45 | num_files = len(data)
|
---|
46 | if len(data) and not data[0]:
|
---|
47 | num_files = 0
|
---|
48 | if num_files < self.min_num:
|
---|
49 | raise ValidationError(self.error_messages['min_num'] % {'min_num': self.min_num, 'num_files': num_files})
|
---|
50 | return
|
---|
51 | elif self.max_num and num_files > self.max_num:
|
---|
52 | raise ValidationError(self.error_messages['max_num'] % {'max_num': self.max_num, 'num_files': num_files})
|
---|
53 | for uploaded_file in data:
|
---|
54 | if self.maximum_file_size and uploaded_file.size > self.maximum_file_size:
|
---|
55 | raise ValidationError(self.error_messages['file_size'] % {'uploaded_file_name': uploaded_file.name})
|
---|
56 |
|
---|
57 | def to_file_object(field, instance, file):
|
---|
58 | if isinstance(file, six.string_types) or file is None:
|
---|
59 | return field.attr_class(instance, field, file)
|
---|
60 | elif isinstance(file, File) and not isinstance(file, FieldFile):
|
---|
61 | file_copy = field.attr_class(instance, field, file.name)
|
---|
62 | file_copy.file = file
|
---|
63 | file_copy._committed = False
|
---|
64 | return file_copy
|
---|
65 | elif isinstance(file, FieldFile) and not hasattr(file, 'field'):
|
---|
66 | file.instance = instance
|
---|
67 | file.field = field
|
---|
68 | file.storage = field.storage
|
---|
69 | return file
|
---|
70 | else:
|
---|
71 | return file
|
---|
72 |
|
---|
73 | class ArrayFileDescriptor(object):
|
---|
74 |
|
---|
75 | def __init__(self, field):
|
---|
76 | self.field = field
|
---|
77 |
|
---|
78 | def __get__(self, instance=None, owner=None):
|
---|
79 | if instance is None:
|
---|
80 | raise AttributeError(
|
---|
81 | "The '%s' attribute can only be accessed from %s instances."
|
---|
82 | % (self.field.name, owner.__name__))
|
---|
83 |
|
---|
84 | return [
|
---|
85 | to_file_object(self.field.base_field, instance, file)
|
---|
86 | for file in instance.__dict__[self.field.name]
|
---|
87 | ]
|
---|
88 |
|
---|
89 | def __set__(self, instance, value):
|
---|
90 | instance.__dict__[self.field.name] = value
|
---|
91 |
|
---|
92 | class ArrayFileField(ArrayField):
|
---|
93 |
|
---|
94 | descriptor_class = ArrayFileDescriptor
|
---|
95 |
|
---|
96 | def set_attributes_from_name(self, name):
|
---|
97 | super(ArrayField, self).set_attributes_from_name(name)
|
---|
98 | self.base_field.set_attributes_from_name("%s_array" % name)
|
---|
99 |
|
---|
100 | def contribute_to_class(self, cls, name, **kwargs):
|
---|
101 | super(ArrayFileField, self).contribute_to_class(cls, name, **kwargs)
|
---|
102 | setattr(cls, self.name, self.descriptor_class(self))
|
---|
103 |
|
---|
104 | def pre_save(self, instance, add):
|
---|
105 | "Returns field's value just before saving."
|
---|
106 | files = [
|
---|
107 | to_file_object(self.base_field, instance, file)
|
---|
108 | for file in super(ArrayField, self).pre_save(instance, add)
|
---|
109 | ]
|
---|
110 |
|
---|
111 | for file_copy in files:
|
---|
112 | if file_copy and not file_copy._committed:
|
---|
113 | file_copy.save(file_copy.name, file_copy, save=False)
|
---|
114 |
|
---|
115 | return files
|
---|
116 |
|
---|
117 | def formfield(self, **kwargs):
|
---|
118 | defaults = {
|
---|
119 | 'form_class': MultiFileField,
|
---|
120 | 'max_num': self.size
|
---|
121 | }
|
---|
122 | defaults.update(kwargs)
|
---|
123 | return super(ArrayField, self).formfield(**defaults)
|
---|