Définissez FileField de Django sur un fichier existant


89

J'ai un fichier existant sur le disque (disons /folder/file.txt) et un champ de modèle FileField dans Django.

Quand je fais

instance.field = File(file('/folder/file.txt'))
instance.save()

il réenregistre le fichier sous file_1.txt(la prochaine fois _2, etc.).

Je comprends pourquoi, mais je ne veux pas de ce comportement - je sais que le fichier auquel je veux associer le champ m'attend vraiment, et je veux juste que Django le pointe.

Comment?


1
Vous n'êtes pas sûr de pouvoir obtenir ce que vous voulez sans modifier Django ou sous-classer FileField. Chaque fois que a FileFieldest enregistré, une nouvelle copie du fichier est créée. Il serait assez simple d'ajouter une option pour éviter cela.
Michael Mior

eh bien oui, on dirait que je dois sous-classer et ajouter un paramètre. Je ne veux pas créer de tables supplémentaires pour cette tâche simple
Garde

1
Placez le fichier dans un emplacement différent, créez votre champ avec ce chemin, enregistrez-le et vous avez le fichier dans la destination upload_to.
benjaoming

Réponses:


22

Si vous souhaitez le faire de manière permanente, vous devez créer votre propre classe FileStorage

import os
from django.conf import settings
from django.core.files.storage import FileSystemStorage

class MyFileStorage(FileSystemStorage):

    # This method is actually defined in Storage
    def get_available_name(self, name):
        if self.exists(name):
            os.remove(os.path.join(settings.MEDIA_ROOT, name))
        return name # simply returns the name passed

Maintenant dans votre modèle, vous utilisez votre MyFileStorage modifié

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)

oh, ça a l'air prometteur. car le code de FileField est un peu non intuitif
Garde

mais ... est-il possible de changer le stockage sur une base par demande, comme: instance.field.storage = mfs; instance.field.save (nom, fichier); mais ne pas le faire dans une autre branche de mon code
Garde

2
Non, car le moteur de stockage est lié au modèle. Vous pouvez éviter tout cela en stockant simplement votre chemin de fichier dans un FilePathFieldou simplement en texte brut.
Burhan Khalid

Vous ne pouvez pas simplement renvoyer un nom. Vous devez d'abord supprimer le fichier existant.
Alexander Shpindler

124

il suffit de définir instance.field.namele chemin de votre fichier

par exemple

class Document(models.Model):
    file = FileField(upload_to=get_document_path)
    description = CharField(max_length=100)


doc = Document()
doc.file.name = 'path/to/file'  # must be relative to MEDIA_ROOT
doc.file
<FieldFile: path/to/file>

15
Le chemin relatif de votre MEDIA_ROOT, c'est-à-dire.
mgalgs

7
Dans cet exemple, je pense que vous pouvez aussi le fairedoc.file = 'path/to/file'
Andrew Swihart

13

essayez ceci ( doc ):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()

5

Il est juste d'écrire sa propre classe de stockage. Cependant, ce get_available_namen'est pas la bonne méthode pour remplacer.

get_available_nameest appelé lorsque Django voit un fichier avec le même nom et tente d'obtenir un nouveau nom de fichier disponible. Ce n'est pas la méthode qui provoque le changement de nom. la méthode causée est _save. Les commentaires dans _savesont assez bons et vous pouvez facilement trouver qu'il ouvre le fichier pour l'écriture avec un drapeau os.O_EXCLqui lancera une OSError si le même nom de fichier existe déjà. Django détecte cette erreur puis appelle get_available_namepour obtenir un nouveau nom.

Je pense donc que la bonne façon est de remplacer _saveet d'appeler os.open () sans indicateur os.O_EXCL. La modification est assez simple mais la méthode est un peu longue donc je ne la colle pas ici. Dites-moi si vous avez besoin de plus d'aide :)


ce sont 50 lignes de code que vous devez copier, ce qui est plutôt mauvais. Le remplacement de get_available_name semble plus isolé, plus court et beaucoup plus sûr pour, par exemple, la mise à niveau vers les nouvelles versions de Django à l'avenir
Michael Gendin

2
Le problème de la substitution uniquementget_available_name est que lorsque vous téléchargez un fichier avec le même nom, le serveur entrera dans une boucle sans fin. Puisque _savevérifie le nom du fichier et décide d'en obtenir un nouveau, il get_available_nameretourne toujours le double. Vous devez donc remplacer les deux.
x1a0

1
Oups, nous avons cette discussion en deux questions, mais ce n'est que maintenant que j'ai remarqué qu'elles sont légèrement différentes) Donc j'ai raison sur cette question, et vous êtes dans celle-ci)
Michael Gendin

1

J'ai eu exactement le même problème! puis je me rends compte que mes modèles étaient à l'origine de cela. exemple j'ai mes modèles comme ceci:

class Tile(models.Model):
  image = models.ImageField()

Ensuite, je voulais avoir plus d'une tuile référençant le même fichier sur le disque! La façon dont j'ai trouvé pour résoudre ce problème a été de changer la structure de mon modèle en ceci:

class Tile(models.Model):
  image = models.ForeignKey(TileImage)

class TileImage(models.Model):
  image = models.ImageField()

Ce qui, après avoir réalisé que cela a plus de sens, car si je veux que le même fichier soit enregistré plus d'un dans ma base de données, je dois créer une autre table pour cela!

Je suppose que vous pouvez aussi résoudre votre problème comme ça, en espérant simplement que vous pourrez changer les modèles!

ÉDITER

Aussi je suppose que vous pouvez utiliser un stockage différent, comme celui-ci par exemple: SymlinkOrCopyStorage

http://code.welldev.org/django-storages/src/11bef0c2a410/storages/backends/symlinkorcopy.py


a du sens dans votre cas, pas dans le mien. Je ne veux pas qu'il soit référencé plusieurs fois. Je crée un objet référençant un fichier, puis je réalise qu'il y a des erreurs dans d'autres attrs, et je rouvre le formulaire de création. Lors de sa nouvelle soumission, je ne veux pas perdre le fichier qui est déjà enregistré sur le disque
Garde

donc je suppose que vous pouvez utiliser mon approche! car vous aurez une table FormFile qui ne contiendra le fichier qu'alors que vous l'avez! puis dans votre table Form, vous aurez un FK pour ce fichier! vous pouvez donc modifier / créer de nouveaux formulaires pour le même fichier! (btw je change l'ordre du FK dans mon exemple principal)
Arthur Neves

Si vous souhaitez publier votre domaine (modèles) dans votre message! je peux aussi avoir une meilleure idée!
Arthur Neves

le domaine n'a pas vraiment d'importance - j'ai un modèle avec une photo qui lui est associée, et j'ai un écran d'édition personnalisé. une fois téléchargée, je veux que la photo reste sur le serveur, mais je n'aime pas vraiment générer un modèle, une table et une recherche FK séparés simplement parce que cela semble être une limitation du cadre
Garde

La limitation ici, je suppose, est que lorsque vous enregistrez un FileField dans django, il passe toujours par Django Storages! il ne sera donc pas logique de forcer simplement un chemin de fichier! aussi comment Django doit-il savoir que le fichier existe déjà dans le chemin? une autre approche que vous pouvez utiliser consiste à utiliser FilePathField à la place! vous pouvez donc simplement définir le chemin dans votre base de données et effectuer la recherche comme vous le pensez.
Arthur Neves

1

Vous devez définir votre propre stockage, l'hériter de FileSystemStorage et remplacer l' OS_OPEN_FLAGSattribut de classe etget_available_name() méthode de :

Version Django: 3.1

Projet / core / files / storages / backends / local.py

import os

from django.core.files.storage import FileSystemStorage


class OverwriteStorage(FileSystemStorage):
    """
    FileSystemStorage subclass that allows overwrite the already existing
    files.
    
    Be careful using this class, as user-uploaded files will overwrite
    already existing files.
    """

    # The combination that don't makes os.open() raise OSError if the
    # file already exists before it's opened.
    OS_OPEN_FLAGS = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, 'O_BINARY', 0)

    def get_available_name(self, name, max_length=None):
        """
        This method will be called before starting the save process.
        """
        return name

Dans votre modèle, utilisez votre OverwriteStorage personnalisé

myapp / models.py

from django.db import models

from core.files.storages.backends.local import OverwriteStorage


class MyModel(models.Model):
   my_file = models.FileField(storage=OverwriteStorage())
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.