mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #3923 from modoboa/feature/webmail-save-drafts
Webmail: save message as draft
This commit is contained in:
@@ -67,7 +67,7 @@ jobs:
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
openldap:
|
||||
image: docker.pkg.github.com/modoboa/docker-openldap/docker-openldap
|
||||
image: ghcr.io/modoboa/docker-openldap/docker-openldap:latest
|
||||
ports:
|
||||
- 389/tcp
|
||||
env:
|
||||
|
||||
@@ -87,8 +87,6 @@ services:
|
||||
- redis
|
||||
volumes:
|
||||
- .:/code
|
||||
networks:
|
||||
- default
|
||||
|
||||
api-unsecured:
|
||||
container_name: modoboa_api_unsecured
|
||||
|
||||
@@ -108,8 +108,12 @@ export default {
|
||||
getComposeSession(uid) {
|
||||
return repository.get(`/webmail/compose-sessions/${uid}/`)
|
||||
},
|
||||
createComposeSession() {
|
||||
return repository.post('/webmail/compose-sessions/')
|
||||
createComposeSession(fromDraftMessageUid) {
|
||||
const data = {}
|
||||
if (fromDraftMessageUid) {
|
||||
data.from_draft_message = fromDraftMessageUid
|
||||
}
|
||||
return repository.post('/webmail/compose-sessions/', data)
|
||||
},
|
||||
getAllowedSenders() {
|
||||
return repository.get('/webmail/compose-sessions/allowed_senders/')
|
||||
@@ -123,6 +127,12 @@ export default {
|
||||
}
|
||||
)
|
||||
},
|
||||
saveComposeSession(sessionUid, data) {
|
||||
return repository.post(
|
||||
`/webmail/compose-sessions/${sessionUid}/save/`,
|
||||
data
|
||||
)
|
||||
},
|
||||
removeAttachment(sessionUid, name) {
|
||||
return repository.delete(
|
||||
`/webmail/compose-sessions/${sessionUid}/attachments/${name}/`
|
||||
|
||||
@@ -40,6 +40,14 @@
|
||||
:text="$gettext('Attachments') + ` (${attachmentCount})`"
|
||||
@click="openAttachmentsDialog"
|
||||
/>
|
||||
<v-btn
|
||||
class="ml-2"
|
||||
icon="mdi-content-save-outline"
|
||||
size="small"
|
||||
:title="$gettext('Save as draft')"
|
||||
:loading="working"
|
||||
@click="saveDraft"
|
||||
/>
|
||||
</v-toolbar>
|
||||
<v-form ref="formRef" class="flex-grow-1 d-flex flex-column">
|
||||
<div>
|
||||
@@ -266,33 +274,43 @@ const initForm = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const prepareMessage = () => {
|
||||
const result = { ...form.value }
|
||||
|
||||
if (result.to?.length) {
|
||||
const to = []
|
||||
for (const rcpt of result.to) {
|
||||
to.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
result.to = to
|
||||
}
|
||||
if (result.cc?.length) {
|
||||
const cc = []
|
||||
for (const rcpt of result.cc) {
|
||||
cc.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
result.cc = cc
|
||||
}
|
||||
if (result.bcc?.length) {
|
||||
const bcc = []
|
||||
for (const rcpt of result.bcc) {
|
||||
bcc.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
result.bcc = bcc
|
||||
}
|
||||
if (route.query.mailid) {
|
||||
result.mailid = route.query.mailid
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const submit = async (reload) => {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
const body = { ...form.value }
|
||||
|
||||
const to = []
|
||||
for (const rcpt of body.to) {
|
||||
to.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
body.to = to
|
||||
if (body.cc?.length) {
|
||||
const cc = []
|
||||
for (const rcpt of body.cc) {
|
||||
cc.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
body.cc = cc
|
||||
}
|
||||
if (body.bcc?.length) {
|
||||
const bcc = []
|
||||
for (const rcpt of body.bcc) {
|
||||
bcc.push(typeof rcpt === 'string' ? rcpt : rcpt.emails[0].address)
|
||||
}
|
||||
body.bcc = bcc
|
||||
}
|
||||
const body = prepareMessage()
|
||||
try {
|
||||
await api.sendEmailFromComposeSession(route.query.uid, body)
|
||||
router.push({ name: 'MailboxView' })
|
||||
@@ -337,8 +355,29 @@ const lookForContacts = debounce(async (search) => {
|
||||
}
|
||||
}, 500)
|
||||
|
||||
const initialize = (body) => {
|
||||
if (body.signature) {
|
||||
const initialize = async (body) => {
|
||||
if (route.query.mailid) {
|
||||
// Load draft
|
||||
const draft = await api.getEmailContent('Drafts', route.query.mailid)
|
||||
form.value.sender = draft.data.from_address.address
|
||||
if (draft.data.to?.length) {
|
||||
form.value.to = draft.data.to.map((rcpt) => rcpt.address)
|
||||
}
|
||||
if (draft.data.cc?.length) {
|
||||
form.value.cc = draft.data.cc.map((rcpt) => rcpt.address)
|
||||
showCcField.value = true
|
||||
}
|
||||
if (draft.data.bcc?.length) {
|
||||
form.value.bcc = draft.data.bcc.map((rcpt) => rcpt.address)
|
||||
showBccField.value = true
|
||||
}
|
||||
if (draft.data.subject) {
|
||||
form.value.subject = draft.data.subject
|
||||
}
|
||||
if (draft.data.body) {
|
||||
form.value.body = draft.data.body
|
||||
}
|
||||
} else if (body.signature) {
|
||||
if (form.value.body) {
|
||||
form.value.body += body.signature
|
||||
} else {
|
||||
@@ -363,6 +402,18 @@ const closeSchedulingForm = () => {
|
||||
showSchedulingForm.value = false
|
||||
}
|
||||
|
||||
const saveDraft = async () => {
|
||||
working.value = true
|
||||
const body = prepareMessage()
|
||||
try {
|
||||
await api.saveComposeSession(route.query.uid, body)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
working.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getItemTitle = (item) => {
|
||||
if (typeof item === 'string') {
|
||||
return item
|
||||
@@ -379,8 +430,9 @@ watch(
|
||||
)
|
||||
|
||||
if (!route.query.uid) {
|
||||
api.createComposeSession().then((resp) => {
|
||||
api.createComposeSession(route.query.mailid).then((resp) => {
|
||||
const query = { ...route.query, uid: resp.data.uid }
|
||||
attachmentCount.value = resp.data.attachments?.length || 0
|
||||
router.push({ name: route.name, query })
|
||||
initialize(resp.data)
|
||||
})
|
||||
|
||||
@@ -53,6 +53,15 @@
|
||||
@click="markEmailAsNotJunk"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="route.query.mailbox === 'Drafts'"
|
||||
class="ml-2"
|
||||
variant="tonal"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
@click="editDraft"
|
||||
>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn class="ml-2" variant="tonal" icon size="small">
|
||||
<v-icon icon="mdi-cog" />
|
||||
@@ -318,6 +327,13 @@ const replyToEmail = (all) => {
|
||||
const forwardEmail = () => {
|
||||
router.push({ name: 'ForwardEmailView', query: route.query })
|
||||
}
|
||||
|
||||
const editDraft = () => {
|
||||
router.push({
|
||||
name: 'ComposeEmailView',
|
||||
query: { mailid: route.query.mailid },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -353,8 +353,10 @@ def decode(value_bytes: bytes, encoding: str, append_to_error: str = "") -> str:
|
||||
return value
|
||||
|
||||
|
||||
def prepare_addresses(addresses, usage="header"):
|
||||
"""Prepare addresses before using them
|
||||
def prepare_addresses(
|
||||
addresses: list[str] | str, usage: str = "header"
|
||||
) -> list[str] | str:
|
||||
"""Prepare addresses before using them.
|
||||
|
||||
:param list addresses: a list of addresses
|
||||
:param string usage: how those addresses are going to be used
|
||||
|
||||
@@ -3,6 +3,7 @@ from email.mime.base import MIMEBase
|
||||
import json
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TypedDict
|
||||
import uuid
|
||||
|
||||
import six
|
||||
@@ -10,7 +11,6 @@ import six
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadhandler import FileUploadHandler, SkipFile
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import smart_bytes
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from modoboa.lib.exceptions import InternalError
|
||||
@@ -21,6 +21,11 @@ from modoboa.parameters import tools as param_tools
|
||||
from .rfc6266 import build_header
|
||||
|
||||
|
||||
Attachment = TypedDict(
|
||||
"Attachment", {"fname": str, "content-type": str, "size": int, "tmpname": str}
|
||||
)
|
||||
|
||||
|
||||
class ComposeSessionManager:
|
||||
|
||||
def __init__(self, username: str):
|
||||
@@ -63,15 +68,15 @@ def get_storage_path(filename):
|
||||
return os.path.join(storage_dir, filename)
|
||||
|
||||
|
||||
def save_attachment(request, session_uid: str, f) -> dict:
|
||||
"""Save a new attachment to the filesystem.
|
||||
def save_attachment_from_upload(request, session_uid: str, f) -> Attachment:
|
||||
"""
|
||||
Save a new attachment to the filesystem, directly from a Django upload.
|
||||
|
||||
The attachment is not saved using its own name to the
|
||||
filesystem. To avoid conflicts, a random name is generated and
|
||||
used instead.
|
||||
|
||||
:param f: an uploaded file object (see Django's documentation) or bytes
|
||||
:return: the new random name
|
||||
:param f: an uploaded file object (see Django's documentation)
|
||||
"""
|
||||
manager = ComposeSessionManager(request.user.username)
|
||||
if not manager.exists(session_uid):
|
||||
@@ -80,15 +85,12 @@ def save_attachment(request, session_uid: str, f) -> dict:
|
||||
fp = NamedTemporaryFile(dir=get_storage_path(""), delete=False)
|
||||
except Exception as e:
|
||||
raise InternalError(str(e)) from None
|
||||
if isinstance(f, six.binary_type | six.text_type):
|
||||
fp.write(smart_bytes(f))
|
||||
else:
|
||||
for chunk in f.chunks():
|
||||
fp.write(chunk)
|
||||
for chunk in f.chunks():
|
||||
fp.write(chunk)
|
||||
fp.close()
|
||||
|
||||
session = manager.get_content(session_uid)
|
||||
attachment = {
|
||||
attachment: Attachment = {
|
||||
"fname": str(f),
|
||||
"content-type": f.content_type,
|
||||
"size": f.size,
|
||||
@@ -99,6 +101,38 @@ def save_attachment(request, session_uid: str, f) -> dict:
|
||||
return attachment
|
||||
|
||||
|
||||
def save_attachment(
|
||||
request, session_uid: str, filename: str, content_type: str, content: str
|
||||
) -> Attachment:
|
||||
"""
|
||||
Save a new attachment to the filesystem
|
||||
|
||||
The attachment is not saved using its own name to the
|
||||
filesystem. To avoid conflicts, a random name is generated and
|
||||
used instead.
|
||||
"""
|
||||
manager = ComposeSessionManager(request.user.username)
|
||||
if not manager.exists(session_uid):
|
||||
raise Http404
|
||||
try:
|
||||
fp = NamedTemporaryFile(dir=get_storage_path(""), delete=False)
|
||||
except Exception as e:
|
||||
raise InternalError(str(e)) from None
|
||||
fp.write(content.encode("utf-8"))
|
||||
fp.close()
|
||||
|
||||
session = manager.get_content(session_uid)
|
||||
attachment: Attachment = {
|
||||
"fname": filename,
|
||||
"content-type": content_type,
|
||||
"size": len(content),
|
||||
"tmpname": os.path.basename(fp.name),
|
||||
}
|
||||
session["attachments"].append(attachment)
|
||||
manager.set_content(session_uid, session)
|
||||
return attachment
|
||||
|
||||
|
||||
def remove_attachment(request, session_uid: str, name: str) -> str | None:
|
||||
manager = ComposeSessionManager(request.user.username)
|
||||
session = manager.get_content(session_uid)
|
||||
|
||||
@@ -116,6 +116,19 @@ class ImapEmail(Email):
|
||||
self.dformat if self.dformat in self.bs.contents else fallback_fmt
|
||||
)
|
||||
|
||||
def fetch_attachments(self):
|
||||
result = []
|
||||
for partnum, filename in self.attachments.items():
|
||||
attdef, content = self.imapc.fetchpart(self.mailid, self.mbox, partnum)
|
||||
result.append(
|
||||
{
|
||||
"filename": filename,
|
||||
"content_type": attdef["Content-Type"],
|
||||
"content": content,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@property
|
||||
def subject(self):
|
||||
"""Just a shortcut to return a subject in any case."""
|
||||
@@ -190,7 +203,7 @@ class ImapEmail(Email):
|
||||
"\r\t\n"
|
||||
)
|
||||
break
|
||||
elif "disposition" in att and len(att["disposition"]) > 1:
|
||||
if "disposition" in att and len(att["disposition"]) > 1:
|
||||
for pos, value in enumerate(att["disposition"][1]):
|
||||
if not value.startswith("filename"):
|
||||
continue
|
||||
|
||||
@@ -37,11 +37,11 @@ class BodyStructure:
|
||||
|
||||
current_mailbox: str
|
||||
|
||||
def __init__(self, definition=None):
|
||||
def __init__(self, definition: list | None = None) -> None:
|
||||
self.is_multipart = False
|
||||
self.contents = {}
|
||||
self.attachments = []
|
||||
self.inlines = {}
|
||||
self.contents: dict[str, list] = {}
|
||||
self.attachments: list[dict] = []
|
||||
self.inlines: dict[str, dict] = {}
|
||||
|
||||
if definition is not None:
|
||||
self.load_from_definition(definition)
|
||||
@@ -125,7 +125,7 @@ class BodyStructure:
|
||||
def has_attachments(self) -> int:
|
||||
return len(self.attachments)
|
||||
|
||||
def find_attachment(self, pnum):
|
||||
def find_attachment(self, pnum: str) -> dict | None:
|
||||
for att in self.attachments:
|
||||
if pnum == att["pnum"]:
|
||||
return att
|
||||
|
||||
@@ -142,7 +142,7 @@ def create_message(
|
||||
else:
|
||||
msg = plain_msg(attributes.get("body", ""))
|
||||
msg.from_email = sender
|
||||
msg.to = attributes["to"]
|
||||
msg.to = attributes.get("to", [])
|
||||
msg.headers = headers
|
||||
msg.extra_headers = extra_headers
|
||||
for hdr in ["subject", "cc", "bcc"]:
|
||||
|
||||
@@ -9,6 +9,7 @@ from modoboa.lib import email_utils
|
||||
from modoboa.webmail import constants, models
|
||||
from modoboa.webmail.lib import imapheader, signature
|
||||
from modoboa.webmail.lib.imaputils import get_imapconnector
|
||||
from modoboa.webmail.lib.utils import create_message
|
||||
|
||||
|
||||
class GlobalParametersSerializer(serializers.Serializer):
|
||||
@@ -260,18 +261,14 @@ class ScheduledDatetimeMixin:
|
||||
return value
|
||||
|
||||
|
||||
class SendEmailSerializer(ScheduledDatetimeMixin, serializers.Serializer):
|
||||
class BaseEmailSerializer(serializers.Serializer):
|
||||
|
||||
sender = serializers.EmailField()
|
||||
to = serializers.ListField(child=serializers.EmailField())
|
||||
cc = serializers.ListField(child=serializers.EmailField(), required=False)
|
||||
bcc = serializers.ListField(child=serializers.EmailField(), required=False)
|
||||
subject = serializers.CharField(required=False)
|
||||
body = serializers.CharField(required=False)
|
||||
|
||||
in_reply_to = serializers.CharField(required=False)
|
||||
|
||||
scheduled_datetime = serializers.DateTimeField(required=False)
|
||||
to = serializers.ListField(child=serializers.EmailField(), required=False)
|
||||
cc = serializers.ListField(child=serializers.EmailField(), required=False)
|
||||
bcc = serializers.ListField(child=serializers.EmailField(), required=False)
|
||||
|
||||
def validate_to(self, value):
|
||||
return email_utils.prepare_addresses(value, "envelope")
|
||||
@@ -283,6 +280,35 @@ class SendEmailSerializer(ScheduledDatetimeMixin, serializers.Serializer):
|
||||
return email_utils.prepare_addresses(value, "envelope")
|
||||
|
||||
|
||||
class SendEmailSerializer(ScheduledDatetimeMixin, BaseEmailSerializer):
|
||||
|
||||
in_reply_to = serializers.CharField(required=False)
|
||||
scheduled_datetime = serializers.DateTimeField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["to"].required = True
|
||||
|
||||
|
||||
class SaveEmailSerializer(BaseEmailSerializer):
|
||||
|
||||
mailid = serializers.IntegerField(required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
message = create_message(
|
||||
self.context["request"].user, validated_data, self.context["attachments"]
|
||||
)
|
||||
drafts_folder = self.context["request"].user.parameters.get_value(
|
||||
"drafts_folder"
|
||||
)
|
||||
with get_imapconnector(self.context["request"]) as imapc:
|
||||
if "mailid" in validated_data:
|
||||
imapc.delete_mail(drafts_folder, validated_data["mailid"])
|
||||
mailid = imapc.push_mail(drafts_folder, message.message())
|
||||
imapc.mark_messages_unread(drafts_folder, [str(mailid)])
|
||||
return mailid
|
||||
|
||||
|
||||
class ComposeSessionSerializer(serializers.Serializer):
|
||||
|
||||
attachments = UploadedAttachmentSerializer(many=True, required=False)
|
||||
@@ -297,6 +323,11 @@ class ComposeSessionSerializer(serializers.Serializer):
|
||||
return str(signature.EmailSignature(self.context["request"].user))
|
||||
|
||||
|
||||
class CreateSessionSerializer(serializers.Serializer):
|
||||
|
||||
from_draft_message = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
class AllowedSenderSerializer(serializers.Serializer):
|
||||
|
||||
address = serializers.EmailField()
|
||||
|
||||
@@ -285,6 +285,18 @@ class ComposeSessionViewSetTestCase(WebmailTestCase):
|
||||
self.authenticate()
|
||||
self._create_compose_session()
|
||||
|
||||
def test_create_from_draft(self):
|
||||
self.authenticate()
|
||||
data = {"from_draft_message": 3444}
|
||||
url = reverse("v2:webmail-compose-session-list")
|
||||
with self.settings(MEDIA_ROOT=self.workdir):
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
uid = response.json()["uid"]
|
||||
manager = ComposeSessionManager(self.user.username)
|
||||
content = manager.get_content(uid)
|
||||
self.assertEqual(len(content["attachments"]), 1)
|
||||
|
||||
def test_get(self):
|
||||
self.authenticate()
|
||||
uid = self._create_compose_session()
|
||||
@@ -338,6 +350,45 @@ class ComposeSessionViewSetTestCase(WebmailTestCase):
|
||||
response = self.client.delete(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_save(self):
|
||||
self.authenticate()
|
||||
uid = self._create_compose_session()
|
||||
# Upload an attachment
|
||||
url = reverse("v2:webmail-compose-session-attachments", args=[uid])
|
||||
with self.settings(MEDIA_ROOT=self.workdir):
|
||||
response = self.client.post(url, {"attachment": get_gif()})
|
||||
# Save the message as draft
|
||||
url = reverse("v2:webmail-compose-session-save", args=[uid])
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"sender": self.user.email,
|
||||
"to": ["test@example.test"],
|
||||
"subject": "test",
|
||||
"body": "Test",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()["mailid"], 11)
|
||||
|
||||
def test_save_existing_draft(self):
|
||||
self.authenticate()
|
||||
uid = self._create_compose_session()
|
||||
url = reverse("v2:webmail-compose-session-save", args=[uid])
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"sender": self.user.email,
|
||||
"to": ["test@example.test"],
|
||||
"subject": "test",
|
||||
"body": "Test",
|
||||
"mailid": 11,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send(self):
|
||||
self.authenticate()
|
||||
uid = self._create_compose_session()
|
||||
|
||||
+45
-12
@@ -312,8 +312,12 @@ class ComposeSessionViewSet(viewsets.GenericViewSet):
|
||||
return response
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "create":
|
||||
return serializers.CreateSessionSerializer
|
||||
if self.action == "send":
|
||||
return serializers.SendEmailSerializer
|
||||
if self.action == "save":
|
||||
return serializers.SaveEmailSerializer
|
||||
if self.action == "allowed_senders":
|
||||
return serializers.AllowedSenderSerializer
|
||||
return serializers.ComposeSessionSerializer
|
||||
@@ -327,10 +331,39 @@ class ComposeSessionViewSet(viewsets.GenericViewSet):
|
||||
return response.Response(serializer.data)
|
||||
|
||||
def create(self, request):
|
||||
uid = attachments.ComposeSessionManager(request.user.username).create()
|
||||
serializer = self.get_serializer({"uid": uid})
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
manager = attachments.ComposeSessionManager(request.user.username)
|
||||
uid = manager.create()
|
||||
from_draft_message = serializer.validated_data.get("from_draft_message")
|
||||
response_attrs = {"uid": uid}
|
||||
if from_draft_message:
|
||||
mailbox = request.user.parameters.get_value("drafts_folder")
|
||||
email = lib.ImapEmail(
|
||||
request,
|
||||
f"{mailbox}:{from_draft_message}",
|
||||
)
|
||||
email.fetch_body_structure()
|
||||
for attachment in email.fetch_attachments():
|
||||
attachments.save_attachment(request, uid, **attachment)
|
||||
response_attrs["attachments"] = manager.get_content(uid)["attachments"]
|
||||
|
||||
serializer = serializers.ComposeSessionSerializer(
|
||||
response_attrs, context=self.get_serializer_context()
|
||||
)
|
||||
return response.Response(serializer.data, status=201)
|
||||
|
||||
@action(methods=["post"], detail=True)
|
||||
def save(self, request, pk):
|
||||
manager = attachments.ComposeSessionManager(request.user.username)
|
||||
context = self.get_serializer_context()
|
||||
context["attachments"] = manager.get_content(pk)["attachments"]
|
||||
serializer = self.get_serializer(data=request.data, context=context)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
mailid = serializer.save()
|
||||
serializer.validated_data["mailid"] = mailid
|
||||
return response.Response(serializer.validated_data)
|
||||
|
||||
@action(methods=["get"], detail=False)
|
||||
def allowed_senders(self, request):
|
||||
addresses = [{"address": request.user.email}]
|
||||
@@ -360,7 +393,7 @@ class ComposeSessionViewSet(viewsets.GenericViewSet):
|
||||
% request.upload_handlers[0].maxsize
|
||||
]
|
||||
return response.Response(errors, status=400)
|
||||
result = attachments.save_attachment(
|
||||
result = attachments.save_attachment_from_upload(
|
||||
request, pk, serializer.validated_data["attachment"]
|
||||
)
|
||||
return response.Response(result)
|
||||
@@ -385,15 +418,15 @@ class ComposeSessionViewSet(viewsets.GenericViewSet):
|
||||
manager.get_content(pk)["attachments"],
|
||||
)
|
||||
return response.Response(status=204)
|
||||
else:
|
||||
status, error = send_mail(
|
||||
request,
|
||||
serializer.validated_data,
|
||||
manager.get_content(pk)["attachments"],
|
||||
)
|
||||
if status:
|
||||
attachments.remove_attachments_and_session(manager, pk)
|
||||
return response.Response(status=204)
|
||||
|
||||
status, error = send_mail(
|
||||
request,
|
||||
serializer.validated_data,
|
||||
manager.get_content(pk)["attachments"],
|
||||
)
|
||||
if status:
|
||||
attachments.remove_attachments_and_session(manager, pk)
|
||||
return response.Response(status=204)
|
||||
return response.Response({"error": error}, status=400)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user