1

So, assume I have the following (simplified) Django model:

class Document(models.Model):
    def get_file_path(self, filename):
        extension = filename.split('.')[-1]
        product_string = ', '.join(p.__str__() for p in self.related_products.all())
        return 'documents/{}/{}.{}'.format(product_string, self.document_id, extension)

    document_id = models.CharField(max_length=9)
    related_products = models.ManyToManyField(to=Product, related_name='documents')
    file = models.FileField(upload_to=get_file_path)

As you can see I want my filepath based on the contents of the m2m (related_products) relationship. This doesn't work though, and throws the following error:

needs to have a value for field "id" before this many-to-many relationship can be used.

Which of course makes sense. But, I don't know how to solve it. I either need access to the m2m relationship information before it's saved. Or, I need to save the model and its relationships first without saving the file, and only then save the file specifically.

Also, if it matter; this only has to work in the Django Admin.

Any help would be appreciated. Thanks! :)

Landcross
  • 472
  • 7
  • 16
  • you can save the model with products first. then you can save the files or you can manually handle the upload thing – shourav Apr 28 '19 at 09:26
  • Well, that's indeed what I thought of as described in my question. But I don't know how to save the model without saving the file, and only then saving the file. – Landcross Apr 28 '19 at 14:44
  • you are saving model by form right?? can you add the view code here?? – shourav Apr 28 '19 at 16:23
  • I'm just using the Django Admin, no views/forms at all. – Landcross Apr 28 '19 at 17:44
  • What's the relationship between `Document` and `Product`? Are you sure you should be using a many-to-many relationship? – Tony Apr 29 '19 at 09:32
  • Yes, I am. A document can belong to multiple products and a product can have multiple documents. – Landcross Apr 29 '19 at 14:36

1 Answers1

0

To allow you to save the relationship in the admin page you need to make the file field optional; this is done by adding blank=True to the model.

file = models.FileField(upload_to=get_file_path, blank=True)

blank, rather than null as I originally suggested, works. See this SO question for the explanation: Django - how to make ImageField/FileField optional?

You can now create the Document-Product relationship and then add a file which will be put in the "documents" folder as defined by your get_file_path method.

Although changing the field allows you to work around the problem of creating the file upload path, it does break the requirement that a Document must have a file. If you can live with this, fine, but if not you'll have to find a different solution which still enforces the file requirement.

After considering this problem, I think you need to change the design regarding document storage. Placing files in folders based on their initial association with a Product is too restrictive. What happens when a Document is later related to another Product, are you going to update the folder name? The same problem exists when a Product is deleted.

Since you are storing your relationships in the database it's not necessary to also put that information in the folder name. Simplify your folder naming and let the database keep track of the links. You will then be able to enforce the file requirement for Documents and use the admin interface to create the relationships.

Tony
  • 9,672
  • 3
  • 47
  • 75
  • But I don't understand what to put in the save method? How can I save everything except the file, and then save the file separately? I tried some things, but it didn't work; I ended up in a loop because I was calling save from within save (which obviously isn't a good idea haha). – Landcross Apr 30 '19 at 06:58
  • Sorry I was thinking about overriding the save method of a form. If you only need this to work in the admin pages, have you tried allowing NULL on the `file` field, saving the relationship and then adding the file? – Tony Apr 30 '19 at 09:09
  • I indeed tried changing the file to allow NULL and saving the file separately from the rest of the Document model. But, like I said above, I can't get it to work properly. I don't know a lot about overriding save methods in Django. The naive way I tried was by manually creating a new Document within the save method without the File, then saving it and then adding the file to it. But that obviously doesn't work because it creates a save loop... I would appreciate it if you could share any code example? – Landcross Apr 30 '19 at 09:29