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 %} {% if item.static_asset.download_url %}
<a href="{{ item.static_asset.download_url }}" <a href="{{ item.static_asset.download_url }}"
target="_blank" target="_blank"
download download="{{ item.static_asset.get_download_name }}"
class="dropdown-item"> class="dropdown-item">
<i class="i-download me-1"></i> <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> <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.preview_video_static_asset.video.tracks.count as preview_video_has_subtitles %}
{% firstof "" item.static_asset.video.tracks.count as has_subtitles %} {% firstof "" item.static_asset.video.tracks.count as has_subtitles %}
{# in case there's more than one downloadable, show a dropdown #} {# 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 %} {% 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 %} {% 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"> <button data-bs-toggle="dropdown" data-bs-target="#downloadDropdown" class="btn btn-link dropdown-toggle">
<i class="i-chevron-down"></i> <i class="i-chevron-down"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-end" id="downloadDropdown"> <div class="dropdown-menu dropdown-menu-end" id="downloadDropdown">
{% if item.preview_video_static_asset %} {% if item.preview_video_static_asset %}
{% include "common/components/navigation/download_button.html" with static_asset=item.preview_video_static_asset additional_download="video" %} {% include "common/components/navigation/download_button.html" with static_asset=item.preview_video_static_asset additional_download="video" %}
{% endif %} {% endif %}
{% if has_subtitles or preview_video_has_subtitles %} {% if has_subtitles or preview_video_has_subtitles %}
{% with static_asset=item.preview_video_static_asset|default:item.static_asset %} {% with static_asset=item.preview_video_static_asset|default:item.static_asset %}
{% firstof item.name item as vtt_filename %} {% firstof item.name item as vtt_filename %}
{% include "common/components/navigation/download_subtitles.html" with name=vtt_filename %} {% include "common/components/navigation/download_subtitles.html" with name=vtt_filename %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
{% include "common/components/navigation/download_button.html" with static_asset=item.static_asset %} {% include "common/components/navigation/download_button.html" with static_asset=item.static_asset %}
{% endif %} {% endif %}
@ -74,7 +74,7 @@
{% if item.static_asset.download_url %} {% if item.static_asset.download_url %}
<a href="{{ item.static_asset.download_url }}" <a href="{{ item.static_asset.download_url }}"
target="_blank" target="_blank"
download download="{{ item.static_asset.get_download_name }}"
class="dropdown-item"> class="dropdown-item">
<i class="i-download me-1"></i> <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> <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" %} {% if asset.contains_blend_file or asset.static_asset.source_type == "file" %}
<a href="{{ static_asset.download_url }}" <a href="{{ static_asset.download_url }}"
target="_blank" target="_blank"
download download="{{ static_asset.get_download_name }}"
class="{% if additional_download %}dropdown-item{% else %}btn btn-link{% endif %}"> class="{% if additional_download %}dropdown-item{% else %}btn btn-link{% endif %}">
<i class="i-download me-1"></i> <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> <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): def __str__(self):
return f'({self.id}) {self.original_filename}' 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 @property
def content_disposition(self) -> Optional[str]: def content_disposition(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header.""" """Try to get a human-readable file name for Content-Disposition header."""
path = PurePosixPath(self.source.name) filename = self.download_name
ext = path.suffix
filename = None
if self.original_filename:
filename = f'{slugify(PurePosixPath(self.original_filename).stem)}{ext}'
if filename: if filename:
return f'attachment; filename="{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}' return f'{self._meta.model_name} {self.static_asset.original_filename}'
@property @property
def content_disposition(self) -> Optional[str]: def download_name(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header."""
path = PurePosixPath(self.source.name) path = PurePosixPath(self.source.name)
ext = path.suffix ext = path.suffix
filename = None
resolution_label = f'-{self.resolution_label}' if self.resolution_label else '' resolution_label = f'-{self.resolution_label}' if self.resolution_label else ''
original_filename = self.static_asset.original_filename original_filename = self.static_asset.original_filename
if 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: if filename:
return f'attachment; filename="{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}" return f"Video variation for {self.video.static_asset.original_filename}"
@property @property
def content_disposition(self) -> Optional[str]: def download_name(self) -> Optional[str]:
"""Try to get a human-readable file name for Content-Disposition header.""" """Return a human-readable file name, if possible."""
path = PurePosixPath(self.source.name) path = PurePosixPath(self.source.name)
ext = path.suffix ext = path.suffix
filename = None
resolution_label = f'-{self.resolution_label}' if self.resolution_label else '' 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) section = getattr(self.video.static_asset, 'section', None)
# This is a training section video, use its name as a file name # This is a training section video, use its name as a file name
if section: if section:
filename = f'{slugify(section.name)}{resolution_label}{ext}' return f'{slugify(section.name)}{resolution_label}{ext}'
elif original_filename: return self.video.download_name
filename = f'{slugify(PurePosixPath(original_filename).stem)}{resolution_label}{ext}'
@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: if filename:
return f'attachment; filename="{filename}"' return f'attachment; filename="{filename}"'

View File

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