Ich möchte allen Leuten danken, die verschiedene Lösungen für dieses Problem bereitgestellt haben. Ich hatte zusätzliche Anforderungen, bei denen ich (a) vor der Übermittlung eine Überprüfung der Dateilänge in JavaScript durchführen wollte, (b) eine zweite Verteidigungslinie auf dem Server durchführen wollte forms.py
, (c) alle fest codierten Bits einschließlich Endbenutzernachrichten beibehalten wollte in forms.py
, (d) Ich wollte, dass ich views.py
so wenig dateibezogenen Code wie möglich habe, und (d) lade die Dateiinformationen in meine Datenbank hoch, da dies kleine Dateien sind, die ich nur für angemeldete Benutzer bereitstellen und beim Meal
Modell sofort löschen möchte Elemente werden gelöscht (dh es reicht nicht aus, sie nur in / media / abzulegen).
Zuerst das Modell:
class Meal(models.Model) :
title = models.CharField(max_length=200)
text = models.TextField()
picture = models.BinaryField(null=True, editable=True)
content_type = models.CharField(max_length=256, null=True, help_text='The MIMEType of the file')
def __str__(self):
return self.title
Dann benötigen Sie ein Formular, das sowohl die In-Server-Validierung als auch die Konvertierung vor InMemoryUploadedFile
dem Speichern von nach bytes
ausführt und das Content-Type
für die spätere Bereitstellung abruft.
class CreateForm(forms.ModelForm):
max_upload_limit = 2 * 1024 * 1024
max_upload_limit_text = str(max_upload_limit)
upload_field_name = 'picture'
picture = forms.FileField(required=False, label='File to Upload <= '+max_upload_limit_text)
class Meta:
model = Meal
fields = ['title', 'text', 'picture']
def clean(self) :
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) :
instance = super(CreateForm, self).save(commit=False)
f = instance.picture
if isinstance(f, InMemoryUploadedFile):
bytearr = f.read();
instance.content_type = f.content_type
instance.picture = bytearr
if commit:
instance.save()
return instance
Fügen Sie in der Vorlage diesen Code hinzu (angepasst an eine vorherige Antwort):
<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>
Hier ist der Ansichtscode, der sowohl Erstellen als auch Aktualisieren behandelt:
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)
Dies ist eine sehr einfache Ansicht, die sicherstellt, dass request.FILES während der Erstellung der Instanz übergeben wird. Sie könnten fast die generische CreateView verwenden, wenn sie (a) mein Formular verwenden und (b) request.files beim Erstellen der Modellinstanz übergeben würde.
Um den Aufwand abzuschließen, habe ich die folgende einfache Ansicht, um die Datei zu streamen:
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
Dies erzwingt nicht, dass Benutzer angemeldet werden, aber ich habe dies weggelassen, da diese Antwort bereits zu lang ist.