Я хочу поблагодарить всех людей, которые предоставили различные варианты решения этой проблемы. У меня были дополнительные требования, в которых я хотел (а) выполнить проверку длины файла в JavaScript перед отправкой, (б) выполнить вторую проверку защиты на сервере в forms.py
, (в) сохранить все жестко закодированные биты, включая конец -пользовательские сообщения в forms.py
, (d) я хотел, чтобы мой views.py
имел как можно меньше кода, связанного с файлами, и (d) загрузил информацию о файле в мою базу данных, поскольку это небольшие файлы, которые я хочу обслуживать вошли в систему и мгновенно удаляют, когда удаляются элементы модели Meal
(т. е. просто перетаскивание их в / media / недостаточно).
Первая модель:
class Meal(models.Model) :
title = models.CharField(max_length=200)
text = models.TextField()
# Picture (you need content type to serve it properly)
picture = models.BinaryField(null=True, editable=True)
content_type = models.CharField(max_length=256, null=True, help_text='The MIMEType of the file')
# Shows up in the admin list
def __str__(self):
return self.title
Затем вам нужна форма, которая выполняет проверку на сервере и преобразование перед сохранением из InMemoryUploadedFile
в bytes
и захватывает Content-Type
для последующего обслуживания.
class CreateForm(forms.ModelForm):
max_upload_limit = 2 * 1024 * 1024
max_upload_limit_text = str(max_upload_limit) # A more natural size would be nice
upload_field_name = 'picture'
# Call this 'picture' so it gets copied from the form to the in-memory model
picture = forms.FileField(required=False, label='File to Upload <= '+max_upload_limit_text)
class Meta:
model = Meal
fields = ['title', 'text', 'picture']
def clean(self) : # Reject if the file is too large
cleaned_data = super().clean()
pic = cleaned_data.get('picture')
if pic is None : return
if len(pic) > self.max_upload_limit:
self.add_error('picture', "File must be < "+self.max_upload_limit_text+" bytes")
def save(self, commit=True) : # Convert uploaded files to bytes
instance = super(CreateForm, self).save(commit=False)
f = instance.picture # Make a copy
if isinstance(f, InMemoryUploadedFile):
bytearr = f.read();
instance.content_type = f.content_type
instance.picture = bytearr # Overwrite with the actual image data
if commit:
instance.save()
return instance
В шаблон добавьте этот код (адаптированный из предыдущего ответа):
<script>
$("#upload_form").submit(function() {
if (window.File && window.FileReader && window.FileList && window.Blob) {
var file = $('#id_{{ form.upload_field_name }}')[0].files[0];
if (file && file.size > {{ form.max_upload_limit }} ) {
alert("File " + file.name + " of type " + file.type + " must be < {{ form.max_upload_limit_text }}");
return false;
}
}
});
</script>
Вот код представления, который обрабатывает и создание, и обновление:
class MealFormView(LoginRequiredMixin, View):
template = 'meal_form.html'
success_url = reverse_lazy('meals')
def get(self, request, pk=None) :
if not pk :
form = CreateForm()
else:
meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
form = CreateForm(instance=meal)
ctx = { 'form': form }
return render(request, self.template, ctx)
def post(self, request, pk=None) :
if not pk:
form = CreateForm(request.POST, request.FILES or None)
else:
meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
form = CreateForm(request.POST, request.FILES or None, instance=meal)
if not form.is_valid() :
ctx = {'form' : form}
return render(request, self.template, ctx)
form.save()
return redirect(self.success_url)
Это очень простое представление, которое гарантирует, что request.FILES передается в
при создании экземпляра. Вы могли бы почти использовать универсальный CreateView, если бы он (а) использовал мою форму и (б) передавал request.files при создании экземпляра модели.
Просто чтобы завершить работу, у меня есть следующее простое представление для потоковой передачи файла:
def stream_file(request, pk) :
meal = get_object_or_404(Meal, id=pk)
response = HttpResponse()
response['Content-Type'] = meal.content_type
response['Content-Length'] = len(meal.picture)
response.write(meal.picture)
return response
Это не заставляет пользователей входить в систему, но я пропустил это, поскольку этот ответ уже слишком длинный.