WIP: Attach invoice PDF to payment emails #104418

Draft
Anna Sirota wants to merge 4 commits from attach-invoice-pdf into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
4 changed files with 59 additions and 37 deletions
Showing only changes of commit 86052c3118 - Show all commits

View File

@ -21,7 +21,7 @@
{% if item.static_asset.download_url %}
<a href="{{ item.static_asset.download_url }}"
target="_blank"
download
download="{{ item.static_asset.get_download_name }}"
class="dropdown-item">
<i class="i-download me-1"></i>
<span>Download{% if additional_download %}{{ additional_download|capfirst }} {% endif %} <span class="text-muted">({{ item.static_asset.download_size }})</span></span>
@ -36,22 +36,22 @@
{% firstof "" item.preview_video_static_asset.video.tracks.count as preview_video_has_subtitles %}
{% firstof "" item.static_asset.video.tracks.count as has_subtitles %}
{# in case there's more than one downloadable, show a dropdown #}
{% if item.preview_video_static_asset and item.static_asset or preview_video_has_subtitles or has_subtitles %}
{% include "common/components/navigation/download_button.html" with static_asset=item.static_asset %}
<button data-bs-toggle="dropdown" data-bs-target="#downloadDropdown" class="btn btn-link dropdown-toggle">
<i class="i-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" id="downloadDropdown">
{% if item.preview_video_static_asset %}
{% include "common/components/navigation/download_button.html" with static_asset=item.preview_video_static_asset additional_download="video" %}
{% endif %}
{% if has_subtitles or preview_video_has_subtitles %}
{% with static_asset=item.preview_video_static_asset|default:item.static_asset %}
{% firstof item.name item as vtt_filename %}
{% include "common/components/navigation/download_subtitles.html" with name=vtt_filename %}
{% endwith %}
{% endif %}
</div>
{% if item.preview_video_static_asset and item.static_asset or preview_video_has_subtitles or has_subtitles %}
{% include "common/components/navigation/download_button.html" with static_asset=item.static_asset %}
<button data-bs-toggle="dropdown" data-bs-target="#downloadDropdown" class="btn btn-link dropdown-toggle">
<i class="i-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" id="downloadDropdown">
{% if item.preview_video_static_asset %}
{% include "common/components/navigation/download_button.html" with static_asset=item.preview_video_static_asset additional_download="video" %}
{% endif %}
{% if has_subtitles or preview_video_has_subtitles %}
{% with static_asset=item.preview_video_static_asset|default:item.static_asset %}
{% firstof item.name item as vtt_filename %}
{% include "common/components/navigation/download_subtitles.html" with name=vtt_filename %}
{% endwith %}
{% endif %}
</div>
{% else %}
{% include "common/components/navigation/download_button.html" with static_asset=item.static_asset %}
{% endif %}
@ -74,7 +74,7 @@
{% if item.static_asset.download_url %}
<a href="{{ item.static_asset.download_url }}"
target="_blank"
download
download="{{ item.static_asset.get_download_name }}"
class="dropdown-item">
<i class="i-download me-1"></i>
<span>Download{% if additional_download %}{{ additional_download|capfirst }} {% endif %} <span class="text-muted">({{ item.static_asset.download_size }})</span></span>

View File

@ -4,7 +4,7 @@
{% if asset.contains_blend_file or asset.static_asset.source_type == "file" %}
<a href="{{ static_asset.download_url }}"
target="_blank"
download
download="{{ static_asset.get_download_name }}"
class="{% if additional_download %}dropdown-item{% else %}btn btn-link{% endif %}">
<i class="i-download me-1"></i>
<span>Download {% if additional_download %}{{ additional_download|capfirst }} {% endif %}<span class="text-muted">({{ static_asset.download_size }})</span></span>

View File

@ -196,15 +196,31 @@ class StaticAsset(
def __str__(self):
return f'({self.id}) {self.original_filename}'
def get_download_name(self) -> Optional[str]:
"""Return file name of what gets downloaded depending on available variations."""
if self.source_type != 'video' or not self.video:
return self.download_name
video = self.video
default_variation = video.default_variation
if default_variation:
return default_variation.download_name
return video.download_name
@property
def download_name(self) -> Optional[str]:
"""Return a human-readable file name, if possible."""
path = PurePosixPath(self.source.name)
ext = path.suffix
if self.original_filename:
return f'{slugify(PurePosixPath(self.original_filename).stem)}{ext}'
return None
@property
def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
path = PurePosixPath(self.source.name)
ext = path.suffix
filename = None
if self.original_filename:
filename = f'{slugify(PurePosixPath(self.original_filename).stem)}{ext}'
filename = self.download_name
if filename:
return f'attachment; filename="{filename}"'
@ -293,16 +309,20 @@ class Video(models.Model):
return f'{self._meta.model_name} {self.static_asset.original_filename}'
@property
def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
def download_name(self) -> Optional[str]:
path = PurePosixPath(self.source.name)
ext = path.suffix
filename = None
resolution_label = f'-{self.resolution_label}' if self.resolution_label else ''
original_filename = self.static_asset.original_filename
if original_filename:
filename = f'{slugify(PurePosixPath(original_filename).stem)}{resolution_label}{ext}'
return f'{slugify(PurePosixPath(original_filename).stem)}{resolution_label}{ext}'
return None
@property
def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
filename = self.download_name
if filename:
return f'attachment; filename="{filename}"'
@ -331,20 +351,22 @@ class VideoVariation(models.Model):
return f"Video variation for {self.video.static_asset.original_filename}"
@property
def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
def download_name(self) -> Optional[str]:
"""Return a human-readable file name, if possible."""
path = PurePosixPath(self.source.name)
ext = path.suffix
filename = None
resolution_label = f'-{self.resolution_label}' if self.resolution_label else ''
original_filename = self.video.static_asset.original_filename
section = getattr(self.video.static_asset, 'section', None)
# This is a training section video, use its name as a file name
if section:
filename = f'{slugify(section.name)}{resolution_label}{ext}'
elif original_filename:
filename = f'{slugify(PurePosixPath(original_filename).stem)}{resolution_label}{ext}'
return f'{slugify(section.name)}{resolution_label}{ext}'
return self.video.download_name
@property
def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
filename = self.download_name
if filename:
return f'attachment; filename="{filename}"'

View File

@ -34,7 +34,7 @@ class StaticAssetSerializer(common.serializers.IdModelSerializer):
class Meta:
model = StaticAsset
fields = '__all__'
exclude = ['source_storage']
writable_fields = (
'author_id',
'contributors_ids',