Buduję aplikację internetową w Django. Mam model, który przesyła plik, ale nie mogę go usunąć. Oto mój kod:
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)
Następnie w „python manage.py shell” robię to:
song = Song.objects.get(pk=1)
song.delete()
Usuwa z bazy danych, ale nie usuwa pliku na serwerze. Czego jeszcze mogę spróbować?
Dzięki!
django
django-models
Marcos Aguayo
źródło
źródło
Odpowiedzi:
Przed Django 1.3 plik był usuwany z systemu plików automatycznie po usunięciu odpowiedniej instancji modelu. Prawdopodobnie używasz nowszej wersji Django, więc będziesz musiał samodzielnie zaimplementować usuwanie pliku z systemu plików.
Możesz to zrobić na kilka sposobów, z których jednym jest użycie sygnału
pre_delete
lubpost_delete
.Przykład
Moją metodą z wyboru jest obecnie mieszanka sygnałów
post_delete
ipre_save
, co sprawia, że przestarzałe pliki są usuwane po każdym usunięciu odpowiednich modeli lub zmianie ich plików.Na podstawie hipotetycznego
MediaFile
modelu: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)
save()
(np. Poprzez zbiorczą aktualizację aQuerySet
), stary plik będzie nadal leżał, ponieważ sygnały nie będą uruchamiane. Nie dzieje się tak, jeśli używasz konwencjonalnych metod obsługi plików.file
nazwa pola jest używana , co nie jest dobrym stylem, ponieważ koliduje z wbudowanymfile
identyfikatorem obiektu.Zobacz też
FieldFile.delete()
w modelu Django 1.11 odwołanie do pola (zwróć uwagę, że opisujeFieldFile
klasę, ale wywołałbyś.delete()
bezpośrednio w polu:FileField
instancje proxy do odpowiedniejFieldFile
instancji i uzyskujesz dostęp do jej metod tak, jakby były polami)Dlaczego Django nie usuwa plików automatycznie: wpis w informacjach o wydaniu dla Django 1.3
Przykład wykorzystania samego
pre_delete
sygnałuźródło
instance.song.delete(save=False)
, ponieważ używa prawidłowego silnika pamięci masowej django.os.path.isfile(old_file.path)
kończy się niepowodzeniem, ponieważold_file.path
powoduje błąd (żaden plik nie jest powiązany z polem). Naprawiłem to, dodającif old_file:
tuż przed wywołaniemos.path.isfile()
.Wypróbuj django-cleanup , automatycznie wywołuje metodę delete na FileField po usunięciu modelu.
pip install django-cleanup
settings.py
INSTALLED_APPS = ( ... 'django_cleanup', # should go after your apps )
źródło
Możesz usunąć plik z systemu plików za pomocą
.delete
metody wywołania pola pliku pokazanej poniżej z Django> = 1.10:obj = Song.objects.get(pk=1) obj.song.delete()
źródło
Możesz także po prostu nadpisać funkcję usuwania modelu, aby sprawdzić, czy plik istnieje, i usunąć go przed wywołaniem funkcji super.
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)
źródło
queryset.delete()
nie wyczyści plików za pomocą tego rozwiązania. Będziesz musiał iterować zestaw zapytań i wywołać.delete()
każdy obiekt.Rozwiązanie Django 2.x:
Usuwanie plików w Django 2 jest bardzo łatwe . Wypróbowałem następujące rozwiązanie wykorzystujące Django 2 i SFTP Storage, a także FTP STORAGE i jestem prawie pewien, że będzie działać z każdym innym menedżerem pamięci, który zaimplementował tę
delete
metodę. (delete
metoda jest jedną zstorage
metod abstrakcyjnych).Zastąp
delete
metodę modelu w taki sposób, że instancja usuwa swoje FileFields przed usunięciem siebie: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.song.name) super().delete()
U mnie działa to całkiem łatwo. Jeśli chcesz sprawdzić, czy plik istnieje przed usunięciem, możesz użyć
storage.exists
. np.self.song.storage.exists(self.song.name)
zwróciboolean
reprezentację, jeśli piosenka istnieje. Więc będzie to wyglądać tak: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.song.name) super().delete()
EDYCJA (dodatkowo):
Jak wspomniał @HeyMan , wywołanie tego rozwiązania
Song.objects.all().delete()
nie powoduje usunięcia plików! Dzieje się tak, ponieważSong.objects.all().delete()
jest uruchomione zapytanie usuwające Menedżera domyślnego . Więc jeśli chcesz mieć możliwość usuwania plików modelu za pomocąobjects
metod, musisz napisać i używać Custom Manager (tylko do nadpisania jego zapytania usuwającego):class CustomManager(models.Manager): def delete(self): for obj in self.get_queryset(): obj.delete()
i aby przypisać
CustomManager
do modelu, musisz zainicjowaćobjects
wewnątrz swojego modelu: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.song.name) super().delete()
Teraz możesz użyć
.delete()
na końcu dowolnychobjects
pod-zapytań. Napisałem najprostszyCustomManager
, ale możesz to zrobić lepiej, zwracając coś o usuniętych obiektach lub cokolwiek chcesz.źródło
Oto aplikacja, która usunie stare pliki po usunięciu modelu lub załadowaniu nowego pliku: 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)
źródło
@Anton Strogonoff
Brakuje mi czegoś w kodzie, gdy zmiana pliku, jeśli tworzysz nowy plik generuje błąd, ponieważ jest to nowy plik i nie znalazłem ścieżki. Zmodyfikowałem kod funkcji i dodałem zdanie try / except i działa dobrze.
@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
źródło
try:
bloku (AttributeError
być może?).Ten kod będzie uruchamiany za każdym razem, gdy załaduję nowy obraz (pole logo) i sprawdzę, czy logo już istnieje, jeśli tak, zamknij je i usuń z dysku. Ta sama procedura mogłaby być oczywiście wykonana w funkcji odbiornika. Mam nadzieję że to pomoże.
# 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='')
źródło