Ik ben een web-app aan het bouwen in Django. Ik heb een model dat een bestand uploadt, maar ik kan het bestand niet verwijderen. Hier is mijn code:
class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
def delete(self, *args, **kwargs):
# You have to prepare what you need before delete the model
storage, path = self.song.storage, self.song.path
# Delete the model before the file
super(Song, self).delete(*args, **kwargs)
# Delete the file after the model
storage.delete(path)
Vervolgens, in python manage.py shell
doe ik dit:
song = Song.objects.get(pk=1)
song.delete()
Het verwijdert het record uit de database, maar niet het bestand op de server.
Wat kan ik nog meer proberen?
Bedankt!
Antwoord 1, autoriteit 100%
Vóór Django 1.3 werd het bestand automatisch uit het bestandssysteem verwijderd toen u de corresponderende modelinstantie verwijderde. U gebruikt waarschijnlijk een nieuwere Django-versie, dus u zult zelf moeten implementeren om het bestand uit het bestandssysteem te verwijderen.
Eenvoudig op signalen gebaseerd voorbeeld
Mijn voorkeursmethode op het moment van schrijven is een mix van post_delete
en pre_save
signalen, waardoor verouderde bestanden worden verwijderd wanneer corresponderende modellen worden verwijderd of hebben hun bestanden gewijzigd.
Gebaseerd op een hypothetisch MediaFile
-model:
import os
import uuid
from django.db import models
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
class MediaFile(models.Model):
file = models.FileField(_("file"),
upload_to=lambda instance, filename: str(uuid.uuid4()))
# These two auto-delete files from filesystem when they are unneeded:
@receiver(models.signals.post_delete, sender=MediaFile)
def auto_delete_file_on_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `MediaFile` object is deleted.
"""
if instance.file:
if os.path.isfile(instance.file.path):
os.remove(instance.file.path)
@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `MediaFile` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False
new_file = instance.file
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
- Ik denk dat een van de apps die ik een tijdje geleden heb gebouwd deze code in productie heeft gebruikt, maar toch op eigen risico gebruikt.
- Er is bijvoorbeeld een mogelijk gegevensverlies-scenario: uw gegevens kunnen uiteindelijk verwijzen naar een niet-bestaand bestand als uw
save()
-methodeaanroep zich binnen een transactie bevindt die wordt teruggedraaid. Je zou kunnen overwegen om logica voor het verwijderen van bestanden intransaction.on_commit()
in te pakken, in de trant vantransaction.on_commit(lambda: os.remove(old_file.path))
, zoals gesuggereerd in de opmerking van Mikhail.django-cleanup
bibliotheek doet iets in die zin. - Edge case: als uw app een nieuw bestand uploadt en modelinstantie naar het nieuwe bestand verwijst zonder
save()
aan te roepen (bijv. door eenQuerySet
bulksgewijs bij te werken), old-bestand blijft rondslingeren omdat signalen niet worden uitgevoerd. Dit gebeurt niet als je conventionele bestandsverwerkingsmethoden gebruikt. - Codeerstijl: in dit voorbeeld wordt
file
gebruikt als veldnaam, wat geen goede stijl is omdat deze botst met de ingebouwde object-IDfile
.
Aanvulling: periodieke opruiming
Realistisch gezien wilt u misschien ookeen periodieke taak uitvoeren om het opschonen van weesbestanden af te handelen in het geval dat een runtime-fout verhindert dat een bestand wordt verwijderd. Met dat in gedachten, zou je waarschijnlijk helemaal af kunnen komen van signaalbehandelaars en van zo’n taak hetmechanisme maken voor het omgaan met ongevoelige gegevens en niet-zo-grote bestanden.
Hoe dan ook, als u metgevoelige gegevens verwerkt, is het altijd beter om dubbel of driedubbel te controleren of u gegevens in productie altijd tijdig verwijdert om eventuele aansprakelijkheden te voorkomen.
>
Zie ook
-
FieldFile.delete()
in Django 1.11 model veldreferentie (merk op dat het deFieldFile
klasse beschrijft, maar je zou.delete()
direct in het veld:FileField
instantie proxeert naar de corresponderendeFieldFile
instantie, en u krijgt toegang tot zijn methoden alsof het veld’s zijn)Houd er rekening mee dat wanneer een model wordt verwijderd, gerelateerde bestanden niet worden verwijderd. Als u verweesde bestanden moet opschonen, moet u dit zelf doen (bijvoorbeeld met een aangepaste beheeropdracht die handmatig kan worden uitgevoerd of periodiek kan worden uitgevoerd via bijvoorbeeld cron).
-
Waarom Django bestanden niet automatisch verwijdert: vermelding in release-opmerkingen voor Django 1.3
In eerdere Django-versies, toen een modelinstantie met een
FileField
werd verwijderd, namFileField
het op zich om het bestand ook uit de backend-opslag te verwijderen. Dit opende de deur naar verschillende scenario’s voor gegevensverlies, waaronder teruggedraaide transacties en velden op verschillende modellen die naar hetzelfde bestand verwijzen. Wanneer in Django 1.3 een model wordt verwijderd, wordt deFileField
-methodedelete()
niet aangeroepen. Als u verweesde bestanden wilt opschonen, moet u dit zelf doen (bijvoorbeeld met een aangepaste beheeropdracht die handmatig kan worden uitgevoerd of periodiek kan worden uitgevoerd via bijvoorbeeld cron).
Antwoord 2, autoriteit 54%
Probeer django-cleanup, het roept automatisch de verwijdermethode aan op FileField wanneer u het model verwijdert.
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig',
)
Antwoord 3, autoriteit 28%
U kunt het bestand van het bestandssysteem verwijderen door de methode .delete
van het bestandsveld aan te roepen zoals hieronder weergegeven met Django >= 1.10:
obj = Song.objects.get(pk=1)
obj.song.delete()
Antwoord 4, autoriteit 9%
Je kunt ook gewoon de verwijderfunctie van het model overschrijven om te controleren of het bestand bestaat en het verwijderen voordat je de superfunctie aanroept.
import os
class Excel(models.Model):
upload_file = models.FileField(upload_to='/excels/', blank =True)
uploaded_on = models.DateTimeField(editable=False)
def delete(self,*args,**kwargs):
if os.path.isfile(self.upload_file.path):
os.remove(self.upload_file.path)
super(Excel, self).delete(*args,**kwargs)
Antwoord 5, autoriteit 9%
Django 2.x-oplossing:
Het is heel gemakkelijk om bestanden te verwijderen in Django 2. Ik heb de volgende oplossing geprobeerd met Django 2 en SFTP-opslag en ook FTP-OPSLAG, en ik ben er vrij zeker van dat het zal werken met andere opslagmanagers die de delete
-methode hebben geïmplementeerd. (delete
methode is een van de storage
abstracte methodes die het bestand fysiek uit de opslag moet verwijderen!)
Overschrijf de methode delete
van het model zodat de instantie zijn FileFields verwijdert voordat hij zichzelf verwijdert:
class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()
Het werkt voor mij vrij eenvoudig.
Als u wilt controleren of het bestand bestaat voordat u het verwijdert, kunt u storage.exists
gebruiken. bijv. self.song.storage.exists(self.song.name)
retourneert een boolean
die aangeeft of het nummer bestaat. Het ziet er dus zo uit:
def delete(self, using=None, keep_parents=False):
# assuming that you use same storage for all files in this model:
storage = self.song.storage
if storage.exists(self.song.name):
storage.delete(self.song.name)
if storage.exists(self.image.name):
storage.delete(self.image.name)
super().delete()
BEWERKEN (Bovendien):
Zoals @HeyManvermeld, met deze oplossing die Song.objects.all().delete()
verwijdert geen bestanden! Dit gebeurt omdat Song.objects.all().delete()
een verwijderquery uitvoert van Standaardmanager. Dus als u bestanden van een model wilt kunnen verwijderen met behulp van objects
-methoden, moet u een Custom Manager(alleen voor het negeren van de verwijderquery):
class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()
en voor het toewijzen van de CustomManager
aan het model, moet u objects
in uw model initialiseren:
class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
objects = CustomManager() # just add this line of code inside of your model
def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()
Je kunt nu .delete()
gebruiken aan het einde van alle objects
subquery’s. Ik heb de eenvoudigste CustomManager
geschreven, maar je kunt het beter doen door iets terug te sturen over objecten die je hebt verwijderd of wat je maar wilt.
Antwoord 6, autoriteit 3%
Hier is een app die oude bestanden verwijdert wanneer het model is verwijderd of een nieuw bestand is geüpload: django- Smartfields
from django.db import models
from smartfields import fields
class Song(models.Model):
song = fields.FileField(upload_to='/songs/')
image = fields.ImageField(upload_to='/pictures/', blank=True)
Antwoord 7, Autoriteit 2%
@anton strogonoff
Ik mis iets in de code wanneer een bestand verandert, als u een nieuw bestand maakt, een fout genereert, is een nieuw bestand A niet een pad gevonden. Ik heb de functiecode gewijzigd en een try / behalve zin toegevoegd en het werkt goed.
@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""Deletes file from filesystem
when corresponding `MediaFile` object is changed.
"""
if not instance.pk:
return False
try:
old_file = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False
new_file = instance.file
if not old_file == new_file:
try:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
except Exception:
return False
Antwoord 8, Autoriteit 2%
Voor degenen die op zoek zijn naar een antwoord in een nieuwere versie van Django (momenteel 3.1).
Ik heb dit website En het werkte voor mij zonder enige wijzigingen, voeg het gewoon toe in uw models.py
:
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.db import models
""" Only delete the file if no other instances of that model are using it"""
def delete_file_if_unused(model,instance,field,instance_file_field):
dynamic_field = {}
dynamic_field[field.name] = instance_file_field.name
other_refs_exist = model.objects.filter(**dynamic_field).exclude(pk=instance.pk).exists()
if not other_refs_exist:
instance_file_field.delete(False)
""" Whenever ANY model is deleted, if it has a file field on it, delete the associated file too"""
@receiver(post_delete)
def delete_files_when_row_deleted_from_db(sender, instance, **kwargs):
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
instance_file_field = getattr(instance,field.name)
delete_file_if_unused(sender,instance,field,instance_file_field)
""" Delete the file if something else get uploaded in its place"""
@receiver(pre_save)
def delete_files_when_file_changed(sender,instance, **kwargs):
# Don't run on initial save
if not instance.pk:
return
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
#its got a file field. Let's see if it changed
try:
instance_in_db = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
# We are probably in a transaction and the PK is just temporary
# Don't worry about deleting attachments if they aren't actually saved yet.
return
instance_in_db_file_field = getattr(instance_in_db,field.name)
instance_file_field = getattr(instance,field.name)
if instance_in_db_file_field.name != instance_file_field.name:
delete_file_if_unused(sender,instance,field,instance_in_db_file_field)
Antwoord 9
Deze code wordt uitgevoerd telkens als ik een nieuw beeld (veld Logo) upload en controleer of er al een logo bestaat, zo ja, sluit het en verwijder deze van de schijf. Dezelfde procedure kan natuurlijk worden gemaakt in de ontvangerfunctie. Ik hoop dat dit helpt.
# Returns the file path with a folder named by the company under /media/uploads
def logo_file_path(instance, filename):
company_instance = Company.objects.get(pk=instance.pk)
if company_instance.logo:
logo = company_instance.logo
if logo.file:
if os.path.isfile(logo.path):
logo.file.close()
os.remove(logo.path)
return 'uploads/{0}/{1}'.format(instance.name.lower(), filename)
class Company(models.Model):
name = models.CharField(_("Company"), null=False, blank=False, unique=True, max_length=100)
logo = models.ImageField(upload_to=logo_file_path, default='')