Skip to content

Allow uploading of images in statuses#3768

Open
iangreenleaf wants to merge 35 commits intobookwyrm-social:mainfrom
iangreenleaf:images-in-statuses
Open

Allow uploading of images in statuses#3768
iangreenleaf wants to merge 35 commits intobookwyrm-social:mainfrom
iangreenleaf:images-in-statuses

Conversation

@iangreenleaf
Copy link
Copy Markdown
Contributor

@iangreenleaf iangreenleaf commented Dec 21, 2025

Description

Allows images to be uploaded and displayed inline in reviews. I wanted the ability to do this (I'm reviewing picture books) so I set out to make it happen. I tried to solve this in a clean and self-contained way while also making it flexible enough to be powerful, so this is what I came up with:

  1. Allow <img> elements and a few attributes through the HTML sanitizer. With this change alone, it's technically possible for people to insert images uploaded elsewhere into their statuses.
  2. A pair of models and a view to allow users to upload images in a way that are not embedded in another model but rather are standalone records. Upon upload, an image is resized to any number of predefined sizes as determined by the server config.
  3. A special !image markdown helper that integrates with the UserUpload class to display responsive images by making use of srcset to provide all the available sizes of image.
  4. Some JavaScript that handles drag n drop events to automatically upload the dropped image and inserts the !image helper at the cursor.

End result:
image
image

A couple notes:

  • I'm an experienced Rails dev but this is my first foray in Django, so there are probably places I've strayed from convention or simply missed easier ways to do stuff. Feel free to suggest improvements.
  • The flaw to how I've set this up at the moment is that if you don't know about the feature, it's totally invisible and you'll never discover it. I could imagine providing some sort of UI to allow uploads outside of the drag n drop mechanic, but that seemed like a series of decisions that someone with more context should be making, and I wanted to ship the simplest version first before I tried to tackle any of that.
  • This PR includes Replace markdown library with mistune #3767 because it needs a new parser to successfully create the HTML in statuses for images.
  • I probably need to do some linter cleanup before this will pass - I'll hopefully get to that soon.

What type of Pull Request is this?

  • Bug Fix
  • Enhancement
  • Plumbing / Internals / Dependencies
  • Refactor

Does this PR change settings or dependencies, or break something?

  • This PR changes or adds default settings, configuration, or .env values
  • This PR changes or adds dependencies
  • This PR introduces other breaking changes

Details of breaking or configuration changes (if any of above checked)

New UPLOAD_IMAGE_SIZES .env value that controls what sizes of images will be generated.

Documentation

I'm not sure if any documentation is needed here, let me know.

  • New or amended documentation will be required if this PR is merged
  • I have created a matching pull request in the Documentation repository
  • I intend to create a matching pull request in the Documentation repository after this PR is merged

Tests

  • My changes do not need new tests
  • All tests I have added are passing
  • I have written tests but need help to make them pass
  • I have not written tests and need help to write them

markdown.py had parsing errors that were throwing off img srcsets.
It doesn't hugely matter one way or another, but the existing tests all
expect no newline, so why rock the boat.
This is all that's needed to allow basic image insertion via markdown.
Also makes sure to save resized files with correct extension
markdown.py had parsing errors that were throwing off img srcsets.
1. File extension wasn't coming through to versions
2. Srcset errors prevented it from working correctly
Copy link
Copy Markdown
Contributor

@ilkka-ollakka ilkka-ollakka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think overall it looks ok, few comments here and there.

Biggest issue I see, is that uploads are not linked to statuses where they are uploaded to, so I don't see clear flow how those are cleaned up if nothing links to those uploads anymore.

});
}

uploadFile(file, target) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could also check filesize. API/nginx will give error if file is too big, but not sure this codeflow gives reasonable explanation to the user in that case?

One example can be found in https://github.com/bookwyrm-social/bookwyrm/pull/3627/files how bookwyrm.js uses form dataset to check upload file-sizes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilkka-ollakka I think I've addressed all your feedback except this item. I got stuck here trying to figure out how to pass DATA_UPLOAD_MAX_MEMORY_SIZE through to the front end. To replicate how it's done in that other PR, I think I'd have to pass it through the status form, and I couldn't figure out how to do that - too many layers of stuff I'm unfamiliar with.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take look if I can provide a way to solve that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could solve the issue by adding the upload-limits to text-area as dataset attributes, then you can access that data via event.currentTarget.dataset ?

So no need to go via the example PR on widgets as there is not really upload form like that. Instead you could for example add simple_tag to templatetags/utilities.py for adding data-set attributes with defined limits and use that tag in text-area where you defined the droppable-textfield ?

Not sure if it is the best way, but atleast it could be straightforward way to do it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me! Thanks for the help.

@iangreenleaf
Copy link
Copy Markdown
Contributor Author

Thanks @ilkka-ollakka, I will go through your comments in detail once the holidays are over and I'm back to my normal schedule, most likely early January.

Regarding not tying the uploads to statuses, yes, that's a design decision I made that's worth discussing. I've seen two schools of thought regarding how to treat uploads. One is to tie them closely to a database record, as you're suggesting, and manage them as a sort of additional piece of data on that record. The other approach is to treat them as independent and sort of idempotent: every uploaded file goes in a big bucket and gets a unique identifier that's the only link it has to anything in the database. Typically in that second way of doing things, every uploaded file lives forever, even if not needed—I suspect this approach really only came into practice once storage was cheap enough to not worry about a bit of "waste".

To align with the first approach instead of the second, I certainly could set up a join table to tie uploads to statuses. I think doing that might significantly increase the complexity of the front-end code, since we would need to figure out how to upload files at the same time as the status form is submitted, and tie each image to the correct place in the text of the status with some sort of placeholder for rendering. It's all a bit easier to handle when uploads happen prior to the creation of the status.

Another option that's in between the two poles would be to create an optional join table to statuses. That would allow me to keep the current flow for uploading immediately, but would give server admins the option to do some sort of clean-up action that deletes any images older than some threshold that aren't tied to a status.

@hughrun
Copy link
Copy Markdown
Member

hughrun commented Dec 29, 2025

Thanks for joining us @iangreenleaf!

This is a cool idea. I'm wondering how attached you are to images uploads being a separate model, and if so can you explain a little about why?

The usual way we'd do this would be to add a file field to an existing model (in this case, probably BookStatus). Then you'd add that field to ReviewForm, CommentForm etc.

That partially solves the issue @ilkka-ollakka has noted with orphaned images (though Django has a few issues with cleaning up files generally).

The downside of that is that they're not "embedded" in the post with markdown, and would have to be added like we do with book covers which I suppose one might argue is a little clunky. But the upside is that the image/s are understood by the Django backend as being part of that status, and the code is easier to understand and maintain because it works in a "Django-like" way. When posts are pushed out as ActivityPub the images will be attachments rather than inline images.

@iangreenleaf
Copy link
Copy Markdown
Contributor Author

@hughrun I'm quite attached to the idea of the images being embedded in the markdown and thus being able to be located at specific points in the text. That is necessary to what I want out of this feature and fits with the perspective of treating book statuses more like a rich-text "article" and less like a microblogging "status". I'm not terribly attached to any of the particulars of how the code makes that functionality possible. If we can accomplish it while managing the files as part of the Django model, that would be fine with me - though as I mentioned in my previous comment I was a little apprehensive about complicating the code by going that route.

@hughrun
Copy link
Copy Markdown
Member

hughrun commented Jan 7, 2026

Ok there are a few options here. I think we can probably get the best of both worlds with some care and not a huge amount of complication.

The biggest issue I see at this point is that currently only Review is published to the rest of the fediverse as an Article - everything else is a Note. So to get the full value you're looking for, we would need to have a conversation about changing that. Mastodon refuses to accept the existence of Article as an object type, and turns them into a brief note with a link, so that has causes some discussions here about compatibility and whether to even publish things as Activity objects.

@mouse-reeve do you have any thoughts on any of this?

@iangreenleaf
Copy link
Copy Markdown
Contributor Author

I would be okay with having "inline" images limited to Reviews - that's the only place that it really feels like it makes sense to me. And maybe the other statuses actually would make sense with attached images; you could post a "started reading" status with a photo of you with your book and mug of hot cocoa or something.

I would want some advice on architecting it if we went that route, as I'm still new to the Bookwyrm codebase. I plugged it into all statuses just because that was the obvious and straightforward place to do it.

@hughrun
Copy link
Copy Markdown
Member

hughrun commented Jan 7, 2026

No problem at all @iangreenleaf. My own first PR for BookWyrm was my first time working with Django and ended up being a bit more complicated than I anticipated, so I empathise with the feeling of not quite knowing how everything fits together (also, BookWyrm is kinda complex because we have to consider ActivityPub as well).

If you give me a few days I'll think about what might be the most straightforward way to do this. I don't think you'll need to change too much, it's mostly how we handle saving images, and the ActivityPub aspect might need some additional changes. As @ilkka-ollakka noted, there are some methods already available for image handling that we can probably plug into as well.

@hughrun hughrun added the enhancement New feature or request label Jan 13, 2026
Copy link
Copy Markdown
Member

@hughrun hughrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iangreenleaf Now that I've had a proper look I can see what you're doing here. I haven't had time to do an actual test run of this yet but it generally looks good.

Uploads are tied to the status when the status is created.
The uploads have been created while the message was drafted, without
the foreign key.
This started failing in a test - unsure why TBH, but the new
version is better documented and should work well.
@hughrun
Copy link
Copy Markdown
Member

hughrun commented Mar 22, 2026

@iangreenleaf let us know when you're ready for this to be looked at again :)

@iangreenleaf
Copy link
Copy Markdown
Contributor Author

@hughrun @ilkka-ollakka I believe I've addressed all the feedback and this is ready for another look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants