tetra-framework / tetra

Tetra - A full stack component framework for Django using Alpine.js
https://www.tetraframework.com
MIT License
568 stars 19 forks source link

Form support lacks file upload #67

Open nerdoc opened 4 months ago

nerdoc commented 4 months ago

While FormComponent works reasonably well, It completely lacks file upload support. The problem is separated in a few parts:

nerdoc commented 2 months ago

Maybe this is usable: https://dev.to/code_rabbi/programmatically-setting-file-inputs-in-javascript-2p7i

nerdoc commented 2 weeks ago

@gsxdsm Basically, in the form_upload branch, Tetra creates a x-model attribute automatically when creating the form component, for each form field. I first really thought that it is an Alpine bug and filed an issue there, just to learn that this is normal HTML behaviour: file input fields can't have x-model attrs.

As far as I found out, like stated above, I think that it should go this way.

nerdoc commented 2 weeks ago

i pushed what I have in my local form_support branch.

gsxdsm commented 2 weeks ago

Got it thanks for the detailed breakdown. Starting to dig in there!

nerdoc commented 2 weeks ago

@gsxdsm Oh, and in case you didn't see it: I started a small example project to test the file upload: https://github.com/tetra-framework/test_tetra_forms Maybe you can use that.

gsxdsm commented 2 weeks ago

Excellent thank you!

nerdoc commented 2 weeks ago

@gsxdsm Did you make any progress? I have time tomorrow to help out here/code - if you tell me what you are doing ATM, I could drop in and help a bit?

gsxdsm commented 2 weeks ago

Not yet, have been a bit swamped - no notable progress. I have been looking at Laravel Livewire to see how they approach this, but nothing concrete.

gsxdsm commented 6 days ago

Still looking into this, not making much progress but will keep you updated this weekend

gsxdsm commented 6 days ago

Have been thinking about the cleanup of the temporary files - don't want to take another dependency here, but I'm thinking we need some sort of job/task. Did you have thoughts on the mechanism for the cleanup?

gsxdsm commented 6 days ago

Maybe kicking off something asynchronously from runserver?

gsxdsm commented 6 days ago

Okay, a quick test of creating an async job spawned from runserver to run at a period interval to do the cleanup seems to work. With this in place, I'll work on the rest of the upload process.

nerdoc commented 6 days ago

You mean a regular job? Because runserver isn't called in production (when using gunicorn, Daphne etc). I think I wouldn't bother too much ATM when this job is executed, but just make some cleanup procedure that actually does the job. It could be up to the Dev when this cleanup happens, starting from a poor-man's-cron to a celery task. IMHO it's enough when the procedure is available...

gsxdsm commented 6 days ago

Sounds good, will take that approach and focus on getting the rest working.

nerdoc commented 6 days ago

please tell me if I can assist. the form_support branch has already some code in it towards that aim, but not very much. If you add a PR, please do against that branch.

gsxdsm commented 6 days ago

Yes will do - I am working off of the form_support branch and the form test project, thanks! It is fairly complex, will keep you posted if I get stuck further.

nerdoc commented 6 days ago

As @waqasidrees07 got stuck in a very early stage, please feel free to contact me at any stage, so you don't make the same errors again as I did ;-)

gsxdsm commented 6 days ago

Quick update:

I have the change event being fired in Javascript and testing the file upload using the FormData (rather than a form tag).

Took a while to get my head around the overall flow, but thanks for the pointers - I see a path now to implementing. At least for single files! (Multi will need to come later)

gsxdsm commented 5 days ago

Getting closer! I have the javascript posting the file to a server method for the temporary upload on change! Debugging a bit, but making progress.

gsxdsm commented 5 days ago

Here's where I am:


The hardest part was getting the interplay between the server method and javascript working, but that's all done. The rest should be pretty straightforward.

One question - do we want:

  1. A task that can be run via manage.py to cleanup stale temp files?
  2. Stale temp files to be cleaned up whenever a new temp file upload happens (this is what livewire does)
  3. Both 1&2 -> benefit, the user will never have temp files accumulate if they forget to run the cleanup job or fail to setup a scheduler

I'm inclined to do #3, but want to get your POV.

Also, I'm thinking the TTL for the temp files should probably be a default of maybe 15 minutes? But should be overridable in settings?

Finally - I was going to make this work for multiple files, but I now realize django doesn't really support multiple files in a single FormField, right?

nerdoc commented 5 days ago

That's great news, really. About your question:

I think that a GC procedure should be available definitely as management command, on the long term it could be a subcommand of "tetra", like manage.py tetra init, manage.py tetra startcomponent, and here: manage.py tetra clean. This could trigger the same procedure as the poor-mans-GC with e.g. during file upload. Attention, here the race condition could occur.

I think I would really leave it up to the user. There may be use cases where the upload-auto-GC makes sense, in some (more critical) environments this would be a no-go...

So it would be absolutely ok if you just implement the GC procedure (e.g. in tetra.utils), I (or you?) can make a makagement command later too.

gsxdsm commented 5 days ago

I'm thinking through the step of moving the file from temporary storage to its permanent location and I'm guessing we'd want to use the FileField.upload_to property, right? (https://docs.djangoproject.com/en/5.1/ref/models/fields/#django.db.models.FileField.upload_to). If this is the case, I'd assume we'd also use the storage method on the File (or Image) field as well, right? We'd probably want to use the storage method on the file (or default) for the temporary file storage too.

Or did you have something more customizable/user controllable in mind?

gsxdsm commented 4 days ago

Okay, have the file moving and deletion of temp file working now. Tomorrow I will cleanup and submit something in the branch to take a look - we can iterate on that while I work on validation and the garbage collection.

gsxdsm commented 4 days ago

Actually I just put together a quick commit now to show what's done so far so you can provide feedback @nerdoc - would love feedback/suggestions. I need to do a lot more testing and refinement, but the basic approach is here

nerdoc commented 4 days ago

That's great, exactly how I thought it should be. I just hat a few minutes to look over the code, did not test it - but AFAICT this is definitely the way to go. Thanks, really. That was a few months of agony that comes to an end now ;-)

nerdoc commented 4 days ago

2 Things came into my mind while reading the code:

Just to be kept in mind.

gsxdsm commented 4 days ago

Yes - I can merge them, I started with them merged and debated splitting it up or not, but will merge back.

Yes, _uploadFile is definitely something that needs a bit of scrutiny. The csrf check helps a little, but I am going to take another look at livewire's approach to see if there are additional checks/holes there. One quick thing I was thinking was to have the method become a no-op if the component doesn't have a FileField...

nerdoc commented 4 days ago

Yes, that would be some additional security. Maybe you could add just a TODO in the callServerMethodWithFile() so we don't forget the security checks at the backend side.

gsxdsm commented 2 days ago

Updated with a todo for security checks and restricted the upload endpoint to only components with a file field