Make MimetypeHandlers take care of deleting associated files for a file attachment.

Review Request #13141 — Created July 13, 2023 and submitted

Information

Review Board
release-6.x

Reviewers

Currently we have no centralized method for deleting extra files associated
with file attachments, such as thumbnail files. This adds a
delete_associated_files method to the base MimetypeHandler class, which
can be implemented by subclasses to take care of deleting any extra associated
files that are created for file attachments of certain mimetypes. This also
updates the ImageMimetype class to delete the files it creates for thumbnails.

  • Tested in an upcoming change where deleting draft file attachments will
    trigger deleting all files associated with the draft.
  • Ran unit tests.
Summary ID
Make MimetypeHandlers take care of deleting associated files.
703c798031b051fc96aa2847144a1184a9db3dbe
Description From Last Updated

Might be worth centralizing the list of available thumbnail files using some function, and then using that in both get_thumbnail() …

chipx86chipx86

Can you show me an example of how these paths end up looking? The before/after? I feel like there's got …

chipx86chipx86

Can you add -> None for each of these, so we can do type checking as part of tests?

chipx86chipx86

Just for alignment purposes, we can prefix all these with f, even if we're not putting in any format strings. …

chipx86chipx86

Add a blank line between these two.

daviddavid

Missing trailing comma.

chipx86chipx86

We can just use attachment, since we have that locally.

chipx86chipx86

lstrip() isn't safe. It doesn't remove a prefix. It removes any leading characters found in the provided string. So, any …

chipx86chipx86
david
  1. Ship It!
  2. 
      
maubin
david
  1. Ship It!
  2. 
      
chipx86
  1. 
      
  2. reviewboard/attachments/mimetypes.py (Diff revision 2)
     
     
     
     
    Show all issues

    Might be worth centralizing the list of available thumbnail files using some function, and then using that in both get_thumbnail() and in here. Maybe something that supplies the filename and the 1x/2x/etc. This will also help us scale up thumbnails for higher-res displays down the road.

  3. reviewboard/attachments/mimetypes.py (Diff revision 2)
     
     
    Show all issues

    Can you show me an example of how these paths end up looking? The before/after? I feel like there's got to be another way to do what we want to do here.

    1. filename without extension: uploaded/files/2023/08/11/8378df46-6405-4305-8422-8d6bbac6a82d__logo
      thumbnail_300 (what the thumbnail method returns): /media/uploaded/files/2023/08/11/8378df46-6405-4305-8422-8d6bbac6a82d__logo_300.png
      thumbnail_300 after: uploaded/files/2023/08/11/8378df46-6405-4305-8422-8d6bbac6a82d__logo_300.png

      Originally I was thinking that I could just strip the leading /media/ in the storage URL for the thumbnail, but I wasn't sure if I could assume that all storage URLs will actually have a leading /media/ or if this is something that admins could possibly change.

    2. I realized that I can just strip the site_media_url (instead of assuming it'll always be /media/) from the storage URL, so I'll do that instead.

  4. reviewboard/attachments/tests.py (Diff revision 2)
     
     
    Show all issues

    Can you add -> None for each of these, so we can do type checking as part of tests?

  5. reviewboard/attachments/tests.py (Diff revision 2)
     
     
     
     
     
     
    Show all issues

    Just for alignment purposes, we can prefix all these with f, even if we're not putting in any format strings.

    There's no difference to Python. A format string that doesn't format just becomes a string.

    >>> import dis
    >>> def foo():
    ...     return (
    ...         'test'
    ...         f'{hi}'
    ...         'bar'
    ...     )
    ...
    >>> def bar():
    ...     return (
    ...         f'test'
    ...         f'{hi}'
    ...         f'bar'
    ...     )
    ...
    >>> dis.dis(foo) == dis.dis(bar)
    True
    
    1. Oh, dis doesn't return, so that was a bad test. But:

      >>> dis.dis(foo)
        3           0 LOAD_CONST               1 ('test')
      
        4           2 LOAD_GLOBAL              0 (hi)
      
        3           4 FORMAT_VALUE             0
                    6 LOAD_CONST               2 ('bar')
                    8 BUILD_STRING             3
      
        2          10 RETURN_VALUE
      >>>
      >>> dis.dis(bar)
        3           0 LOAD_CONST               1 ('test')
      
        4           2 LOAD_GLOBAL              0 (hi)
      
        3           4 FORMAT_VALUE             0
                    6 LOAD_CONST               2 ('bar')
                    8 BUILD_STRING             3
      
        2          10 RETURN_VALUE
      
  6. 
      
maubin
david
  1. 
      
  2. reviewboard/attachments/mimetypes.py (Diff revision 3)
     
     
     
    Show all issues

    Add a blank line between these two.

  3. 
      
maubin
david
  1. Ship It!
  2. 
      
chipx86
  1. 
      
  2. reviewboard/attachments/mimetypes.py (Diff revision 4)
     
     
    Show all issues

    Missing trailing comma.

  3. reviewboard/attachments/mimetypes.py (Diff revision 4)
     
     
     
    Show all issues

    We can just use attachment, since we have that locally.

  4. reviewboard/attachments/mimetypes.py (Diff revision 4)
     
     
    Show all issues

    lstrip() isn't safe. It doesn't remove a prefix. It removes any leading characters found in the provided string. So, any characters listed in site_media_url will be removed from the beginning of the URL.

    Ideally we'd use removeprefix(), but that's only in 3.9+. So you'd instead need to do:

    if t.startswith(site_media_url):
        t = t[len(site_media_url):]
    

    Or maybe assert that it starts with it? Or skip/log if it doesn't?

  5. 
      
maubin
david
  1. Ship It!
  2. 
      
chipx86
  1. Ship It!
  2. 
      
maubin
Review request changed
Status:
Completed
Change Summary:
Pushed to release-6.x (0705f96)