• 
      

    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)