Published on

Release: Async Uploads & PDF Generation

Authors
  • Name
    Dave Coates

To upgrade any existing projects it's recommended to pull in the relevant commits from upstream.

git remote add upstream git@gitlab.internal.alliancesoftware.com.au:alliance/template-django.git
git cherry-pick XXX1 XXX2

In cases where you are significantly behind it may be easier to pull the whole app (eg. common_lib) in but you'll need to review the relevant branch.


In this release

Thanks to Callum, Fang, Vitaly & Levi for their contributions and reviews.

Upload direct to S3

This release introduces a new app common_storage that provides a storage class (S3AsyncUploadStorage) and model fields (AsyncFileField, AsyncImageField) for defining fields that should upload directly to a storage provider (S3 currently but can support others, eg Azure) and save the details to the database.

This process works as follows

a) The frontend initiates an upload via the UploadWidget. This calls the django backend to get a signed URL to upload to.

b) The file is uploaded to the signed URL which goes to a temporary location in the bucket (/async-temp-files/ by default)

c) The form is submitted and the record save. Once the record has been saved file is moved from the temporary location to the permanent location.

Setup

To add this to existing projects you can copy the common_storage app from the template and activate it. See commit 80465b41 or read the setup instructions.

The template has the app enabled by default but to use it you'll need to set DEFAULT_FILE_STORAGE = "common_storage.s3.S3AsyncUploadStorage" as detailed in the setup instructions.

If you wish to use the AsyncImageField you need to install Pillow.

Usage

Once setup is completed you can, for most use cases, simply use (AsyncFileField, AsyncImageField) instead of the django provided FileField and ImageField. This will automatically use the correct frontend widget when used in a django ModelForm or in a Presto form (assuming using the default widget for a codegen model).

For more advanced usage see the documentation

React Hooks & Components

  • Add the UploadWidget to handle uploading to an async backend
  • Add the usePreventFormSubmissionHook for dealing with disabling a form while waiting for something to happen in a React component.
    • UploadWidget uses this to disable the submission of a django form while upload is in progress

Example

PDF Generation

Another new app common_pdf has been added to support generating PDF's from views using pyppeteer. This allows you to generate a PDF from an existing django view that can include javascript (eg. rendered in React).

Setup

To add this to existing projects you can copy the common_pdf app from the template and activate it. See commit bfaf2437 or read the setup instructions.

Usage

It's most simple usage is

from common_pdf.django_pyppeteer import view_as_pdf

@view_as_pdf()
def my_view(request):
    return HttpResponse('Some content')

Accessing that view with ?pdf=True will result in the PDF being generated.

You can also manually render it, eg. to send in an email:

pdf = render_pdf(
    request.build_absolute_uri(reverse("demo_app:restaurant_menu", kwargs={"pk": self.object.pk})),
    request_headers=extract_request_headers(request),
)
message = EmailMessage(
    f"Menu for {self.object.brand.name} {self.object.name}",
    "Here is the menu you requested",
    "demo@examplesystem.com",
    [email],
)
message.attach("menu.pdf", pdf, "application/pdf")
message.send()

This example renders from a URL and passes the headers from the current request.

Big thanks to Callum for implementing this.

Examples

You can see an example at https://www.examplesystem.com/demo/restaurant/5/detail/. The Download Menu button will download a PDF or you can see the view directly at https://www.examplesystem.com/demo/restaurant/5/menu/ (you can pass ?pdf=True to generate from there).

You can also enter an email address and have the PDF generated and sent there.

Server Choices

The @server_choices decorator is used to expose a backend endpoint that will serve up choices and provide pagination, filtering etc. This allows you to show a drop down on a foreign key field without worrying about there being too much data.

Previously it was only supported when using a Presto ViewModel. This meant you had to generate a ViewModel even if it wasn't used for anything else.

You can now use @server_choices on a django Form or a django-filters FilterSet. This will set the django widget used to one that will handle rendering the necessary React component in your template automatically.

As part of this change the decorator has moved from common_lib.server_choices.serializer to common_lib.server_choices.

See the server_choices documentation for more details.

Upgrading

To upgrade an existing project see commit 95aecae8.

Presto

Version 0.0.12 & 0.0.13 released with some fixes and enhancements. There's some breaking changes - see the changelog.

See bcb01c5 for required change upgrading to 0.0.12.

See feeb61d for required change upgrading to 0.0.13.

Alliance Utils

Version 1.0.0 has been released - see the changelog.

Alliance Utils is now on github and published to pypi - you can install with pip install django-allianceutils.

The presto_drf package is no longer required and the functionality has been moved into allianceutils. See 79aead18 for how to migrate.

Thanks to Fang for handling this.

Codegen Enhancements

Thanks to Vitaly there's a few codegen enhancements:

  • Creating a FilterSet will now assign it to the generated ViewSet
  • On generated FilterSet classes CharField will default to using a lookup_expr of icontains
  • When generating Presto ViewModel nothing will be written if the output would be identical (less spam in the dev console too)

Misc

  • 0f70d225 - stop-runaway-react-effects has been added to the template. This catches situations when a react use(Layout)Effect runs repeatedly in rapid succession.
  • 2992f66e - Masquerade buttons have been added to user list views (thanks Vitaly)
  • d4a23f7f - Jest setup now works with typescript
  • 7ea2cc44 - form_item tag now allows customisation of help text, required, label and form item / widget class names (thanks Vitaly)
  • 4d6cea1d - Add extra_kwargs option to CrudFilterSet. This can be used to change how a field works without redefining the whole thing (eg. easily change the widget used) (thanks Vitaly)
  • 70d64527 - This gives better errors in dev when using the incorrect render function from django render_component