Django verwijdert FileField

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 shelldoe 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_deleteen pre_savesignalen, 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 in transaction.on_commit()in te pakken, in de trant van transaction.on_commit(lambda: os.remove(old_file.path)), zoals gesuggereerd in de opmerking van Mikhail. django-cleanupbibliotheek 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 een QuerySetbulksgewijs 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 filegebruikt als veldnaam, wat geen goede stijl is omdat deze botst met de ingebouwde object-ID file.

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 de FieldFileklasse beschrijft, maar je zou .delete()direct in het veld: FileFieldinstantie proxeert naar de corresponderende FieldFileinstantie, 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 FileFieldwerd verwijderd, nam FileFieldhet 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 de FileField-methode delete()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).

  • Voorbeeld van het gebruik van een pre_deletesignaal alleen


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 .deletevan 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. (deletemethode is een van de storageabstracte methodes die het bestand fysiek uit de opslag moet verwijderen!)

Overschrijf de methode deletevan 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.existsgebruiken. bijv. self.song.storage.exists(self.song.name)retourneert een booleandie 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 CustomManageraan het model, moet u objectsin 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 objectssubquery’s. Ik heb de eenvoudigste CustomManagergeschreven, 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='')

Other episodes