fix: Premailer failed to parse the report HTML.

pull/14965/head
wangruidong 2025-03-03 16:44:34 +08:00 committed by w940853815
parent 8b2276ce08
commit edd998da20
7 changed files with 227 additions and 921 deletions

View File

@ -1,11 +1,12 @@
{% load i18n %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<div class="report-container">
<header class="header">
<img src="{% static 'img/JumpServer_white_logo.svg' %}" alt="Logo"/>
{% autoescape off %}
{{ logo }}
{% endautoescape %}
</header>
<div class="info-section">
@ -48,155 +49,6 @@
</main>
</div>
<style>
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header img {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}
</style>
{% include './css/report.css' %}
</style>

View File

@ -5,7 +5,9 @@
<div class="report-container">
<header class="header">
<img src="{% static 'img/JumpServer_white_logo.svg' %}" alt="Logo"/>
{% autoescape off %}
{{ logo }}
{% endautoescape %}
</header>
<div class="info-section">
@ -120,195 +122,6 @@
</div>
</main>
</div>
<style>
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header img {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-summery-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
gap: 2rem;
width: inherit;
margin-top: 2rem;
}
.main-section .tabel-summery-section .result-section {
width: 100%;
padding: 1rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header {
padding-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table {
margin-bottom: 0;
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.no-data {
text-align: center;
color: #6c757d;
padding: 2rem;
background: #f6f6f6;
border-radius: 8px;
margin: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}
</style>
{% include './css/report.css' %}
</style>

View File

@ -1,11 +1,12 @@
{% load i18n %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<div class="report-container">
<header class="header">
<img src="{% static 'img/JumpServer_white_logo.svg' %}" alt="Logo"/>
{% autoescape off %}
{{ logo }}
{% endautoescape %}
</header>
<div class="info-section">
@ -100,194 +101,5 @@
</div>
<style>
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header img {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-summery-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
gap: 2rem;
width: inherit;
margin-top: 2rem;
}
.main-section .tabel-summery-section .result-section {
width: 100%;
padding: 1rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header {
padding-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table {
margin-bottom: 0;
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.no-data {
text-align: center;
color: #6c757d;
padding: 2rem;
background: #f6f6f6;
border-radius: 8px;
margin: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}
</style>
{% include './css/report.css' %}
</style>

View File

@ -0,0 +1,194 @@
html {
font-size: 10px;
}
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header svg {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-summery-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
gap: 2rem;
width: inherit;
margin-top: 2rem;
}
.main-section .tabel-summery-section .result-section {
width: 100%;
padding: 1rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header {
padding-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table {
margin-bottom: 0;
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.no-data {
text-align: center;
color: #6c757d;
padding: 2rem;
background: #f6f6f6;
border-radius: 8px;
margin: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}

View File

@ -1,12 +1,12 @@
{% load i18n %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<div class="report-container">
<header class="header">
<img src="{% static 'img/JumpServer_white_logo.svg' %}" alt="Logo"/>
{% autoescape off %}
{{ logo }}
{% endautoescape %}
</header>
<div class="info-section">
@ -123,193 +123,5 @@
</div>
<style>
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header img {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-summery-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
gap: 2rem;
width: inherit;
margin-top: 2rem;
}
.main-section .tabel-summery-section .result-section {
width: 100%;
padding: 1rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header {
padding-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table {
margin-bottom: 0;
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.no-data {
text-align: center;
color: #6c757d;
padding: 2rem;
background: #f6f6f6;
border-radius: 8px;
margin: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}
{% include './css/report.css' %}
</style>

View File

@ -5,7 +5,9 @@
<div class="report-container">
<header class="header">
<img src="{% static 'img/JumpServer_white_logo.svg' %}" alt="Logo"/>
{% autoescape off %}
{{ logo }}
{% endautoescape %}
</header>
<div class="info-section">
@ -122,193 +124,5 @@
</div>
<style>
body,
p {
margin: 0;
padding: 0;
}
.report-container {
display: flex;
flex-direction: column;
}
.report-container .header {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
padding: 0.3rem 1rem;
background-color: #148f76;
}
.report-container .header img {
height: 100%
}
.info-section {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f5f5;
height: 4rem;
padding: 0 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.info-section .info {
margin: unset;
font-size: 1.6rem;
}
.main-section {
margin-top: 3rem;
padding: 0 1rem;
}
.main-section .synopsis-section {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.main-section .synopsis-section .synopsis-item {
display: flex;
flex-direction: column;
flex: 1;
padding: 1rem 2rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
height: 350px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-section .synopsis-section .synopsis-item h3 {
font-weight: 500;
font-size: 1.5rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
height: 100%;
cursor: pointer;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p {
display: inline-flex;
width: 100%;
line-height: 1;
gap: 2rem;
font-size: 14px;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content p .item-label {
width: 18rem;
}
.main-section .synopsis-section .synopsis-item .synopsis-item-content span {
align-items: center;
justify-content: center;
font-weight: normal;
}
.main-section .tabel-summery-section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
gap: 2rem;
width: inherit;
margin-top: 2rem;
}
.main-section .tabel-summery-section .result-section {
width: 100%;
padding: 1rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.main-section .tabel-execution-section h3 {
font-size: 1.5rem;
color: #2c3e50;
}
.section-header {
padding-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
color: #2c3e50;
font-size: inherit;
}
.section-header span {
background: #e8f5e9;
color: #2e7d32;
padding: 0.2rem 0.8rem;
border-radius: 1rem;
font-size: inherit;
}
.custom-table {
margin-bottom: 0;
width: 100%;
border-collapse: collapse;
overflow: hidden;
}
.custom-table th {
background: #f6f6f6;
color: #495057;
padding: 0.75rem;
font-size: 1.5rem;
border-bottom: 2px solid #e0e0e0;
}
.custom-table td {
padding: 0.75rem;
font-size: 14px;
border-bottom: 1px solid #e0e0e0;
}
.custom-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.custom-table tr:last-child td {
border-bottom: none;
}
.no-data {
text-align: center;
color: #6c757d;
padding: 2rem;
background: #f6f6f6;
border-radius: 8px;
margin: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.new-accounts .section-header span {
background: #e8f5e9;
color: #2e7d32;
}
.lost-accounts .section-header span {
background: #fbe9e7;
color: #d84315;
}
{% include './css/report.css' %}
</style>

View File

@ -134,12 +134,21 @@ class BaseManager:
return f"Automation {self.execution.id} finished"
def get_report_context(self):
logo = self.get_file_content("static/img/JumpServer_white_logo.svg")
return {
"execution": self.execution,
"summary": self.execution.summary,
"result": self.execution.result,
"logo": logo,
}
@staticmethod
def get_file_content(path):
file_path = os.path.join(settings.BASE_DIR, path)
with open(file_path, "r", encoding="utf-8") as f:
file_content = f.read()
return file_content
def send_report_if_need(self):
recipients = self.execution.recipients
if not recipients:
@ -147,7 +156,7 @@ class BaseManager:
print("Send report to: ", ",".join([str(u) for u in recipients]))
report = self.gen_report()
report = transform(report)
report = transform(report, cssutils_logging_level="CRITICAL")
subject = self.get_report_subject()
emails = [r.email for r in recipients if r.email]
send_mail_async(subject, report, emails, html_message=report)