Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement attachment_api and serving of attachments #4541

Merged
merged 14 commits into from
Nov 8, 2024

Conversation

jrobbins
Copy link
Collaborator

@jrobbins jrobbins commented Nov 7, 2024

This PR implements the server side logic for uploading and serving attachments. Specifically,

  • Uploading immediately stores the attachment and returns a URL that can be placed into a UI field that has a list of URLs (some of which are stored on our server and some of which might link to other sites)
  • Serving checks that the request was made with the expected URL (including a domain that does not have our main domain cookies) and then returns the attachment content or thumbnail

Also in the PR,

  • Added an openapi declaration, and the associated generated files. This is our first openapi entry point that accepts binary data.
  • Changed the thumbnail size to match the size of images in the existing enterprise release notes page.
  • Fixed a comment formatting error in permission.py

Copy link
Collaborator

@jcscottiii jcscottiii left a comment

Choose a reason for hiding this comment

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

A few comments to consider.

Also, I see that you have API tests that:

  1. assert the POST behavior
  2. assert serving from an entity manually added to the database.

You should add a test that does the POST and serves the entity afterwards. This can come later in a playwright test when we have the UI. But I just wanted to bring that up now.

Comment on lines 83 to 91

if is_thumb and attachment.thumbnail:
content = attachment.thumbnail
headers = self.get_headers()
headers['Content-Type'] = 'image/png'
else:
content = attachment.content
headers = self.get_headers()
headers['Content-Type'] = attachment.mime_type
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can raise headers = self.get_headers() out before the conditional statements

Suggested change
if is_thumb and attachment.thumbnail:
content = attachment.thumbnail
headers = self.get_headers()
headers['Content-Type'] = 'image/png'
else:
content = attachment.content
headers = self.get_headers()
headers['Content-Type'] = attachment.mime_type
headers = self.get_headers()
if is_thumb and attachment.thumbnail:
content = attachment.thumbnail
headers['Content-Type'] = 'image/png'
else:
content = attachment.content
headers['Content-Type'] = attachment.mime_type

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.



class AttachmentsAPI(basehandlers.EntitiesAPIHandler):
"""Features are the the main records that we track."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I think this comment was a copy-paste from features_api

"""Features are the the main records that we track."""

Instead, you may want to mention how the attachments relate to features

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

if redirect_resp:
self.abort(403, msg='User lacks permission to edit')

files = kwargs.get('mock_files', self.request.files)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should not have this logic that looks for mock_files in the keywords in the live code. Instead, we should adjust the tests. More about this in the test file.

Suggested change
files = kwargs.get('mock_files', self.request.files)
files = self.request.files

Comment on lines 92 to 96
mock_files = {'uploaded-file': testing_config.Blank(
filename='hello_attach.txt',
read=lambda: b'hello attachments!',
mimetype='text/plain')}
with test_app.test_request_context(self.request_path):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
mock_files = {'uploaded-file': testing_config.Blank(
filename='hello_attach.txt',
read=lambda: b'hello attachments!',
mimetype='text/plain')}
with test_app.test_request_context(self.request_path):
mock_file = (io.BytesIO(b'hello attachments!'), 'test.txt')
with test_app.test_request_context(path=self.request_path, data={'uploaded-file': mock_file}):

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for that suggestion. I had spent half the day struggling with passing in a complete POST body as a binary string.

if settings.DEV_MODE or settings.UNIT_TEST_MODE:
origin = settings.SITE_URL
else:
digits = attachment.key.integer_id() % 1000
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a comment as to why we are doing modulo 1000?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


@permissions.require_create_feature
def do_post(self, **kwargs) -> dict[str, str]:
"""Handle POST requests to create a single feature."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, I think you meant to adjust this comment too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

@jrobbins
Copy link
Collaborator Author

jrobbins commented Nov 8, 2024

A few comments to consider.

Also, I see that you have API tests that:

  1. assert the POST behavior
  2. assert serving from an entity manually added to the database.

You should add a test that does the POST and serves the entity afterwards. This can come later in a playwright test when we have the UI. But I just wanted to bring that up now.

I went ahead and added a round-trip unit test now.

@jrobbins jrobbins merged commit cbfb373 into main Nov 8, 2024
7 checks passed
@jrobbins jrobbins deleted the 20241029-serving-attachments branch November 8, 2024 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants